Kubernetes网络权威指南:基础、原理与实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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所示。

图1-10 tun/tap设备的基本原理

图1-10是一个经典的、通过Socket调用实现用户态和内核态数据交互的过程。物理网卡从网线接收数据后送达网络协议栈,而进程通过Socket创建特殊套接字,从网络协议栈读取数据。

从网络协议栈的角度看,tun/tap设备这类虚拟网卡与物理网卡并无区别。只是对tun/tap设备而言,它与物理网卡的不同表现在它的数据源不是物理链路,而是来自用户态!这也是tun/tap设备的最大价值所在。提前“剧透”:flannel的UDP模式的技术要点就是tun/tap设备。

tun/tap设备其实就是利用Linux的设备文件实现内核态和用户态的数据交互,而访问设备文件则会调用设备驱动相应的例程,要知道设备驱动也是内核态和用户态的一个接口。tun设备的工作模式如图1-11所示。

图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所示。

图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设备的数据包之后,打印出收到了多少字节的数据包。程序代码如下所示:

假设我们把以上C语言代码保存在tun.c文件中,然后编译成tun二进制文件:

tun程序启动后会一直阻塞并等待接收数据包:

虽然tun程序目前还没有任何输出,但系统已经自动创建了一块新的tun设备。打开另一个shell terminal,通过ip命令查看网卡,输出如下所示:

这块tun0网卡没有被分配IP地址,初始状态也是DOWN。从tun0网卡的POINTOPOINT输出,也可以验证上文提到的“tun表示虚拟点对点设备”。接下来,将给它分配IP地址192.128.1.2,并设置状态为UP:

有意思的是,当我们做好上面一系列配置后,tun0网卡上已经收到3个48字节的报文:

再来给tun0发送4个ping包看看会发生什么:

尽管ping包没有返回,但当我们切回shell terminal会发现tun0网卡上又收到了6个84字节的报文,如下所示:

对上述现象的解释是,ping包发送给了tun0网卡。因为我们的程序在收到数据包后不做任何处理也没有返回报文,所以我们看不到ping的回程报文。我们可以在发送ping包的同时通过tcpdump抓包看到发出去的4个icmp echo请求包,如下所示:

这也说明数据包正确地发送给了程序tun。