TUN/TAP
TUN/TAP 虚拟网络设备一端连着协议栈,另外一端不是物理网络,而是另外一个处于用户空间的应用程序。也就是说,协议栈发给 TUN/TAP 的数据包能被这个应用程序读取到,当然应用程序能直接向 TUN/TAP 发送数据包。
一个典型的 TUN/TAP 的例子如下图所示:
上图中我们配置了一个物理网卡,IP 为 18.12.0.92,而 tun0 为一个 TUN/TAP 设备,IP 配置为 10.0.0.12。数据包的流向为:
-
应用程序 A 通过 socket A 发送了一个数据包,假设这个数据包的目的 IP 地址是
10.0.0.22 -
socket A 将这个数据包丢给协议栈
-
协议栈根据本地路由规则和数据包的目的 IP,将数据包由给 tun0 设备发送出去
-
tun0 收到数据包之后,将数据包转发给给了用户空间的应用程序 B
-
应用程序 B 收到数据包之后构造一个新的数据包,将原来的数据包嵌入在新的数据包(IPIP 包)中,最后通过 socket B 将数据包转发出去
Note: 新数据包的源地址变成了 eth0 的地址,而目的 IP 地址则变成了另外一个地址
18.13.0.91. -
socket B 将数据包发给协议栈
-
协议栈根据本地路由规则和数据包的目的 IP,决定将这个数据包要通过 eth0 发送出去,于是将数据包转发给 eth0
-
eth0 通过物理网络将数据包发送出去
我们看到发送给 10.0.0.22的网络数据包通过在用户空间的应用程序 B,利用 18.12.0.92发到远端网络的 18.13.0.91,网络包到达 18.13.0.91后,读取里面的原始数据包,读取里面的原始数据包,再转发给本地的 10.0.0.22。这就是VPN的基本原理。
使用 TUN/TAP 设备我们有机会将协议栈中的部分数据包转发给用户空间的应用程序,让应用程序处理数据包。常用的使用场景包括数据压缩,加密等功能。
Note: TUN 和 TAP 设备的区别在于,TUN 设备是一个虚拟的端到端 IP 层设备,也就是说用户空间的应用程序通过 TUN 设备只能读写 IP 网络数据包(三层),而 TAP 设备是一个虚拟的链路层设备,通过 TAP 设备能读写链路层数据包(二层)。在使用
ip命令创建设备的时候使用--dev tun和--dev tap来区分。
代码示例
这里写了一个程序,它收到 tun 设备的数据包之后,只打印出收到了多少字节的数据包,其它的什么都不做,如何编程请参考后面的参考链接。
|
|
虚拟设备演示
|
|
什么是 tun/tap?
TUN/TAP 虚拟网络设备为用户空间程序提供了网络数据包的发送和接收能力。他既可以当做点对点设备(TUN),也可以当做以太网设备(TAP)。实际上,不仅 Linux 支持 TUN/TAP 虚拟网络设备,其他 UNIX 也是支持的,他们之间只有少许差别。
TUN/TAP 虚拟网络设备的原理比较简单,他在 Linux 内核中添加了一个 TUN/TAP 虚拟网络设备的驱动程序和一个与之相关连的字符设备/dev/net/tun,字符设备 tun 作为用户空间和内核空间交换数据的接口。当内核将数据包发送到虚拟网络设备时,数据包被保存在设备相关的一个队列中,直到用户空间程序通过打开的字符设备 tun 的描述符读取时,它才会被拷贝到用户空间的缓冲区中,其效果就相当于,数据包直接发送到了用户空间。通过系统调用 write 发送数据包时其原理与此类似。
值得注意的是:一次read系统调用,有且只有一个数据包被传送到用户空间,并且当用户空间的缓冲区比较小时,数据包将被截断,剩余部分将永久地消失,write系统调用与read类似,每次只发送一个数据包。所以在编写此类程序的时候,请用足够大的缓冲区,直接调用系统调用read/write,避免采用C语言的带缓存的IO函数。
TUN/TAP 是一类虚拟网卡的驱动。网卡驱动很好理解,就是 netdev+driver,最后将数据包通过这些驱动发送出去,netdev 可以参考内核或者 OVS 代码,基本使用的就是几个钩子函数。
虚拟网卡就是没有物理设备的网卡,那么他的驱动就是需要开发人员自己编写。一般虚拟网卡用于实现物理网卡不愿意做的事情,例如 tunnel 封装(用于 vpn,openvpn( http://openvpn.sourceforge.net)和Vtun( http://vtun.sourceforge.net)),多个物理网卡的聚合等。一般使用虚拟网卡的方式与使用物理网卡一样,在协议栈中通过回调函数call到虚拟网卡的API,经过虚拟网卡处理之后的数据包再由协议栈发送出去。
tun/tap 的使用
linux2.4 内核之后代码默认编译 tun、tap 驱动,使用的时候只需要将模块加载即可(modprobe tun,mknod /dev/net/tun c 10 200)。运行 tun、tap 设备之后,会在内核空间添加一个杂项设备(miscdevice,类比字符设备、块设备等)/dev/net/tun,实质上是主设备号 10 的字符设备。从功能上看,tun 设备驱动主要应该包括两个部分,一是虚拟网卡驱动,其实就是虚拟网卡中对 skb 进行封装解封装等操作;二是字符设备驱动,用于内核空间与用户空间的交互。
源代码在/drivers/net/tun.c 中,与其他 netdev 类似,tun 这个 netdev 也提供 open、close、read、write 等 API。在分析 TUN/TAP 驱动实现前,我们先看下如何使用。使用 tun/tap 设备的示例程序(摘自 openvpn 开源项目 http://openvpn.sourceforge.net,tun.c 文件)
|
|
调用上述函数后,就可以在 shell 命令行下使用 ifconfig 命令配置虚拟网卡了:
|
|
配置好虚拟网卡地址后,就可以通过生成的字符设备描述符,在程序中使用 read 和 write 函数就可以读取或者发送给虚拟的网卡数据了。
tun/tap 的实现
tun/tap 设备驱动的开始也是 init 函数,其中主要调用了 misc_register 注册了一个 miscdev 设备。
|
|
而 tun_miscdev 得定义如下:
|
|
注册完这个设备之后将在系统中生成一个“/dev/net/tun”文件,同字符设备类似,当应用程序使用 open 系统调用打开这个文件时,将生成 file 文件对象,而其 file_operations 将指向 tun_fops。
|
|
下面我们以应用层使用的步骤来分析内核的对应实现。应用层首先调用 open 打开“/dev/net/tun”,这将最终调用 tun_fops 的 open 函数,即 tun_chr_open。
|
|
经过这个函数后,整个数据结构的关系就如下图所示。注意这里的 struct file 结构就是每次应用调用 open 打开/dev/net/tun 生成的。
应用程序执行完 open 操作后,一般会执行 ioctl (fd, TUNSETIFF, (void *) &ifr) 来真正创建 tap/tun 设备。这将最终调用 tun_ops 中的 tun_chr_ioctl 函数。
l tun_chr_ioctl
tun_chr_ioctl 中会调用__tun_chr_ioctl。
|
|
可以看出如果 cmd 是 TUNSETIFF,则会调用 tun_set_iff 函数。
l tun_set_iff
点击(此处)折叠或打开
|
|
经过这个函数之后 tun/tap 的相关数据结构组织就如下图所示了。
数据通道实现
下面我们看 tun/tap 设备是如何进行数据转发的,我们从两个方向分析,首先是用户态到内核态,然后是内核态到用户态。整个过程如下图所示。
用户态到内核态
用户态调用 write 向 tun/tap 设备中写入数据,最终调用 kernel 中对应 file 结构中的 tun_fops 的 write 函数。整个调用路径如下。
注意 tun/tap 设备的 net_device 并没有向 bridge 那样注册 rx_handle 接受函数,所以经过 netif_receive_skb 后就进入了上层协议栈,对于系统来说就像从物理网卡 eth0 接受上来的包一样。如果用户态想在接受,就要创建 socket 了。
内核态到用户态
从 tun/tap 设备发出的数据包,就需要调用 net_device 的 ndo_start_xmit 函数了。整个流程如下图(橙色的线)。这里要说明一点,有人可能会疑惑如果所有进程都打开”/dev/net/tun”读取数据不会混淆吗?答案是不会的,因为每个进程 open 后内核都有自己的 file 文件对象,同时 TUNSETIFF 后也会有不同的 net_device 设备对象。
参考资料
-
No backlinks found.