Linux 常见隧道协议整理及用法

起因

最近由于拿到了一些不错的资源,所以准备用隧道通过这些资源把几个地区的机器都连在一起。之前的方式是通过 VXLAN 组一个 二层网络 来实现较为方便的互相访问,但是现在由于走了别的出口,没有办法在进行之前那样 点对点 的连接了,所以我们需要通过隧道来实现。但是在使用这些 隧道协议 的过程中,发现了一些之前没有遇到过的问题,所以打算好好整理一下,防止以后再遇到的时候还需要翻来翻去的找资料。

IP in IP (aka IPIP)

IP in IP 是一种把 IP数据包 封装进另一个 IP数据包 的隧道协议,外层 IP数据包 头部包含了隧道入口点 IP 以及隧道出口点 IP,而内部的 IP数据包 除了TTL的自然递减之外,完全不会改变。本协议的定义在 RFC2003

用法

1
2
3
4
ip tunnel ${interface name} mode ipip \
local ${local endpoint address} \
remote ${remote endpoint address} \
ttl ${time to live}

参数列表:

  • interface name: 指定一个可用的接口名称
  • local endpoint address: 隧道本地入口的地址
  • remote endpoint address: 隧道远端出口的地址
  • time to live: 指定 IP数据包的最大 TTL

示例:

1
2
3
ip tunnel add tun0 mode ipip local 100.64.0.1 remote 192.168.0.1 ttl 255
ip link set dev tun0 up
ip addr add 10.0.0.1/24 dev tun0

Simple Internet Transition (aka SIT)

SIT 一般用于将 IPv6数据包 封装在 IPv4数据包内,这样可以为没有 IPv6 接入的机器提供 Tunnel Broker服务。本协议的定义在 RFC1933

用法:

1
2
3
ip tunnel add ${interface name} mode sit \
local ${local endpoint address} \
remote ${remote endpoint address}

参数列表:

  • interface name: 指定一个可用的接口名称
  • local endpoint address: 隧道本地入口的地址
  • remote endpoint address: 隧道远端出口的地址

示例:

1
2
3
ip tunnel add tun0 local 100.64.0.1 remote 192.168.0.1
ip link set dev tun0 up
ip addr add 2001:db8:1::1/64 dev tun0

IPv4 in IPv6 (aka IPIP6)

IPIP6 和 SIT 是相反的,SIT 是用 IPv4数据包 封装 IPv6数据包,而 IPIP6 是用 IPv6数据包 封装 IPv4数据包。

用法:

1
2
3
ip -6 tunnel add ${interface name} mode ipip6 \
local ${local endpoint address} \
remote ${remote endpoint address}

参数列表:

  • interface name: 指定一个可用的接口名称
  • local endpoint address: 隧道本地入口的地址
  • remote endpoint address: 隧道远端出口的地址

示例:

1
2
3
ip -6 tunnel add tun8 mode ipip6 local 2001:db8:1::1 remote 2001:db8:1::2
ip link set dev tun8 up
ip addr add 10.0.0.1/24 dev tun 8

Generic Routing Encapsulation (aka GRE)

GRE 可能是本文整理的重点了,原因其实很简单:GRE真的非常好用。GRE规定了如何用一种网络协议去封装另一种网络协议的方法。GRE的隧道由两端的源IP地址和目的IP地址来定义,允许用户使用IP包封装IP、IPX、AppleTalk包,并支持全部的路由协议(如RIP2、OSPF等)。通过GRE,用户可以利用公共IP网络连接IPX网络、AppleTalk网络,还可以使用保留地址进行网络互连,或者对公网隐藏企业网的IP地址。本协议的定义在 RFC2784RFC1701

点对点

用法:

1
2
3
4
ip tunnel add ${interface name} mode gre \
local ${local endpoint address} \
remote ${remote endpoint address} \
key ${key value}

参数列表:

  • interface name: 指定一个可用的接口名称
  • local endpoint address: 隧道本地入口的地址
  • remote endpoint address: 隧道远端出口的地址
  • key value: 一个密钥,当两端密钥相同时隧道才能连通

示例:

1
2
3
ip tunnel add tun4 mode gre local 192.0.2.1 remote 203.0.113.6 key 123
ip link set dev tun4 up
ip addr add 10.0.0.1/24 dev tun4

## 一对多

用法:

1
2
ip tunnel add ${interface name} mode gre 
local ${local endpoint address} key ${key value}

参数列表:

  • interface name: 指定一个可用的接口名称
  • local endpoint address: 隧道本地入口的地址
  • key value: 一个密钥,当两端密钥相同时隧道才能连通

示例:

1
2
3
ip tunnel add tun8 mode gre local 192.0.2.1 key 1234
ip link set dev tun8 up
ip address add 10.0.0.1/27 dev tun8

注意!

这样配置的隧道缺少了远端出口的地址,所以 key 是鉴别隧道流量的唯一方法,因此 key 是必须的。
没有显性的指明远端地址,也就意味着我们需要手动添加 邻居

本地:

1
ip neigh add 10.0.0.2 lladdr 203.0.113.6 dev tun8

远端:

1
ip neighbor add 10.0.0.1 lladdr 192.0.2.1 dev tun8

既然已经说了 GRE 是本文的重点,那就肯定要说一下:GRE 可以在 IPv4 和 IPv6 两种协议上运作,而不像其他的隧道协议,只能在一种 IP协议 上运作。

更新: GRE 的 一对多 模式我并没有亲自验证过,有读者反映通过以上命令不能实现,但是近期没有时间再次去验证,所以暂时删除以上的内容。

喂…喂…喂…

当我在两台机器上配置完成隧道之后,我以为万事大吉后面什么都不用管了,但是我错了,我忘记了现在的云计算厂商都适用了 SDN,也就意味着,流量在到达你的机器之前,可能经过了数次 NAT,那么一旦 NAT Session 失效,你的隧道也就不可能再通了。后来我查阅资料发现,像 Cisco 或者 Mikrotik 的路由器,对于隧道协议都有一个 Keepalive 选项,其目的就是为了让隧道能够一直保持连通。但是,Linux 上并没有这个功能,那么就只能发挥自己的聪明智慧了 —— ping!

由于我使用的是 Ubuntu 18.04.1,本身自带了 systemd,所以我就写了一个 service 来专门启动 ping 实现 Keepalivenetwork-init.service 是用来在启动时建立隧道的 service,只有这个 service 成功启动了,Keepalive 才会启动,否则没有意义。

1
2
3
4
5
6
7
8
9
10
11
12
13
# [email protected]

[Unit]
Description = Initializor of Private Network
Wants=network-init.service
After=network-init.service

[Service]
ExecStart=/bin/ping -i 10 -s 0 10.0.0.%i
User=nobody

[Install]
WantedBy=default.target

但是这并不优雅

既然会因为 NAT Session 过期而导致隧道不通,那么,除了 Keepalive,还有什么方法可以实现呢?
我在上一篇文章里介绍了 在 Linux 上配置 VXLAN,而 VXLAN 是通过 UDP 数据包来承载 IP数据包的,所以可以用 点对点 的VXLAN隧道来实现和别的隧道协议一样的效果。