基于TUN/TAP实现多个局域网设备之间的通讯
目标
本文会对tun/tap
的原理进行基本说明,然后基于tun/tap
实现多个局域网设备之间的通讯,实现跨网络通信。
例如:实现局域网A中的主机和局域网B中的主机进行通信。
原理
简要说明物理网卡的工作流程
在了解tun/tap
之前,先简要说明物理网卡的工作流程。
网卡处于网络协议栈和物理网卡之间,一端连着网络协议栈,一端连着物理网络
- 当发送数据时:将接收网络协议栈的数据,并将数据封装成帧,并通过网线(对无线网络来说就是电磁波)将数据发送到网络中。
- 当接收数据时:接收网络上其他设备传过来的帧,并将帧进行解码,然后传递到网络协议栈中。
TUN、TAP基本原理
目前主流的虚拟网卡方案有tun/tap和veth两种,在时间上tun/tap
出现得更早,它是一组通用的虚拟驱动程序包,里面包含了两个设备,分别是用于网络数据包处理的虚拟网卡驱动,以及用于内核空间与用户空间交互的字符设备(Character Devices
,这里具体指/dev/net/tun
)驱动。大概在 2000 年左右,Solaris
系统为了实现隧道协议(Tunneling Protocol
)开发了这套驱动,从 Linux Kernel 2.1
版开始移植到Linux
内核中,当时是源码中的可选模块,2.4 版之后发布的内核都会默认编译 tun/tap
的驱动。
tun
和 tap
是两个相对独立的虚拟网络设备,其中 tap
模拟了以太网设备,具备操作二层数据包(以太帧)的能力,tun
则模拟了网络层设备,具备操作三层数据包(IP
报文)的能力。使用 tun/tap
设备的目的是实现把来自协议栈的数据包先交由某个打开了/dev/net/tun
字符设备的用户进程处理后,再把数据包重新发回到链路中,你可以通俗地将它理解为这块虚拟化网卡一端连接着网络协议栈,另一端连接着用户态程序,只要协议栈中的数据包能被用户态程序截获并加工处理,程序员就有足够的舞台空间去玩出各种花样,譬如数据压缩、流量加密、透明代理等功能都能够以此为基础来实现。
就以我们此次要实现的应用程序为例,当程序发送给tun设备数据包时,整个工作流程如图所示:
实现思路
要实现局域网A中的主机和局域网B中的主机进行通信,我们需要有三端,分别为客户端A、客户端B、服务端C,其中服务端C部署在公有云上,它和客户端A、和客户端B的网络是通的。
分别启动服务端C客户端A、B,并且服务端C分别和客户端A、B建立TCP
链接。
客户端A创建tun
网卡,再对这个网卡配置IP
以及子网掩码并且激活tun
网卡,那么Linux
会自动加上这个网段路由规则。
客户端B创建tun
网卡,再对这个网卡配置IP
以及子网掩码并且激活tun
网卡,那么Linux
会自动加上这个网段路由规则。
客户端A、B设置IP
,需要在同一个网段,并且不与已有的网络冲突,例如:10.10.10.1/24、10.10.10.2/24
。
客户端A向客户端B的IP
(10.10.10.2
)发起请求,通过路由规则的计算,将会路由到tun
网卡,然后通过用户程序对tun
网卡进行监听,将tun
网卡的数据通过客户端A与服务端C建立的TCP
连接发送出去(这里走的就是物理网卡)。
当C端接收到请求数据之后,向所有和它建立TCP
链接的客户端发起广播请求(除了A端这个最初的发送端除外),如果是目的地IP
,将会响应给服务端,否则将会丢弃,当服务端收到响应之后,再同理的将响应数据广播给最初的发送端。
在服务端和客户端的TCP
交互过程中,我们要注意处理TCP
的粘包问题。
我们前期设置tun
网卡的IP
和激活tun
网卡,可以先手动操作,需要注意的一点就是,tun
网卡它是通过用户程序进行创建的,将用户程序结束之后,tun
网卡将会自动删除,所以我们需要每次维护它的IP
并且手动激活它。
至此,这个程序的大致实现思路就结束了。
这里就没代码的具体实现了,主要讲讲思路,如果对代码有兴趣的话。easy-tun,注释比较细,应该很容易能够看懂。
测试
这里的测试主要是对easy-tun的代码进行测试。
前提
GO环境
启动服务端
# 设置go拉取依赖的地址
root@vultr:~/goWorkspace# go env -w GOPROXY=https://goproxy.cn,direct
# 拉取依赖
root@vultr:~/goWorkspace# go get
# 启动服务端
root@vultr:~/goWorkspace# go run Server.go
启动客户端并且设置IP
- 启动客户端A
# 设置go拉取依赖的地址
root@vultr:~/goWorkspace# go env -w GOPROXY=https://goproxy.cn,direct
# 拉取依赖
root@vultr:~/goWorkspace# go get
# 通过-ser指定服务端的IP地址,这里填你服务端的IP
[root@localhost goworkspace]# go run Client.go -ser xxx.xxx.xxx.xxx
server address :xxx.xxx.xxx.xxx
local tun device name :gtun
connect server succeed.
# 对指定网卡设置IP,gtun为你网卡的名称,如果你的tun网卡名称不是gtun,自行调整
[root@localhost goworkspace]# sudo ip addr add 10.10.10.1/24 dev gtun
# 将gtun网卡激活,如果你的tun网卡名称不是gtun,自行调整
[root@localhost goworkspace]# sudo ip link set gtun up
- 启动客户端B
# 设置go拉取依赖的地址
root@vultr:~/goWorkspace# go env -w GOPROXY=https://goproxy.cn,direct
# 拉取依赖
root@vultr:~/goWorkspace# go get
# 通过-ser指定服务端的IP地址,这里填你服务端的IP
[root@localhost goworkspace]# go run Client.go -ser xxx.xxx.xxx.xxx
server address :xxx.xxx.xxx.xxx
local tun device name :gtun
connect server succeed.
# 对指定网卡设置IP,gtun为你网卡的名称,如果你的tun网卡名称不是gtun,自行调整
[root@localhost goworkspace]# sudo ip addr add 10.10.10.2/24 dev gtun
# 将gtun网卡激活,如果你的tun网卡名称不是gtun,自行调整
[root@localhost goworkspace]# sudo ip link set gtun up
在客户端Aping
客户端B
[root@localhost goworkspace]# ping 10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=355 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=64 time=671 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=64 time=363 ms
64 bytes from 10.10.10.2: icmp_seq=4 ttl=64 time=356 ms
64 bytes from 10.10.10.2: icmp_seq=5 ttl=64 time=1473 ms
64 bytes from 10.10.10.2: icmp_seq=6 ttl=64 time=592 ms
至此,我们实现了不在同一个局域网的两台机器,像访问局域网一样进行通讯。
参考
- <<凤凰架构>>