引子
2017年末,在SUSE实习的时候man ip link
,看到了这种奇奇怪怪的东西:
1 | TYPE := [ bridge | bond | can | dummy | hsr | ifb | ipoib | macvlan | macvtap | vcan | veth | vlan | vxlan | |
除了bridge
,别的都没有见过,于是学习了下与之有关的知识。对图解几个与Linux网络虚拟化相关的虚拟网卡-VETH/MACVLAN/MACVTAP/IPVLAN中的以下观点感到不错:
一块网卡就是一道门,一个接口,它上面一般接协议栈,下面一般接介质。最关键的是,你要明确它们确实在上面和下面接的是什么。
TUN/TAP:用户可以用文件句柄操作的字符设备
前者就是网络中的封装(encapsulation)。而后者,维基百科介绍说:TUN/TAP是一种虚拟的网络设备。TAP等同于一个以太网设备提供了一种机制,它操作第二层网络数据包如以太网数据帧;TUN模拟了网络层设备,操作第三层数据包比如IP数据包。操作系统通过TUN/TAP设备向绑定该设备的用户空间程序发送数据,反之,用户空间的程序也可以像操作硬件网络设备那样,通过TUN/TAP设备发送数据。
要知道,从用户空间流经内核的网络数据流并不会传递给用户空间的程序,而是直接交给NIC传输。TUN/TAP设备的特性可以将网络数据传递给用户空间程序,使用户空间的程序处理网络数据流成为可能。
那么,能不能实现一种用户空间的程序,不向内核添加一行代码,便可以篡改、监视本机的所有网络流量呢?这与Stack Overflow中的Can I make a “TCP packet modifier” using tun/tap and raw sockets?不谋而合。
想法描述
这中用户空间的程序起名为IP modifer,假如发送个ICMP Request,数据包有着如下的流程:
- 通过配置路由,使ICMP Request经过TUN设备送出。
- 数据包流经内核协议栈,将封装有ICMP Request的IP数据包传递给IP packet modifer程序。
- 数据包经IP packet modifer处理后通过eth0设备送出。
- 数据包再一次流经内核协议栈,最后交给NIC送出本机。
技术难点
想法虽好,但是:
- 如何使数据包流经TUN设备?
- IP packet modifer处理后怎样通过eth0设备送出?
这种需求很容易想到各类VPN软件的实现,为此,我专门在我的腾讯云上部署了ocserv,并调研了一番源码。
ocserv中的实现
客户端与服务端建立连接后,ip a
显示了如下的信息:
1 | // Client |
ip r
信息如下:
1 | // Client |
同时,Server端通过tcpdump
可以看到:
1 | root@VM-44-93-ubuntu:~# tcpdump -i any port 8000 -vvv |
根据上面列出的信息,我猜想了这样几件事情:
- 客户端建立连接后,会修改默认路由为
172.16.31.1
,它定义在ocserv的配置文件中,为服务端IP地址119.29.x.25
设置静态路由,使数据不经过TUN设备。 - 客户端将流经TUN设备的数据发往服务端,端口为8000,它同样定义在ocserv的配置文件中。
- 服务端接收数据后,会向vpns0设备写入由客户端TUN设备捕获的数据。
- iptables nat表中POSTROUTING链上的SNAT规则使客户端可以与106.39.167.118通信。
在ocserv的源码中,又找到了下列信息:
1 | // src/tun.h |
确认猜想的第三点使正确的。并且从Stack Overflow上的What exactly happens to packets written to a TUN/TAP device?回答得知,向TUN设备注入数据包,kernel并不会知道这个数据包到底来自真实的NIC物理设备还是TUN设备。
我的实现
并没有采取直接修改main
路由表的方式来使所有网络流量流经TUN设备,而是使用了策略路由:
- 非来自TUN设备的IP数据包查询路由表100
- 路由表100的优先级比254高
创建TUN设备并设置策略路由的相关命令如下:
1 | # ip tuntap add mode tun tun0 |
这样,IP packet modifer可以截获所有的网络流量,参考ocserv的实现可以知道,只需要把数据写回TUN设备便可,那么就要求:
- 来自TUN设备的IP数据包查询路由表254(main)
仅需下面的这个命令:
1 | # ip rule add from all iif tun0 lookup main |
但,默认情况下,任何从非loopback网卡进来的任何数据包的源地址不能是本机地址。若接收源地址为本机地址的数据包需要NIC启用accept_local
,与此有关的patch在这,为kernel v2.6.33中的新增特性。因此,还需要执行以下命令:
1 | # sysctl -w net.ipv4.conf.tun0.accept_local=1 |
实现
据此,我实现了三个简单的示例程序:
通过策略路由,它会截获所有自主机发出的网络流量,并把他们打印出来。
与simple-tun-read-write.py类似,但它无需手动配置。
使用rust实现的IP packet modifer,它可以伪造ICMP echo。
相关的源码都可以在这里找到。
参考资料
图解几个与Linux网络虚拟化相关的虚拟网卡-VETH/MACVLAN/MACVTAP/IPVLAN
TUN與TAP
Can I make a “TCP packet modifier” using tun/tap and raw sockets?
What exactly happens to packets written to a TUN/TAP device?
关于Linux内核引入的accept_local参数的一个问题
How to configure linux routing/filtering to send packets out one interface, over a bridge and into another interface on the same box
ipv4 05/05: add sysctl to accept packets with local source addresses