Advertisement

虚拟网络技术:TUN设备

阅读量:

本文首发于我的公众号码农之屋**(id: Spider1818)** ,专注于干货分享,包含但不限于Java编程、网络技术、Linux内核及实操、容器技术等。欢迎大家关注,二维码文末可以扫。

导读: 云化场景到处都是虚拟机和容器,它们背后的网络管理都离不开虚拟网络设备,了解虚拟网络设备将有助于我们更好地理解云化场景的网络架构。本篇文章将对Linux的TUN进行介绍。


一、虚拟网络设备和物理网络设备的区别

网络设备就像是一个管道,从其中任意一端收到的数据将从另一端发送出去。 比如物理网卡,它的两端分别是内核协议栈和物理网络,从物理网络收到的数据,会转发给内核协议栈,而应用程序从协议栈发过来的数据将会通过物理网络发送出去。

对于Linux内核网络设备管理模块来说,虚拟设备和物理设备没什么区别,都是网络设备,都能配置IP,从网络设备来的数据,都会转发给内核协议栈,而从协议栈过来的数据,也会交由网络设备发送出去(怎么发送出去的,发到哪里去,那是设备驱动的事情,跟Linux内核没关系)。所以说 虚拟网络设备的一端也是协议栈,而另一端是什么则取决于虚拟网络设备的驱动实现。


**二、TUN是什么?**它的另一端是什么?

TUN Linux内核的 一种虚拟三层网络设备 ,纯软件实现, 通过此设备可以处理来自网络层的数据。

从上图可以看出TUN设备和物理设备eth0之间的差别,它们的一端虽然都连着协议栈,但另一端却不一样,eth0连接的是物理网络,而TUN设备的另一端是一个用户态程序,协议栈发给TUN设备的数据包能被这个应用程序读取到,并且应用程序能直接向TUN设备写数据。

这里列举一个典型的TUN设备的应用场景,host1的进程A跟host2的进程B通信,利用TUN驱动进程,实现隧道封装,从而实现以VPN方式进行通信。

这里假设host1的eth0 IP为10.10.10.1/24,TUN设备IP为172.168.1.1,而host2的eth0 IP为10.100.10.1/24,TUN设备IP为192.168.1.1。host1和host2网络是互通的。

下面来我们先来看看在host1上,数据包的发包流程:

1、host1的应用程序A通过socket发送了一个数据包,目的IP地址是172.168.1.100

2、socket将这个数据包丢给内核协议栈处理

3、协议栈根据数据包的目的IP地址,匹配本地路由规则,知道这个数据包应该由TUN虚拟设备出去,于是将数据包交给TUN设备

4、TUN设备收到数据包之后,发现另一端被TUN驱动进程打开了,于是将数据包丢给了TUN驱动进程

5、TUN驱动进程收到数据包之后,做一些跟业务相关的处理,然后构造一个新的数据包,将原来的数据包嵌入在新的数据包中,最后通过socke将数据包转发出去,这时候新数据包的源地址变成了eth0的地址,而目的IP地址变成了host2的eth0的IP

6、socket将数据包丢给内核协议栈

7、协议栈根据本地路由,发现这个数据包应该要通过eth0发送出去,于是将数据包交给eth0

8、eth0通过物理网络将数据包发送出去

接下来我们看下在host2上,数据包的收包流程:

1、eth0收到数据包之后,把数据包丢给了内核协议栈处理

2、协议栈进行拆包(仅拆除了外层封装,内层封装由TUN驱动进程处理),根据目的端口号把数据包发给了TUN驱动进程(两主机的TUN驱动进程都是监听同个端口号)

3、TUN驱动进程拿到数据包后进行解包处理,然后把解出来的包发给TUN设备

4、TUN设备收到包之后移交给内核协议栈处理

5、协议栈根据目的IP,发现是送给应用程序B的,于是将数据包交给程序B,最终程序B就收到了程序A给它发的数据包

从上面的流程中可以看出,数据包选择走哪个网络设备完全由路由表控制,所以如果我们想让某些网络流量走隧道封装的转发流程,就需要配置路由表让这部分数据走TUN设备。


三、TUN设备有什么用?

从上面介绍过的流程可以看出来,TUN设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。于是比较常用的数据压缩,加密等功能就可以在TUN设备驱动程序实现, TUN设备最常用的场景是VPN,包括tunnel以及应用层的IPSec。


四、示例程序

这里写了一个程序,它收到TUN设备的数据包之后,只打印出收到了多少字节的数据包,其它的什么都不做。

复制代码
 #include <net/if.h>

    
 #include <sys/ioctl.h>
    
 #include <sys/stat.h>
    
 #include <fcntl.h>
    
 #include <string.h>
    
 #include <sys/types.h>
    
 #include <linux/if_tun.h>
    
 #include<stdlib.h>
    
 #include<stdio.h>
    
  
    
 int tun_alloc(int flags)
    
 {
    
  
    
     struct ifreq ifr;
    
     int fd, err;
    
     char *clonedev = "/dev/net/tun";
    
  
    
     if ((fd = open(clonedev, O_RDWR)) < 0) {
    
     return fd;
    
     }
    
  
    
     memset(&ifr, 0, sizeof(ifr));
    
     ifr.ifr_flags = flags;
    
  
    
     if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) {
    
     close(fd);
    
     return err;
    
     }
    
  
    
     printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name);
    
  
    
     return fd;
    
 }
    
  
    
 int main()
    
 {
    
  
    
     int tun_fd, nread;
    
     char buffer[1500];
    
  
    
     /* Flags: IFF_TUN   - TUN device (no Ethernet headers)
    
      *        IFF_TAP   - TAP device
    
      *        IFF_NO_PI - Do not provide packet information
    
      */
    
     tun_fd = tun_alloc(IFF_TUN | IFF_NO_PI);
    
  
    
     if (tun_fd < 0) {
    
     perror("Allocating interface");
    
     exit(1);
    
     }
    
  
    
     while (1) {
    
     nread = read(tun_fd, buffer, sizeof(buffer));
    
     if (nread < 0) {
    
         perror("Reading from interface");
    
         close(tun_fd);
    
         exit(1);
    
     }
    
  
    
     printf("Read %d bytes from tun/tap device\n", nread);
    
     }
    
  
    
     return 0;
    
 }

我的公众号**「码农之屋」(id: Spider1818)** ,分享的内容包括但不限于 Linux、网络、云计算虚拟化、容器Docker、OpenStack、Kubernetes、SDN、OVS、DPDK、Go、Python、C/C++编程技术等内容,欢迎大家关注。

全部评论 (0)

还没有任何评论哟~