TUN 设备原理
本文要阐述的就是 sslvpn 的运行机制。对其问题进行技术上的细化,就是要解决两个被公网环境独立开的私网,如何建立通道。核心细节就是tun设备。TUN设备还被用来在flannel中,作为跨宿主机容器间网络通信。
simpletun
首先玩一下简单的例子 https://github.com/gregnietsky/simpletun.git 然后有个直观的感受
我们找2台机器 分别使用make
命令编译上述代码,生成 二进制 simpletun
A物理ip 11.238.116.73
B物理ip 11.238.116.75
在B机器上执行
sudo ./simpletun -i tun90 -s -d &
sudo ifconfig tun90 6.6.6.2/24
在A机器上执行
sudo ./simpletun -i tun90 -c 11.238.116.75 -d &
sudo ifconfig tun90 6.6.6.1/24
simpletun 二进制会帮助生成一个叫做 tun90
的虚拟网卡,然后我们使用ifconfig来进行ip地址的分配
其次,B机器上,通过-s
告诉simpletun 监听 本机TCP 的 55555 端口,并且作为服务端;A机器上通过-c
命令附加B的物理ip。
启动完成之后,在A机器上执行ping 6.6.6.2
会出现如下日志,部分日志是ping的输出,表示ping成功,部分是simpletun打印的日志。
PING 6.6.6.2 (6.6.6.2) 56(84) bytes of data.
TAP2NET 4: Read 84 bytes from the tap interface
TAP2NET 4: Written 84 bytes to the network
NET2TAP 4: Read 84 bytes from the network
NET2TAP 4: Written 84 bytes to the tap interface
64 bytes from 6.6.6.2: icmp_seq=1 ttl=64 time=39.7 ms
TAP2NET 5: Read 84 bytes from the tap interface
TAP2NET 5: Written 84 bytes to the network
NET2TAP 5: Read 84 bytes from the network
NET2TAP 5: Written 84 bytes to the tap interface
64 bytes from 6.6.6.2: icmp_seq=2 ttl=64 time=39.9 ms
同样的,我们在B上面执行nc -l -k 9090
监听 本机9090端口程序,然后再A上面执行nc 6.6.6.2 9090
后敲入abc
A的输出
$nc 6.6.6.2 9090
TAP2NET 12: Read 60 bytes from the tap interface
TAP2NET 12: Written 60 bytes to the network
NET2TAP 11: Read 60 bytes from the network
NET2TAP 11: Written 60 bytes to the tap interface
TAP2NET 13: Read 52 bytes from the tap interface
TAP2NET 13: Written 52 bytes to the network
abc
TAP2NET 14: Read 56 bytes from the tap interface
TAP2NET 14: Written 56 bytes to the network
NET2TAP 12: Read 52 bytes from the network
NET2TAP 12: Written 52 bytes to the tap interface
B的输出
NET2TAP 12: Read 60 bytes from the network
NET2TAP 12: Written 60 bytes to the tap interface
TAP2NET 11: Read 60 bytes from the tap interface
TAP2NET 11: Written 60 bytes to the network
NET2TAP 13: Read 52 bytes from the network
NET2TAP 13: Written 52 bytes to the tap interface
NET2TAP 14: Read 56 bytes from the network
NET2TAP 14: Written 56 bytes to the tap interface
TAP2NET 12: Read 52 bytes from the tap interface
TAP2NET 12: Written 52 bytes to the network
abc
我们发现,A机器通过tcp 访问 6.6.6.2的9090端口,这个6.6.6.0/24相当于私网请求,而 A和B之间的网路根本没有6.6.6.0/24网段的路由,那么 B如何收到到A发送的私网请求呢,这当然是我们本文需要解释的疑惑。
TUN设备特性
对于 tun 设备的write
操作(open tun,然后对fd进行write),tun在内核态获取到用户态数据后,调用netif_rx
,通常情况下,netif_rx
这个函数只有在机器收到报文是调用提交给协议栈,说明我们对tun设备的write
操作,相当于让机器模拟收包。所以很关键的一点就是,tunwrite
的数据必须要包含三次协议头(IP),否则linux协议栈无法将其路由。
对于 tun 设备的获取数据操作,首先tun设备在什么情况下,会获取到数据呢?即当我们在外部应用程序毫无感知tun设备存在的情况下,tun设备是如何获取到外部程序的数据的。
首先tun设备是有三层ip地址的,也就是意味着,tun设备可以被作为三层转发接口发送数据,我们只需要让 外部程序 发出的请求,被路由到tun设备即可。例如 我们访问200.100.1.1这个公网地址走tun设备,route add -net 200.100.1.1/32 dev tun90
,这样请求就会走tun口出去。
然后 tun设备的 驱动程序 和 常规的类似eth不同,它不会真的将数据从自己接口发出去。
tun设备的发送接口,做的操作是将 数据 保存下来放到自己的list上,然后通知监听tun设备的用户程序进行读取。
所以想要TUN拦截到数据,那么需要应用程序的目的地址,能从TUN设备口“出去”,配置一条路由就是常见的操作。
注意 tun设备获取到的数据,时携带三层头的数据。
TUN 实践
上解讲了TUN设备的特性,所有的特性均是TUN的字符驱动和设备驱动实现,我们结合这个特性,就能做出一个简单的私网穿越功能,也就是开头 simpletun 的功能
请求发送
1、应用程序发送数据,例如ping到 6.6.6.2,因为本机配置的路由等规则,数据被linux内核路由到 tun90进行发送。
2、tun90 获取到数据后,不会进行真的发送操作,,而是封装成三层报文,然后放到自己队列里面,等待用户程序进行读取,这就是tun设备的特性。
此时 tun client会获取到数据。数据是包含三层头的的数据,其中源ip是 tun设备的6.6.6.1 目的地址是应用程序访问的目的地址 6.6.6.2。
3、tun client 将 这个包含三层头的数据,通过公网发送,自然 这个公网上的五元组是本机物理地址以及目的物理地址,自然这个报文能够通过公网发送。
4、tun server 收到 数据,这个数据就是 上一步2中的数据,包含了6.6.6.1->6.6.6.2这个三层头的数据,tun server写到tun设备。
5、tun90 收到用户的write操作之后,会将数据扔到linux协议栈,linux协议栈就去解析 6.6.6.1->6.6.6.2这个三层头的数据怎么转发,自然 6.6.6.2 是自己本机地址,自然能处理。
注意 1 的发送,实际是 tun设备的xmit发送,4的write实际是tun设备的receive,是不一样的。
本例只是以PING为例子。如果 物理机A 的 应用程序 访问的是 一个 公网的地址,例如100.100.100.100,那么,物理机B如果在开启了ip_forward并且自己和100.100.100.100通,那么,这个请求就被转发到了100.100.100.100,开启nat的情况下,会向公网发出 11.238.116.75 -> 100.100.100.100 的请求,响应数据 100.100.100.100->11.238.116.75 回来后依靠nat还原成100.100.100.100->6.6.6.1。
数据响应
回包的也很有意思
6、假设 机器B要回复 6.6.6.1->6.6.6.2 PING对应的 响应包,那么机器B的回复响应的IP头是 6.6.6.2->6.6.6.1,这个包会被路由机器的tun90,这个过程相当于上述发送过程的1,tun90拦截到这个包之后,期待 tun server将其读取。
7、tun server探测到tun设备有数据,于是就读取数据,数据是包含6.6.6.2->6.6.6.1这个ip头的数据。
8、tun server 使用 tun client建立的通道,将数据通过公网传输至client。
9、tun client 将数据写入到 机器A的tun。
10、根据 tun 特性,tun 机会将 包含 6.6.6.2->6.6.6.1这个ip头的数据 的数据扔会协议栈,linux 就能 这样,外部执行的ping就能获取到响应数据。