基于UDP协议搭建隧道

摘要

隧道技术在现代网络应用中变得越来越重要,通过把不同的网络连接在一起,隧道技术可以创建虚拟私有网络,访问被防火墙拦住的端口。

隧道技术可以在网络堆栈的不同层实现;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 隧道,分别为 k1k2 ,配置的 IP 地址分别为 192.168.127.151192.168.127.152,系统版本都是 CentOS 7.6.1810

环境说明

这两台机器都需要比较新的内核,因为从 3.18 版本的内核才支持 fou。

1
2
[root@k1 ~]# uname -r
3.10.0-957.el7.x86_64

版本过低就意味着内核无法提供 fou 模块,也就无法搭建隧道。

1
2
3
[root@k1 ~]# modinfo fou
modinfo: ERROR: Module fou not found.
[root@k1 ~]#

即便是升级了内核,加载了 fou 模块,还需要对 ip_tunnel 模块有依赖,下面的输出如果没有 ip_tunnel 说明环境无法满足需要。

1
2
[root@k1 ~]# modinfo --field=depends fou
ip6_udp_tunnel,udp_tunnel,ip_tunnel

升级内核

分别为 k1k2 两台主机升级内核。

导入 elrepo 源的公钥

1
2
# Import the public key:
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

安装 elrepo 源

1
2
# To install ELRepo for RHEL-7, SL-7 or CentOS-7:
yum install http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

使用 yum 安装内核相关的 rpm 包

1
2
3
# Upgrade kernel
yum --enablerepo=elrepo-kernel install kernel-ml kernel-ml-devel
rpm -qa kernel-ml

确认安装成功后,查看和设置内核启动顺序,最后重新生成 grub 配置

1
2
3
4
# Config for grub
awk -F'[\047]+' '/^menuentry/{print $2}' /boot/grub2/grub.cfg
grub2-set-default 0
grub2-mkconfig -o /boot/grub2/grub.cfg

升级iproute

系统自带的 ip 命令是由 iproute 软件包提供的,要想使用 fou 子命令需要保证软件包的版本是新的。

1
2
3
[root@k1 ~]# yum -y update iproute
[root@k1 ~]# rpm -qf /sbin/ip
iproute-4.11.0-14.el7.x86_64

装载模块

重启后查看两台机器的环境是否正常,并装载所需要的 fou 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k1 ~]# systemctl reboot
[root@k1 ~]# uname -r
5.1.12-1.el7.elrepo.x86_64
[root@k1 ~]# modinfo --field=depends fou
ip6_udp_tunnel,udp_tunnel,ip_tunnel
[root@k1 ~]# modprobe fou
[root@k1 ~]# modprobe ipip
[root@k1 ~]# lsmod | egrep 'ipip|fou'
ipip 16384 0
tunnel4 16384 1 ipip
fou 28672 0
ip_tunnel 24576 2 fou,ipip
ip6_udp_tunnel 16384 1 fou
udp_tunnel 16384 1 fou

另一台机器的环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k2 ~]# systemctl reboot
[root@k2 ~]# uname -r
5.1.12-1.el7.elrepo.x86_64
[root@k2 ~]# modinfo --field=depends fou
ip6_udp_tunnel,udp_tunnel,ip_tunnel
[root@k2 ~]# modprobe fou
[root@k2 ~]# modprobe ipip
[root@k2 ~]# lsmod | egrep 'ipip|fou'
ipip 16384 0
tunnel4 16384 1 ipip
fou 28672 0
ip_tunnel 24576 2 fou,ipip
ip6_udp_tunnel 16384 1 fou
udp_tunnel 16384 1 fou
[root@k2 ~]#

