Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

Vinllen Chen


To be a better coder

tun/tap运行机制

  最近需要连接Quagga和SDN Controller,由于Quagga原因,目前没办法直接在两者之间建立连接,所以只能曲线救国,采用tun/tap搞个虚拟网卡中转一下。本文主要介绍以下tun/tap的机制,关于具体连接部分将在以后进行介绍。
  tun和tap的区别在于tun是三层设备,用于ip转发,无法与物理网卡做 bridge,但是可以通过三层交换(如 ip_forward)与物理网卡连通;tap是二层设备,用于mac转发。
  以下几个图来自kghost的博客
  首先是正常网卡接收报文是从物理链路接收的: tun2
  而虚拟网卡(tun/tap)是从用户态接收数据,在用户态和协议栈之间进行中转数据。数据从用户态读进来,穿透/dev/net/tun字符设备抵达内核,然后交给虚拟网卡,虚拟网卡将报文通过协议栈交给用户态进程tun/tap驱动进程,反之亦然。具体在字符设备如何在用户态和内核态传递数据可以参考这篇博客,介绍的比较详细。 tun3
  当然,tun/tap的用途可以用来搞VPN,数据包两次经过协议栈,在App地方实现数据包的封装加密,从而达到目的。 tun4
  以下代码是介绍如何创建一个虚拟网卡的过程,具体全部代码可以查看simpletun

int tun_alloc(char *dev, int flags) {

  struct ifreq ifr;
  int fd, err;

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

  memset(&ifr, 0, sizeof(ifr));

  ifr.ifr_flags = flags;

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

  if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {
    perror("ioctl(TUNSETIFF)");
    close(fd);
    return err;
  }

  strcpy(dev, ifr.ifr_name);

  return fd;
}

  tun_alloc函数创建一个虚拟网卡设备,并返回一个文件描述符,我们可以通过对该描述符的read/write达到数据交互的目的。创建完之后可以通过ifconfig -a查看到设备的存在,但是目前的状态的down的,我们需要把它分配个ip并up起来,假设文件名为abc: ifconfig abc 20.1.1.1/24,ok,这时候查看ifconfig发现设备已经起来了。这个时候我们当然可以ping这个ip,但是由于环回地址lo的存在,所有本地的ip都不会经过协议栈到达网卡,而是直接返回了。正确的做法是我们先检查路由表:route -n,发现存在这样一条路由表项:Destination=20.1.1.0/24 Iface=abc,表示发往20.1.1.0/24网段的都从abc网卡出去,所以ping这个网段的主机就会到达这个虚拟网卡。然后可以ping这个20.1.1.0/24网段内的主机地址,这样就会从abc网卡上发出报文了:ping 20.1.1.2。报文就会穿过虚拟网卡到达用户态的App,我们就可以read读取这个报文了。虚拟网卡在内核态传递给用户态时的做法是缓存报文,用户态调用read时才会传递给用户态。我们可以按如下代码实现最简单的读取:

 ...
  /* tunclient.c */

  char tun_name[IFNAMSIZ];

  /* Connect to the device */
  strcpy(tun_name, "tun77");
  tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI);  /* tun interface */

  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);
  }

  我们此时ping 20.1.1.2会发现不断显示读取到报文。
  写到这里,tun/tap实现中转部分已经差不多介绍完毕了,那么剩下的连接部分我们就可以在用户态完成,比如左边的App连接Controller,右边的socket api连接Quagga,两者都可以建立socket连接,采用epoll/select/poll方式监听多个描述符,就比如simpletun中所使用的poll建立vpn连接,都是一个道理。
  之后的博客我将会介绍具体如何连接Controller和Quagga。

参考:

http://www.ibm.com/developerworks/cn/linux/l-tuntap/
https://blog.kghost.info/2013/03/27/linux-network-tun/
http://stackoverflow.com/questions/9374165/tuntap-interface-in-c-linux-cant-capture-udp-packets-sent-on-the-tuntap-wit
http://www.cnblogs.com/heidsoft/p/3525788.html


About the author

vinllen chen

Beijing, China

格物致知


Discussions

comments powered by Disqus