摘要
隧道技术在现代网络应用中变得越来越重要,通过把不同的网络连接在一起,隧道技术可以创建虚拟私有网络,访问被防火墙拦住的端口。
隧道技术可以在网络堆栈的不同层实现;SSH 隧道在 TCP 层,而 GRE 和 IPIP 这种隧道协议则直接在 IP 层上。不但如此,还有越来越多的关注集中在 UDP 协议上。Tom Herbert 发布的 foo over UDP
(FOU)补丁集,以一种通用的方式实现了 UDP 层的隧道技术,而且已经进入了 3.18 的 net-next tree 了。
为什么选择 UDP 呢?目前几乎所有的网络接口都对 UDP 有硬件支持,并实现了检验校验和等细节操作。UDP 刚好提供了足够的信息(确切的说,就是端口)来轻松地路由数据报文封包。UDP 也可以用于接收端缩放技术(Receive Side Scaling,RSS)和等价多路径协议(ECMP),来提升性能。UDP 隧道的优越之处使很多开发者认为未来几年它将大规模普及。
数据报文封包和 UDP 隧道相对来说还是比较容易理解的概念。试想一个进入隧道的 TCP 数据包:
这个数据报有正常的 IP 和 TCP 头,后面是用户要发送的数据。封包的过程如下:
这样的话,这个数据包就是一个 UDP 数据包,里面封装的是 TCP 数据包。系统可以将像普通的 UDP 数据包一样发送;在接收端,额外的 UDP 头部被去掉后,原始的 TCP 数据包继续进入网络堆栈进行处理。
这个补丁集的一些数据表明 SIT 和 IPIP 的性能有了很大提升;而 GRE 的性能则跟没有用 FOU 的情况差不多。所以,利用已有的对 UDP 收发的优化,这个特性显然能够提升速度。它不需要专门的硬件支持;当前能处理 UDP 协议的硬件就足够了。所以说这是个能工作于当前任何系统的非常简单的解决方案——而且内核 3.18 版本发布时就可以使用了。
搭建
我们准备两台主机来配置一个 FOU 隧道,分别为 k1
和 k2
,配置的 IP 地址分别为 192.168.127.151
和 192.168.127.152
,系统版本都是 CentOS 7.6.1810
环境说明
这两台机器都需要比较新的内核,因为从 3.18 版本的内核才支持 fou。
1 | [root@k1 ~]# uname -r |
版本过低就意味着内核无法提供 fou 模块,也就无法搭建隧道。
1 | [root@k1 ~]# modinfo fou |
即便是升级了内核,加载了 fou
模块,还需要对 ip_tunnel
模块有依赖,下面的输出如果没有 ip_tunnel
说明环境无法满足需要。
1 | [root@k1 ~]# modinfo --field=depends fou |
升级内核
分别为 k1
和 k2
两台主机升级内核。
导入 elrepo 源的公钥
1 | # Import the public key: |
安装 elrepo 源
1 | # To install ELRepo for RHEL-7, SL-7 or CentOS-7: |
使用 yum 安装内核相关的 rpm 包
1 | # Upgrade kernel |
确认安装成功后,查看和设置内核启动顺序,最后重新生成 grub 配置
1 | # Config for grub |
升级iproute
系统自带的 ip
命令是由 iproute
软件包提供的,要想使用 fou
子命令需要保证软件包的版本是新的。
1 | [root@k1 ~]# yum -y update iproute |
装载模块
重启后查看两台机器的环境是否正常,并装载所需要的 fou
模块
1 | [root@k1 ~]# systemctl reboot |
另一台机器的环境
1 | [root@k2 ~]# systemctl reboot |
配置 FOU 隧道
配置一条 FOU 隧道需要两步。发送端 k1
和接收端 k2
分别设置,这样能允许人们随意配置它们。
在接收端 k2 上只要设置一个端口用于接收封装好的数据报文就行了。有个新的 fou
子命令可用于此:
1 | [root@k2 ~]# ip fou add port 5555 ipproto 4 |
此命令在接收端 k2
设置了 5555 端口,指明此端口接收到的数据报的协议号是 4,也就是 IP 封包。此端口接收到的数据包会被拆解掉外面的封装;然后再放入到网络堆栈中,从而发往它的实际目的地址。想要删除这个端口则使用 ip fou del port 5555 ipproto 4
。
在发送端 k1
就稍复杂了点,因为必须指定目的地址,还要能够与已有的封装协议共存。典型的命令如下:
1 | [root@k1 ~]# ip link add ftok2 type ipip remote 192.168.127.152 local 192.168.127.151 ttl 255 dev eth0 encap fou encap-sport auto encap-dport 5555 |
此命令在发送端 k1
设置了一个新的虚拟接口(ftok2
),用于 IPIP 封装。数据包的源端口由网络栈决定,而目的端口设置为 5555 。当然要使用这个网络接口还要选择封装协议,目前支持的协议有 IPIP,SIT(IPv4-IPv6隧道协议)和 GRE(用于虚拟专用网)。
配置完成后即可使用 ip tunnel
命令查看隧道信息,其中 tunl0
是 fou
模块装载后系统自动生成的。
1 | [root@k1 ~]# ip tunnel show |
通信是双向的,因此还需要按照上述的步骤反过来。
k1
作为接收端配置一次。
1 | [root@k1 ~]# ip fou add port 5555 ipproto 4 |
k2
作为发送端配置一次。请注意命令中的名称以及 remote
和 local
对应的地址。
1 | [root@k2 ~]# ip link add ftok1 type ipip remote 192.168.127.151 local 192.168.127.152 ttl 255 dev eth0 encap fou encap-sport auto encap-dport 5555 |
配置接口状态
隧道配置完成后虚拟的接口是处于 DOWN
状态的,可以通过 ip link show
命令查看
1 | [root@k1 ~]# ip link show ftok2 |
可以看到 state DOWN
,说明接口没有启用
1 | [root@k2 ~]# ip link show ftok1 |
通过 ip link set
命令将接口启用
1 | [root@k1 ~]# ip link set ftok2 up |
另外一台
1 | [root@k2 ~]# ip link set ftok1 up |
现在,隧道就成功地建立起来了。
要想这两台主机成功路由(转发)数据包,还需要开启转发功能。在 k1
和 k2
上分别编辑 /etc/sysctl.conf
并加入如下配置:
1 | # Controls IP packet forwarding |
然后执行 sysctl -p
,转发功能即可立即生效。
配置路由的下一跳IP
由于隧道属于虚拟接口,因此对于路由的下一跳(下一个越点)的 IP 地址只能配置在本机的虚拟接口上。此处我们称下一跳 IP 为 GWIP(网关IP)。
现在我们在 k1
上配置 GWIP 为 10.10.10.1
1 | [root@k1 ~]# ip addr add 10.10.10.1 dev ftok2 |
在 k2
上配置 GWIP 为 10.10.10.2
1 | [root@k2 ~]# ip addr add 10.10.10.2 dev ftok1 |
配置路由
要想实现在隧道中进行通信,就需要在两端配置静态路由。使用 ip route
命令可以实现在 Linux 主机添加静态路由。命令格式如下:
1 | ip route add to 目标网络 via 下一跳IP dev 从本机出去的网卡名称 |
现在有了 GWIP 就可以通过隧道路由了,我们将这两个 GWIP 做通试试看。
在 k1
上配置到 k2
的路由
1 | [root@k1 ~]# ip route add to 10.10.10.2 via 10.10.10.1 dev ftok2 |
在 k2
上配置到 k1
的路由
1 | [root@k2 ~]# ip route add to 10.10.10.1 via 10.10.10.2 dev ftok1 |
隧道测试
k1
作为发送端
1 | [root@k1 ~]# ping -I 10.10.10.1 10.10.10.2 -c 3 |
k2
作为发送端
1 | [root@k2 ~]# ping -I 10.10.10.2 10.10.10.1 -c 3 |
通过抓包可以看到数据包的请求和响应过程中,接收端的端口都是 5555
1 | [root@k2 ~]# tcpdump -nn -i eth0 udp port 5555 |
做了 GWIP 的静态路由之后,两个机器的 GWIP 能互通说明隧道建立成功了,此时你就可以根据实际的情况和需求,添加公网静态路由到隧道了。
参考链接: