IP隧道:是指可以使两个网络进行通信的通道。网络隧道最典型的应用场景,例如我们熟知的VPN。VPN的原理其实就是在两端建立加密隧道实现两端互相通信。Linux操作系统强大而又非常完善的网络功能,不仅衍生出了各种网络路由、防火墙的操作系统,还为云原生时代的跨主机网络通信赋予了灵魂,越是底层的技术,就越是难以被淘汰,即便是当下如日中天的云原生的技术本质也依旧是新瓶装旧酒的网络、存储、资源,只是换了一种玩法,粒度不同了。底层的技术虽然枯燥,但是却对了解掌握其技术原理有非常大的帮助,本文尝试用尽可能容易理解的方式,阐述Linux 网络隧道的基本知识。
一、网络基本知识
传统的网络可以类比现实生活中的快递运作机制来进行解释:
二层通信:从杭州的滨江寄送一个快递到西湖, 是同一个邮政编码(子网掩码),只需要知道获取(ARP协议)地址(mac)即可直接送达。
三层通信:从西湖(源地址)寄送一个快递去上海的松江(目标地址):快递需要先寄送到杭州集散点(第一跳网关),再送到上海集散中心(第二跳网关网关), 送往松江集散点(第三跳网关).....最终抵达目标(目标地址)
如下图所示,一个完整的路由追踪, 从发起请求的一端抵达目标,中奖要经过很多跳网关跨越多个网络:
网络报文
二、网络隧道介绍及原理
如果说传统的网络报文的流转像是寄快递, 那么网络隧道就是VIP专送,从西湖寄送一个快递去上海的松江, 拿到快递直接专车送到目的地,但是VIP专送的包裹可能涉密,于是很多场景下, 包裹加了锁, 到了目的地再解锁打开拿到包裹, 这里的专送通道就是隧道,加上锁(加密)这就是VPN的原理。本文我们先抛开加密,先理解隧道技术的原理。快递的装车、 卸货用网络隧道里面的解释,就是封包和解包的过程。
1. ip tunnel 命令
ip 命令包含在Linux系统中新一代的网络管理工具包:iproute2中,但是本文所涉及的网络隧道只需要子命令:ip tunnel即可, 输入ip tunnel help结果如下:
Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ]
[ mode { ipip | gre | sit | isatap | vti } ] [ remote ADDR ] [ local ADDR ]
[ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]
[ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]
[ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]
[ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]
Where: NAME := STRING
ADDR := { IP_ADDRESS | any }
TOS := { STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff }
TTL := { 1..255 | inherit }
KEY := { DOTTED_QUAD | NUMBER }
可以从mode中看到, Linux系统原生支持ipip、gre、sit 、isatap、vti五种隧道类型。此外还有一种:vxlan, vxlan的原理是封装以太网栈的方式实现了虚拟二层网络。
隧道类型:
ipip: ip in ip 顾名思义,就是在ip报文上再封装一层ip报文。只支持ipv4协议。
gre:通用路由封装,理论上支持所有三层协议。
sit:用于将ipv4封装成ipv6, 本文不涉及
isatap:类似sit的ipv6隧道封装, 本文不涉及
vti:思科提出的虚拟隧道接口,主要用于IPSec VPN
vxlan:虚拟二层网络,是应用最为广泛的overlay网络方案,
2. 隧道和协议封装
ipip、gre、vxlan都是通过UDP协议封装报文来实现的。以IPIP为例在ip报文上封装一层ip报文像俄罗斯套娃一样进行封装:
当报文抵达隧道的对端, 对端会进行解封装, 解封装就是去掉封装加上去的一层后把报文交给Linux网络栈。
开销:隧道的原理就是在一端封装另一端解封装,不同的隧道协议风暴的开销也不一样, 因为ipip、gre、vxlan都已经在内核中集成,所以封装和解封装都在内核态完成, 避免了用户空间到内核空间的切换,大大的提升了效率,kubernetes的 cni flannel 早期使用的隧道模式为自行实现的UDP封装, 未集成Linux内核, 封装解封运行在用户态,而数据的交互和传递则在内核态完成,这就造成了为了传递数据,需要内核态和用户态的频繁切换,导致切换成本高昂。此外不同的隧道协议封包的包头大小,也会产生额外的开销:
vxlan封装二层报文,额外开销(50 bytes), 虽然额外开销变大了,但是vxlan是功能最强大的隧道模式, VXLAN的VLAN ID有24位可以支持 2^24 = 16,777,216个VLAN(传统的VLAN ID只有12位:2^12 = 4096) 可以支持千万级别的网络租户, 非常适用于大型网络。
ipip封装三层报文,额外开销:20 bytes, 不能像vxlan一样在一个网络里面划分多个租户(VLAN),但是开销最小,小型单一网络需求非常适用。
gre则是通用封装,其理论上可以封装任何有效的三层协议类型,包括路由协议,额外开销:20 + 4 bytes到16 bytes 开销适中,常用于VPN 技术。
三、网络隧道的应用场景及实战
1. 应用场景
网络隧道传统的场景就是VPN,而在当下又称为了跨主机通信的解决方案,以在kubernetes calico ipip模式下为例, docker runtime实现了在每一个主机节点上创建虚拟网络,同一节点上的容器之间可以互相通信,此时的主机节点可以视为一个二层网络,calico利用隧道技术实现节点到节点间的网络通信。
容器网络打通:利用隧道可以很方便的联通两个网络,例如研发同学想要连上公司wifi就可以直接连接香港机房里的某个集群中的某一个pod进行debug利用隧道可以很方便的实现。
代替VPN场景,解决各种网络互通问题。
2. 实战
场景:有2台主机192.168.0.1和192.168.100.1不在同一个二层网络里面,192.168.100.1上面存在了一个容器网络。192.168.0.1想要直接访问容器地址,假设容器网络是172.16.0.0/16 如果使用隧道实现的话, 以IPIP隧道为例:
192.168.0.1主机上执行:
# 创建一个远程地址是192.168.100.1, 本端地址是192.168.0.1的ipip隧道, 虚拟设备:vpn0
ip tunnel add vpn0 mode ipip remote 192.168.100.1 local 192.168.0.1 ttl 64
# 启动设备
ip line set vpn0 up
#给设备添加一个IP
ip addr add 10.0.0.1/24 dev vpn0
# 开启路由转发
echo 1 > /proc/sys/net/ipv4/ip_forward
192.168.100.1主机上执行:
# 创建一个远程地址是192.168.0.1, 本端地址是192.168.100.1的ipip隧道, 虚拟设备:vpn0
ip tunnel add vpn0 mode ipip remote 192.168.0.1 local 192.168.100.1 ttl 64
# 启动设备
ip link set vpn0 up
#给设备添加一个IP
ip addr add 10.0.0.2/24 dev vpn0
# 开启路由转发
echo 1 > /proc/sys/net/ipv4/ip_forward
192.168.0.1主机上ping 主机192.168.100.1上的虚拟地址已经可以ping通。
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.386 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.472 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.293 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.336 ms
64 bytes from 10.0.0.2: icmp_seq=5 ttl=64 time=0.399 ms
^C
--- 10.0.0.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4002ms
rtt min/avg/max/mdev = 0.293/0.377/0.472/0.061 ms
在192.168.0.1主机上添加静态路由:
# 将容器网络指向到对端
ip route add 172.16.0.0/16 via 10.0.0.2 dev vpn0
尝试ping一个目标的容器网络地址:
ping 172.16.0.1
# 发现ping不通, 抓包可以发现ping request其实是可以抵达隧道的另一端的容器的,但是因为容器不知道下一跳网关,为了避免这个问题,需要用iptables做下伪装:
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE
再尝试ping发现已经成功:
PING 172.16.0.1 (172.16.0.1) 56(84) bytes of data.
64 bytes from 172.16.0.1: icmp_seq=1 ttl=64 time=0.297 ms
64 bytes from 172.16.0.1: icmp_seq=2 ttl=64 time=0.318 ms
64 bytes from 172.16.0.1: icmp_seq=3 ttl=64 time=0.300 ms
^C
--- 172.16.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.297/0.305/0.318/0.009 ms
其实将上文建立隧道的命令中的mode后面的ipip改成gre,就同样可以实现gre隧道。虽然具体的技术原理不一样,但是ip tunnel命令在操作上是同构的。
总结
Linux 的网络隧道提供了原生的网络管理能力, 利用隧道技术,可以方便的解决日常遇到的各种网络互通需求。虽然有专门的VPN应用, 但是隧道技术不依赖第三方应用,在很多特殊的场景中, 更为灵活。掌握技术的原理,也更容易理解kubernetes等容器编排系统的网络原理。