![Kubernetes网络权威指南:基础、原理与实践](https://wfqqreader-1252317822.image.myqcloud.com/cover/281/27741281/b_27741281.jpg)
1.4 给用户态一个机会:tun/tap设备
我们在1.3节讲解Linux bridge时就向读者介绍过tun/tap设备,并强调tun/tap设备在虚拟机的组网过程中起到作用。但促使我们用一节的篇幅介绍它的另一个原因是:tun/tap设备是理解flannel的基础,而flannel是一个重要的Kubernetes网络插件。
tun/tap设备到底是什么?从Linux文件系统的角度看,它是用户可以用文件句柄操作的字符设备;从网络虚拟化角度看,它是虚拟网卡,一端连着网络协议栈,另一端连着用户态程序。
如果把veth pair称为设备孪生,那么tun/tap就像是一对表兄弟。虽然很多情况下我们都是连带提到它们,但它们还是有些区别的。tun表示虚拟的是点对点设备,tap表示虚拟的是以太网设备,这两种设备针对网络包实施不同的封装。
tun/tap设备有什么作用呢?tun/tap设备可以将TCP/IP协议栈处理好的网络包发送给任何一个使用tun/tap驱动的进程,由进程重新处理后发到物理链路中。tun/tap设备就像是埋在用户程序空间的一个钩子,我们可以很方便地将对网络包的处理程序挂在这个钩子上,OpenVPN、Vtun、flannel都是基于它实现隧道包封装的。
1.4.1 tun/tap设备的工作原理
我们先简单介绍物理设备上的数据是如何通过Linux网络栈送达用户态程序的,tun/tap设备的基本原理如图1-10所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_79.jpg?sign=1739267644-Qgw6iRLCRyEAnZ4v1DcHHMwUWu56Dpq6-0-e4e1335cc621de4969dba216b8073a53)
图1-10 tun/tap设备的基本原理
图1-10是一个经典的、通过Socket调用实现用户态和内核态数据交互的过程。物理网卡从网线接收数据后送达网络协议栈,而进程通过Socket创建特殊套接字,从网络协议栈读取数据。
从网络协议栈的角度看,tun/tap设备这类虚拟网卡与物理网卡并无区别。只是对tun/tap设备而言,它与物理网卡的不同表现在它的数据源不是物理链路,而是来自用户态!这也是tun/tap设备的最大价值所在。提前“剧透”:flannel的UDP模式的技术要点就是tun/tap设备。
tun/tap设备其实就是利用Linux的设备文件实现内核态和用户态的数据交互,而访问设备文件则会调用设备驱动相应的例程,要知道设备驱动也是内核态和用户态的一个接口。tun设备的工作模式如图1-11所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_80.jpg?sign=1739267644-kPa6DrFRrsKgolux3rWUvpCAzEZWxuHI-0-b82e1ac633fe9c5add3eb0b647161e81)
图1-11 tun设备的工作模式
普通的物理网卡通过网线收发数据包,而tun设备通过一个设备文件(/dev/tunX)收发数据包。所有对这个文件的写操作会通过tun设备转换成一个数据包传送给内核网络协议栈。当内核发送一个包给tun设备时,用户态的进程通过读取这个文件可以拿到包的内容。当然,用户态的程序也可以通过写这个文件向tun设备发送数据。
tap设备与tun设备的工作原理完全相同,区别在于:
·tun设备的/dev/tunX文件收发的是IP包,因此只能工作在L3,无法与物理网卡做桥接,但可以通过三层交换(例如ip_forward)与物理网卡连通;
·tap设备的/dev/tapX文件收发的是链路层数据包,可以与物理网卡做桥接。
1.4.2 利用tun设备部署一个VPN
tun设备的tun是英文隧道(tunnel)的缩写,言下之意,tun设备似乎与隧道网络存在一丝联系。tun/tap设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。常见的tun/tap设备使用场景有数据压缩、加密等,最常见的是VPN,包括tunnel及应用层的IPSec等。我们将使用tun设备搭建一个基于UDP的VPN,网络拓扑如图1-12所示。
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_81.jpg?sign=1739267644-qtMAgWi5JVjHFmuEKB7ysizZUADZtMCF-0-0d729648669f6234c89e9a5dd69077ac)
图1-12 使用tun设备搭建一个基于UDP的VPN
如上所示,数据包的流程包括:
(1)App1是一个普通的程序,通过Socket API发送了一个数据包,假设这个数据包的目的IP地址是192.168.1.3(和tun0在同一个网段)。
(2)程序A的数据包到达网络协议栈后,协议栈根据数据包的目的IP地址匹配到这个数据包应该由tun0网口出去,于是将数据包发送给tun0网卡。
(3)tun0网卡收到数据包之后,发现网卡的另一端被App2打开了(这也是tun/tap设备的特点,一端连着协议栈,另一端连着用户态程序),于是将数据包发送给App2。
(4)App2收到数据包之后,通过报文封装(将原来的数据包封装在新的数据报文中,假设新报文的原地址是eth0的地址,目的地址是和eth0在同一个网段的VPN对端IP地址,例如100.89.104.22)构造出一个新的数据包。App2通过同样的Socket API将数据包发送给协议栈。
(5)协议栈根据本地路由,发现这个数据包应该通过eth0发送出去,于是将数据包交给eth0,最后eth0通过物理网络将数据包发送给VPN的对端。
综上所述,发到192.168.1.0/24网络的数据首先通过监听在tun0设备上的App2进行封包,利用eth0这块物理网卡发到远端网络的物理网卡上,从而实现VPN。
不难看出,VPN网络的报文真正从物理网卡出去要经过网络协议栈两次,因此会有一定的性能损耗。另外,经过用户态程序的处理,数据包可能已经加密,包头进行了封装,所以第二次通过网络栈内核看到的是截然不同的网络包。这个过程和我们后面要讨论的flannel容器组网方案有异曲同工之处,flannel网络的本质就是一个隧道网络,后面我们会做更深入的介绍。
1.4.3 tun设备编程
我们将用一个简单的C语言程序示范tun设备的具体使用方法。这个程序在收到tun设备的数据包之后,打印出收到了多少字节的数据包。程序代码如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_82.jpg?sign=1739267644-6HZAmiSQKgLbv7vg8zHQ92HHZIQe4Dxu-0-6370cf1dc9ac8177945f4ec7a9abf0dc)
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_83.jpg?sign=1739267644-HYz2i6V4ci5eHYMCZEBC65R7DEEXsJRR-0-baed9fe164ac1971bffc281107a76efb)
假设我们把以上C语言代码保存在tun.c文件中,然后编译成tun二进制文件:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_84.jpg?sign=1739267644-7Dz9UxS3QrfjNSQcHeRdVKXwiZJgSXRR-0-1557d4fceb9b503b77bbc57035221d22)
tun程序启动后会一直阻塞并等待接收数据包:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_85.jpg?sign=1739267644-yFDKJct1M7mjn4vsCuyWymlJOl4sflKh-0-c083fdf721f2b248517c2fc5105d7de2)
虽然tun程序目前还没有任何输出,但系统已经自动创建了一块新的tun设备。打开另一个shell terminal,通过ip命令查看网卡,输出如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_86.jpg?sign=1739267644-7eFmE0SvG8GgXCYMNzmTi5H6cARu2avM-0-5ba2d5fbff5e88ad22b9e6d323a926ee)
这块tun0网卡没有被分配IP地址,初始状态也是DOWN。从tun0网卡的POINTOPOINT输出,也可以验证上文提到的“tun表示虚拟点对点设备”。接下来,将给它分配IP地址192.128.1.2,并设置状态为UP:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_87.jpg?sign=1739267644-olS9tEwGF1XnJZR3EvbAyrkP63NUPifZ-0-7661aed7881e9205f0657559f44747d4)
有意思的是,当我们做好上面一系列配置后,tun0网卡上已经收到3个48字节的报文:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_88.jpg?sign=1739267644-hbJhkwsTy4thNggPVO0yqGe6uswn0VpJ-0-cdded1d07c08f6044073ddd0a3d75679)
再来给tun0发送4个ping包看看会发生什么:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_89.jpg?sign=1739267644-xC9sMMZEq3jA5BLZWZPi96W2PhrgiEhA-0-97e4f3a32750094af0f20a024c817ed9)
尽管ping包没有返回,但当我们切回shell terminal会发现tun0网卡上又收到了6个84字节的报文,如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_91.jpg?sign=1739267644-UZW3TJcx18cNo0YpNXwMEtvZlMBnah0F-0-62ac976f76fb3e5ecd52b0acf5ca1c71)
对上述现象的解释是,ping包发送给了tun0网卡。因为我们的程序在收到数据包后不做任何处理也没有返回报文,所以我们看不到ping的回程报文。我们可以在发送ping包的同时通过tcpdump抓包看到发出去的4个icmp echo请求包,如下所示:
![](https://epubservercos.yuewen.com/D7598A/15937388804515906/epubprivate/OEBPS/Images/txt001_92.jpg?sign=1739267644-O5b8YLzM6dllg4sPrLzszvLtIaj6vN1d-0-971ba83d589f3a36525b1e3dd04e6cac)
这也说明数据包正确地发送给了程序tun。