TUN/TAPデバイスの浅い分析(二)--TUN/TAPのプログラミング

4772 ワード

この記事では、TUN/TAPデバイスのプログラミングについて詳しく説明したいと思います.
実はこの2つの設備のプログラミングについて、基本的には8つの文に属して、みんなは普通このようにします.
デバイスを起動する前に
あるlinuxはtunモジュールをカーネルにコンパイルしていないので、私たちが最初にしなければならないことは、私たちのシステムがTUN/TAPをサポートしているかどうかを確認することです.詳細については、ここを参照してください.http://blog.csdn.net/lishuhuakai/article/details/70305543ああ、この文章はもう余計なことは言わない.
tunモジュールだけでは十分ではありません.前の文書で説明したファイルを作成し、コマンドを実行します.
% sudo mknod /dev/net/tun c 10 200 # c       ,10 200            

これにより、/dev/net/ディレクトリの下にtunという名前のファイルが表示されます.もちろんここのtunは任意のあなたの好きな名前に変えることができます.
デバイスの起動
TUNデバイスの場合、一般的には次のように初期化されます.
int 
tun_alloc(char dev[IFNAMSIZ]) // dev           
{
  struct ifreq ifr;
  int fd, err;

  if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { //     
    perror("open");
    return -1;
  }

  bzero(&ifr, sizeof(ifr));
  
  /* Flags : IFF_TUN   - TUN  
   *         IFF_TAP   - TAP  
   *         IFF_NO_PI -          
   */
  
  ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // tun          , tap  ,    

  if (*dev) {
    strncpy(ifr.ifr_name, dev, IFNAMSIZ); 
  }

  if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { //     
    perror("ioctl TUNSETIFF");
    close(fd);
    return err;
  }
  //         ,              tun  ,   tunX,X  0     ,  tap  
  //    tapX,X  0     
  strcpy(dev, ifr.ifr_name); //         dev 
  return fd;
}

TAPデバイスを起動したい場合は、上記のifr.ifr_flags = IFF_TUN | IFF_NO_PI;ifr.ifr_flags = IFF_TAP | IFF_NO_PI;に変更すれば簡単です.では、TAPデバイスを起動します.
ネットワークアドレスの設定
上のコードはファイルを開き、ファイルの記述子を返しますが、まだ十分ではありません.1枚のNICにとって、ネットワークアドレスを構成しなければなりません.ルーティング情報さえあれば、NICは正常に動作します.
仮想TUN/TAPデバイスの起動に成功すると、コマンドでアドレスを設定できます.
例えばTAPデバイスを例に挙げます.
% sudo ip link set dev tap0 up  #   tap0  ,        ,      ipconfig       tap0    ,           ip  

% sudo ip address add dev tap0 10.0.1.5/24 #  tap0  ip  

% ifconfig  #    ifconfig         tap0   
tap0: flags=4163  mtu 1500
        inet 10.0.1.5  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::1872:80ff:fe20:46e2  prefixlen 64  scopeid 0x20
        ether 1a:72:80:20:46:e2  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

        
% ip route show #          
default via 192.168.140.2 dev ens33 proto static metric 100
10.0.0.0/24 dev ens39 proto kernel scope link src 10.0.0.130 metric 100
10.0.1.0/24 dev tap0 proto kernel scope link src 10.0.1.5 #      ip ,         
192.168.140.0/24 dev ens33 proto kernel scope link src 192.168.140.133 metric 100

手動でコマンドを叩くことでtap 0デバイスを構成するのは面倒ですが、システム関数を直接プログラムで呼び出すことができます.
int 
run_cmd(char *cmd, ...)
{
    va_list ap;
    char buf[CMDBUFLEN];
    va_start(ap, cmd);
    vsnprintf(buf, CMDBUFLEN, cmd, ap);
    va_end(ap);
    if (debug) { // DEBUG       
        printf("EXEC: %s
", buf); } return system(buf); }

上のコマンドをrun_に直接渡します.cmd関数でよい.
もちろん、このような方法が好きでない場合は、次の関数を使用するなど、他の方法もあります.
int
set_stack_attribute(char *dev)
{
    struct ifreq ifr;
    struct sockaddr_in addr;
    int sockfd, err = -1;

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, tapaddr, &addr.sin_addr);

    bzero(&ifr, sizeof(ifr));
    strcpy(ifr.ifr_name, dev);
    bcopy(&addr, &ifr.ifr_addr, sizeof(addr));
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    // ifconfig tap0 10.0.1.5 #  ip  
    if ((err = ioctl(sockfd, SIOCSIFADDR, (void *)&ifr)) < 0) {
        perror("ioctl SIOSIFADDR");
        goto done;
    }

    /*         */
    if ((err = ioctl(sockfd, SIOCGIFFLAGS, (void *)&ifr)) < 0) {
        perror("ioctl SIOCGIFADDR");
        goto done;
    }

    /*         */
    ifr.ifr_flags |= IFF_UP;
    // ifup tap0 #    
    if ((err = ioctl(sockfd, SIOCSIFFLAGS, (void *)&ifr)) < 0) {
        perror("ioctl SIOCSIFFLAGS");
        goto done;
    }

    inet_pton(AF_INET, "255.255.255.0", &addr.sin_addr);
    bcopy(&addr, &ifr.ifr_netmask, sizeof(addr));
    // ifconfig tap0 10.0.1.5/24 #      
    if ((err = ioctl(sockfd, SIOCSIFNETMASK, (void *) &ifr)) < 0) {
        perror("ioctl SIOCSIFNETMASK");
        goto done;
    }


done:
    close(sockfd);
    return err;
}

上の関数は主に上のコマンドとほぼ同じことをします.
データの送受信
データの送受信は非常に簡単で、戻ってきたファイル記述子を読み取るたびにデータを受信することができ、データが来ないときは、ずっとどこかにブロックされています.もちろん、非ブロックIOを遊んで、データを送信したい場合は、そのファイル記述子に対応するファイルにデータを書き込むだけでいいです.