配置 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
2
[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
[root@k1 ~]#

此命令在发送端 k1 设置了一个新的虚拟接口(ftok2),用于 IPIP 封装。数据包的源端口由网络栈决定,而目的端口设置为 5555 。当然要使用这个网络接口还要选择封装协议,目前支持的协议有 IPIP,SIT(IPv4-IPv6隧道协议)和 GRE(用于虚拟专用网)。

配置完成后即可使用 ip tunnel 命令查看隧道信息,其中 tunl0fou 模块装载后系统自动生成的。

1
2
3
4
[root@k1 ~]# ip tunnel show
ftok2: ip/ip remote 192.168.127.152 local 192.168.127.151 dev eth0 ttl 255
tunl0: any/ip remote any local any ttl inherit nopmtudisc
[root@k1 ~]#

通信是双向的,因此还需要按照上述的步骤反过来。

k1 作为接收端配置一次。

1
[root@k1 ~]# ip fou add port 5555 ipproto 4

k2 作为发送端配置一次。请注意命令中的名称以及 remotelocal 对应的地址。

1
2
3
4
5
[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   
[root@k2 ~]# ip tunnel show
ftok1: ip/ip remote 192.168.127.151 local 192.168.127.152 dev eth0 ttl 255
tunl0: any/ip remote any local any ttl inherit nopmtudisc
[root@k2 ~]#

配置接口状态

隧道配置完成后虚拟的接口是处于 DOWN 状态的,可以通过 ip link show 命令查看

1
2
3
4
[root@k1 ~]# ip link show ftok2
4: ftok2@eth0: <POINTOPOINT,NOARP> mtu 1472 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 192.168.127.151 peer 192.168.127.152
[root@k1 ~]#

可以看到 state DOWN,说明接口没有启用

1
2
3
4
[root@k2 ~]# ip link show ftok1
4: ftok1@eth0: <POINTOPOINT,NOARP> mtu 1472 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 192.168.127.152 peer 192.168.127.151
[root@k2 ~]#

通过 ip link set 命令将接口启用

1
2
3
4
5
[root@k1 ~]# ip link set ftok2 up
[root@k1 ~]# ip link show ftok2
4: ftok2@eth0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ipip 192.168.127.151 peer 192.168.127.152
[root@k1 ~]#

另外一台

1
2
3
4
5
[root@k2 ~]# ip link set ftok1 up
[root@k2 ~]# ip link show ftok1
4: ftok1@eth0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ipip 192.168.127.152 peer 192.168.127.151
[root@k2 ~]#

现在,隧道就成功地建立起来了。

要想这两台主机成功路由(转发)数据包,还需要开启转发功能。在 k1k2 上分别编辑 /etc/sysctl.conf 并加入如下配置:

1
2
# Controls IP packet forwarding
net.ipv4.ip_forward = 1

然后执行 sysctl -p ,转发功能即可立即生效。

配置路由的下一跳IP

由于隧道属于虚拟接口,因此对于路由的下一跳(下一个越点)的 IP 地址只能配置在本机的虚拟接口上。此处我们称下一跳 IP 为 GWIP(网关IP)。

现在我们在 k1 上配置 GWIP 为 10.10.10.1

1
2
3
4
5
6
7
[root@k1 ~]# ip addr add 10.10.10.1 dev ftok2
[root@k1 ~]# ip addr show dev ftok2
4: ftok2@eth0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 192.168.127.151 peer 192.168.127.152
inet 10.10.10.1/32 scope global ftok2
valid_lft forever preferred_lft forever
[root@k1 ~]#

k2 上配置 GWIP 为 10.10.10.2

1
2
3
4
5
6
7
[root@k2 ~]# ip addr add 10.10.10.2 dev ftok1
[root@k2 ~]# ip addr show dev ftok1
4: ftok1@eth0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 192.168.127.152 peer 192.168.127.151
inet 10.10.10.2/32 scope global ftok1
valid_lft forever preferred_lft forever
[root@k2 ~]#

配置路由

要想实现在隧道中进行通信,就需要在两端配置静态路由。使用 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
2
3
4
5
6
7
8
9
10
[root@k1 ~]# ping -I 10.10.10.1 10.10.10.2 -c 3
PING 10.10.10.2 (10.10.10.2) from 10.10.10.1 : 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=0.371 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=64 time=0.767 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=64 time=0.762 ms

--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2087ms
rtt min/avg/max/mdev = 0.371/0.633/0.767/0.186 ms
[root@k1 ~]#

k2 作为发送端

1
2
3
4
5
6
7
8
9
10
[root@k2 ~]# ping -I 10.10.10.2 10.10.10.1 -c 3
PING 10.10.10.1 (10.10.10.1) from 10.10.10.2 : 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=0.325 ms
64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=0.929 ms
64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=0.494 ms

--- 10.10.10.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2010ms
rtt min/avg/max/mdev = 0.325/0.582/0.929/0.255 ms
[root@k2 ~]#

通过抓包可以看到数据包的请求和响应过程中,接收端的端口都是 5555

1
2
3
4
5
6
7
8
[root@k2 ~]# tcpdump -nn -i eth0 udp port 5555
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:37:55.453105 IP 192.168.127.152.44213 > 192.168.127.151.5555: UDP, length 84
21:37:55.453703 IP 192.168.127.151.53964 > 192.168.127.152.5555: UDP, length 84
21:37:56.476594 IP 192.168.127.152.44213 > 192.168.127.151.5555: UDP, length 84
21:37:56.477170 IP 192.168.127.151.53964 > 192.168.127.152.5555: UDP, length 84
21:37:57.504192 IP 192.168.127.152.44213 > 192.168.127.151.5555: UDP, length 84

做了 GWIP 的静态路由之后,两个机器的 GWIP 能互通说明隧道建立成功了,此时你就可以根据实际的情况和需求,添加公网静态路由到隧道了。

参考链接:

有钱任性,请我吃包辣条
0%