리눅스 TUN/TAP 디바이스 프로그래밍

반응형

 

    개요

    TUN 과 TAP(Terminal Access Point) 은 가상 네트워크 커널 인터페이스입니다.

    위키피디아(https://en.wikipedia.org/wiki/TUN/TAP)에 따르면, 운영체제가 TUN/TAP 디바이스로 전송한 패킷들은 해당 TUN/TAP 디바이스에 연결된 사용자공간 프로그램으로 전달되며, 사용자공간 프로그램이 TUN/TAP 디바이스로 전송한 패킷들은 운영체제 네트워크 스택으로 전달됩니다.

    TUN 디바이스는 IP 계층에서 이러한 역할을 수행하고, TAP 디바이스는 데이터링크 계층에서 이러한 역할을 수행합니다.

     

    사용법

    다음 명령으로 디바이스를 생성합니다.

    mkdir /dev/net (없을 경우)
    mknod /dev/net/tun c 10 200

     

    다음 명령으로 디바이스 권한을 설정합니다.

    chmod 0666 /dev/net/tun

     

    드라이버 모듈을 로딩합니다.

    modprobe tun

     

     

     

    어플리케이션 프로그램 작성법

    초기화 루틴

    • IFF_TUN 플래그 : TUN 디바이스로 생성합니다. (IP 패킷이 교환됨)
    • IFF_TAP 플래그 : TAP 디바이스로 생성합니다. (Ethernet 패킷이 교환됨)
    • IFF_NO_PI : 설정할 경우, 패킷정보가 전달되지 않습니다.
      • 패킷정보는 4바이트 길이를 가지며, 앞 2바이트는 Flags, 뒤 2바이트는 프로토콜 유형(예: IP, IPv6 - 즉, EtherType)을 나타냅니다.
    #include <linux/if.h>
    #include <linux/if_tun.h>
    
    int tun_alloc(char *dev)
    {
        struct ifreq ifr;
        int fd, err;
    
        if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
           return tun_alloc_old(dev);
    
        memset(&ifr, 0, sizeof(ifr));
    
        /* Flags: IFF_TUN   - TUN device (no Ethernet headers) 
         *        IFF_TAP   - TAP device  
         *
         *        IFF_NO_PI - Do not provide packet information  
         */ 
        ifr.ifr_flags = IFF_TUN; 
        if( *dev )
           strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    
        if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
           close(fd);
           return err;
        }
        strcpy(dev, ifr.ifr_name);
        return fd;
    }

     

    read/write (패킷 송/수신) 를 동시에 처리하기 위해 여러 개의 큐(=file descriptor)를 생성할 수도 있습니다.

    #include <linux/if.h>
    #include <linux/if_tun.h>
    
    int tun_alloc_mq(char *dev, int queues, int *fds)
    {
        struct ifreq ifr;
        int fd, err, i;
    
        if (!dev)
            return -1;
    
        memset(&ifr, 0, sizeof(ifr));
        /* Flags: IFF_TUN   - TUN device (no Ethernet headers)
         *        IFF_TAP   - TAP device
         *
         *        IFF_NO_PI - Do not provide packet information
         *        IFF_MULTI_QUEUE - Create a queue of multiqueue device
         */
        ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE;
        strcpy(ifr.ifr_name, dev);
    
        for (i = 0; i < queues; i++) {
            if ((fd = open("/dev/net/tun", O_RDWR)) < 0)
               goto err;
            err = ioctl(fd, TUNSETIFF, (void *)&ifr);
            if (err) {
               close(fd);
               goto err;
            }
            fds[i] = fd;
        }
    
        return 0;
    err:
        for (--i; i >= 0; i--)
            close(fds[i]);
        return err;
    }

     

    큐는 기본적으로 생성과 동시에 활성화 상태가 되지만, 다음 코드를 통해 각 큐를 활성화, 비활성화 시킬 수도 있습니다.

    #include <linux/if.h>
    #include <linux/if_tun.h>
    
    int tun_set_queue(int fd, int enable)
    {
        struct ifreq ifr;
    
        memset(&ifr, 0, sizeof(ifr));
    
        if (enable)
           ifr.ifr_flags = IFF_ATTACH_QUEUE;
        else
           ifr.ifr_flags = IFF_DETACH_QUEUE;
    
        return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
    }

     

    패킷 수신 루틴

    위에서 생성된 파일 디스크립터에 대한 read() 함수 호출을 통해 패킷을 수신할 수 있습니다.

    위에서 TUN 디바이스로 생성했을 경우에는 IP 패킷이, TAP 디바이스로 생성했을 경우에는 Ethernet 패킷이 수신됩니다.

    /* Now read data coming from the kernel */
    while(1) {
      /* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */
      nread = read(tun_fd,buffer,sizeof(buffer));
      if(nread < 0) {
        perror("Reading from interface");
        close(tun_fd);
        exit(1);
      }
    
      /* Do whatever with the data */
      printf("Read %d bytes from device %s\n", nread, tun_name);
      for (int i = 0; i < nread; i++) {
        if ((i!=0) && (i%16==0)) {
          printf("\n");
        }
        printf("%02X ", buffer[i]);
      }
      printf("\n");
    }

     

     

     

    샘플 어플리케이션 작성 및 실행 (예제)

    코드 작성

    다음은 TUN/TAP 디바이스로부터 패킷을 수신하는 샘플 어플리케이션입니다.

    #include <sys/socket.h> /// <linux/if.h> 보다 위에 선언되어야 한다.
    #include <linux/if.h>
    #include <linux/if_tun.h>
    #include <sys/ioctl.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    
    
    int tun_alloc(char *dev, int flags) 
    {
      struct ifreq ifr;
      int fd, err;
      char *clonedev = "/dev/net/tun";
    
      printf("Allocating tun device\n");
    
      /* Arguments taken by the function:
       *
       * char *dev: the name of an interface (or '\0'). MUST have enough space to hold the interface name if '\0' is passed
       * int flags: interface flags (eg, IFF_TUN etc.)
       */
    
       /* open the clone device */
       if( (fd = open(clonedev, O_RDWR)) < 0 ) {
         printf("Fail to open clone device %s - %m\n", clonedev);
         return fd;
       }
    
       /* preparation of the struct ifr, of type "struct ifreq" */
       memset(&ifr, 0, sizeof(ifr));
    
       ifr.ifr_flags = flags;   /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */
    
       if (*dev) {
         /* if a device name was specified, put it in the structure; otherwise,
          * the kernel will try to allocate the "next" device of the
          * specified type */
         strncpy(ifr.ifr_name, dev, IFNAMSIZ);
         printf("Create device - %s\n", dev);
       }
    
       /* try to create the device */
       if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
         printf("Fail to create device - %m\n");
         close(fd);
         return err;
       }
    
      /* if the operation was successful, write back the name of the
       * interface to the variable "dev", so the caller can know
       * it. Note that the caller MUST reserve space in *dev (see calling
       * code below) */
      strcpy(dev, ifr.ifr_name);
    
      printf("Success to allocate tun device %s\n", dev);
    
      /* this is the special file descriptor that the caller will use to talk
       * with the virtual interface */
      return fd;
    }
    
    
    int main(int argc, char *argv[])
    {
      int nread;
      char buffer[2000];
      char tun_name[IFNAMSIZ];
      char tap_name[IFNAMSIZ];
      
        /* Connect to the device */
      strcpy(tun_name, "tun77");
      int tun_fd = tun_alloc(tun_name, IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE);  /* tun interface : IFF_TUN */
      if(tun_fd < 0){
        perror("Allocating interface");
        exit(1);
      }
    
      /* Now read data coming from the kernel */
      while(1) {
        /* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */
        nread = read(tun_fd,buffer,sizeof(buffer));
        if(nread < 0) {
          perror("Reading from interface");
          close(tun_fd);
          exit(1);
        }
    
        /* Do whatever with the data */
        printf("Read %d bytes from device %s\n", nread, tun_name);
        for (int i = 0; i < nread; i++) {
          if ((i!=0) && (i%16==0)) {
            printf("\n");
          }
          printf("%02X ", buffer[i]);
        }
        printf("\n");
      }
    }

     

     

     

    어플리케이션 실행

    # ./tun-test &
    [1] 9664
    Allocating tun device
    Create device - tun77
    Success to allocate tun device tun77
    root@imx6qpdlsolox:~/20200214#
    root@imx6qpdlsolox:~/20200214# ifconfig tun77 up
    root@imx6qpdlsolox:~/20200214# Read 90 bytes from device tun77
    33 33 00 00 00 16 02 DB 09 4C 6C D6 86 DD 60 00
    00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
    00 00 00 00 00 16 3A 00 05 02 00 00 01 00 8F 00
    02 68 00 00 00 01 04 00 00 00 FF 02 00 00 00 00
    00 00 00 00 00 01 FF 4C 6C D6
    
    root@imx6qpdlsolox:~/20200214# Read 78 bytes from device tun77
    33 33 FF 4C 6C D6 02 DB 09 4C 6C D6 86 DD 60 00
    00 00 00 18 3A FF 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
    00 01 FF 4C 6C D6 87 00 99 07 00 00 00 00 FE 80
    00 00 00 00 00 00 00 DB 09 FF FE 4C 6C D6
    Read 90 bytes from device tun77
    33 33 00 00 00 16 02 DB 09 4C 6C D6 86 DD 60 00
    00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
    00 00 00 00 00 16 3A 00 05 02 00 00 01 00 8F 00
    02 68 00 00 00 01 04 00 00 00 FF 02 00 00 00 00
    00 00 00 00 00 01 FF 4C 6C D6
    
    # ifconfig
    eth0      Link encap:Ethernet  HWaddr fe:ff:ff:f4:35:76
              inet addr:192.168.0.60  Bcast:192.168.0.255  Mask:255.255.255.0
              inet6 addr: fe80::fcff:ffff:fef4:3576/64 Scope:Link
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:405239 errors:0 dropped:33801 overruns:0 frame:0
              TX packets:3472 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:61232351 (58.3 MiB)  TX bytes:304234 (297.1 KiB)
    
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              inet6 addr: ::1/128 Scope:Host
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:97480 errors:0 dropped:0 overruns:0 frame:0
              TX packets:97480 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1
              RX bytes:7408704 (7.0 MiB)  TX bytes:7408704 (7.0 MiB)
    
    tun77     Link encap:Ethernet  HWaddr 02:db:09:4c:6c:d6
              inet6 addr: fe80::db:9ff:fe4c:6cd6/64 Scope:Link
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:18 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:0 (0.0 B)  TX bytes:3502 (3.4 KiB)
    

     

    댓글

    Designed by JB FACTORY