
1.6 初识Linux隧道:ipip
前文介绍的tun设备也叫作点对点设备,之所以叫这个名字,是因为tun经常被用来做隧道通信(tunnel)。
我们可以通过命令ip tunnel help查看IP隧道的相关操作。Linux原生支持下列5种L3隧道:
·ipip:即IPv4 in IPv4,在IPv4报文的基础上封装一个IPv4报文;
·GRE:即通用路由封装(Generic Routing Encapsulation),定义了在任意一种网络层协议上封装其他任意一种网络层协议的机制,适用于IPv4和IPv6;
·sit:和ipip类似,不同的是sit用IPv4报文封装IPv6报文,即IPv6 over IPv4;
·ISATAP:即站内自动隧道寻址协议(Intra-Site Automatic Tunnel Addressing Protocol),与sit类似,也用于IPv6的隧道封装;
·VTI:即虚拟隧道接口(Virtual Tunnel Interface),是思科提出的一种IPSec隧道技术。下面我们以ipip为例,介绍Linux隧道通信的基本原理。
注:Linux L3隧道底层实现原理都基于tun设备,因此我们可以将本节看作tun设备的高级应用篇。
1.6.1 测试ipip隧道
要使用ipip隧道,首先需要内核模块ipip.ko的支持。
通过lsmod|grep ipip查看内核是否加载,若没有则用modprobe ipip加载,正常加载应该显示:

加载ipip内核模块后,就可以创建隧道了。方法是先创建一个tun设备,然后将该tun设备绑定为一个ipip隧道。ipip隧道网络拓扑如图1-18所示。

图1-18 ipip隧道网络拓扑
创建两个network namespace:

创建两对veth pair,令其一端挂在某个namespace下:

分别给两对veth-pair端点配上IP并启用:

验证一下:v1 ping v2,结果为不通。
检查ip_forward的值:

我们已经知道Linux本身就是一台路由器,Linux提供开关/proc/sys/net/ipv4/ip_forward来操作路由功能,默认这个开关是关的,打开只需:

这种打开方式只是临时的,若想持久,可以修改配置文件/etc/sysctl.conf,添加或修改项net.ipv4.ip_forward为:

在我们的例子中,即使打开了ip_forward选项,v1到v2依然不通。
查看ns1的路由表:

只有一条直连路由,没有通往10.10.20.0/24网段的路由,因此手动配置一条路由,如下所示:

再查看路由表:

同理,也给ns2配上通往10.10.10.0/24网段的路由。
再ping一次,发现通了!

保证v1和v2能够通信后,再创建tun设备,并设置为ipip隧道。
在ns1上创建tun1和ipip tunnel:

上面的命令是在ns1上创建tun设备tun1,并设置隧道模式为ipip,然后设置隧道端点,用remote和local表示,这是隧道外层IP。对应的还有隧道内层IP,用ip addr xx peer xx配置。从tun1发到tun2的原始IP报文和经过隧道封装后的IP报文如图1-19所示。

图1-19 IP报文封装示意图
同理,我们也在ns2上创建tun2和ipip tunnel。

完成上述配置,两个tun设备端点就可以互通了,代码如下:

1.6.2 ipip隧道测试结果复盘
我们试着分析上面的实验过程:
(1)ping命令构建一个ICMP请求,ICMP报文封装在IP报文中,源和目的IP地址分别是10.10.100.10和10.10.200.10。
(2)由于tun1和tun2不在同一网段,所以要查看路由表。通过ip tunnel命令建立ipip隧道后,会自动生成一条路由,如下所示:

以上路由表信息表明:去往目的地10.10.200.10的报文直接从tun1出去了。
(3)由于配置了隧道端点,数据包出了tun1,直接到达v1。根据ipip隧道的配置,会封装上一层新的IP头,源和目的IP地址分别为10.10.10.2和10.10.20.2。
(4)由于v1和v2同样不在一个网段,查看路由表,发现去往10.10.20.0网段的报文从v1网卡出,去往10.10.10.1网关,即veth pair在主机上的另一端v1_p。
(5)Linux打开了ip_forward,它相当于一台路由器,10.10.10.0和10.10.20.0是两条直连路由,所以直接查路由表转发,从这台主机转到另一台主机的v2_p上。
(6)根据veth pair的设备特性,数据包到达另一台主机的v2_p上,会直接从ns2的v2出来。内核解封装数据包,发现内层IP报文的目的IP地址是10.10.200.10,这正是自己配置的ipip隧道的tun2地址,于是将报文交给tun2设备。至此,tun1的ping请求包成功到达tun2。
(7)由于ICMP报文的传输特性,有去必有回,所以ns2上会构造ICMP响应报文,并根据以上相同步骤封装和解封装数据包,直至到达tun1,整个ping过程完成。
以上便是ipip隧道的通信过程,感兴趣的读者可以自行抓包验证。读者会发现有两层IP报文头,外层使用ipip协议构成隧道的端点,内层是正常的通信报文,封装了ICMP报文作为payload。
1.6.3 小结
现在的Linux内核原生支持5种隧道协议,它们的底层实现都采用tun设备。我们熟知的各种VPN软件,其底层实现都离不开这5种隧道协议。其他隧道实现方式与ipip隧道的大同小异。