关于 vxlan 的详细介绍,可以参考 VXLAN 解读

最近在研究 docker k8s 相关的一些东西,flannel 需要了解到 vxlan 相关知识,下面通过两个简单的实验来了解一下 vxlan 在 linux 中是如何工作的。

实验环境


  • OS: Ubuntu 18.04
  • Docker 19.08
  • Kernel: 4.15

创建两台 VM,物理网卡使用 ens3 进行通信,IP 为:

  • 172.16.3.219
  • 172.16.3.203

而要创建的 overlay 网络网段为 10.100.1.0/24,实验目的就是 vxlan 能够通过 overlay IP 互相连通。
快速搭建和理解VXLAN的方法之一就是利用Linux。从内核3.7版本开始,Linux就开始支持VXLAN。到了内核3.12版本,Linux对VXLAN的支持已经完备,支持单播和组播,IPv4和IPv6。利用man查看ip的link子命令,可以查看是否有vxlan type,如下:

  1. man ip-link

可以看到,在支持的协议中,有 vxlan。

TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |
          bridge | bond | team | ipoib | ip6tnl | ipip | sit | vxlan |
          gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan |
          vti | nlmon | team_slave | bond_slave | ipvlan | geneve |
          bridge_slave | vrf | macsec }

实验


点对点的 vxlan

我们先来搭建一个最简单的 vxlan 网络,两台机器构成一个 vxlan 网络,每台机器上有一个 vtep,vtep 通过它们的 IP 互相通信。这次实验完成后的网络结构如下图所示:
image.png
先用 ip 命令创建一个 vxlan 接口 vxlan0

ip link add vxlan0 type vxlan \
    id 1 \
    dstport 4789 \
    remote 172.16.3.203 \
    local 172.16.3.219 \
    dev ens3

上面这条命令创建一个名字为 vxlan0,类型为 vxlan 的网络 interface,后面是 vxlan interface 需要的参数:

  • id 1:指定 VNI 的值,这个值可以在 1 到 2^24 之间
  • dstport:vtep 通信的端口,linux 默认使用 8472(为了保持兼容,默认值一直没有更改),而 IANA 分配的端口是 4789,所以我们这里显式指定了它的值
  • remote 172.16.3.203:对方 vtep 的地址,类似于点对点协议
  • local 172.16.3.219:当前节点 vtep 要使用的 IP 地址
  • dev ens3:当节点用于 vtep 通信的网卡设备,用来读取 IP 地址。注意这个参数和 local 参数含义是相同的,在这里写出来是为了告诉大家有两个参数存在

执行完之后,系统就会创建一个名字为 vxlan0 的 interface,可以用 ip -d link 查看它的详细信息:

root@henryxzx:~# ip -d link show dev vxlan0
11: vxlan0: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 16:bb:ae:5d:cc:08 brd ff:ff:ff:ff:ff:ff promiscuity 0 
    vxlan id 1 remote 172.16.3.203 local 172.16.3.219 dev ens3 srcport 0 0 dstport 4789 ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

接下来,为刚创建的 interface 配置 IP 地址并启用它:

ip addr add 10.100.1.2/24 dev vxlan0
ip link set vxlan0 up

执行结束之后,会发现路由表项多了下面的内容,所有 10.100.1.0/24 网段的 IP 地址要通过 vxlan0 来转发:

root@henryxzx:~# ip route get 10.100.1.0
broadcast 10.100.1.0 dev vxlan0 src 10.100.1.2 uid 0 
    cache <local,brd>

同时,vxlan0 fdb 表项中的内容如下:

root@henryxzx:~# bridge fdb | grep vxlan0
00:00:00:00:00:00 dev vxlan0 dst 172.16.3.203 via ens3 self permanent

这个表项的意思是说,默认的而 vtep 对端地址为 172.16.3.203 ,如果接收到的报文添加上 vxlan 头部之后,都会发到 172.16.3.203
在另外一台虚拟机(172.16.3.203)上也进行相同的配置,要保证 VNI 相同,dstport 也是 4789,并修改 vtep 的地址和 remote IP 地址到相应的值。测试两台 vtep 的连通性:

root@henryxzx:~# ping 10.100.1.3
PING 10.100.1.3 (10.100.1.3) 56(84) bytes of data.
64 bytes from 10.100.1.3: icmp_seq=1 ttl=64 time=0.286 ms
64 bytes from 10.100.1.3: icmp_seq=2 ttl=64 time=0.179 ms
64 bytes from 10.100.1.3: icmp_seq=3 ttl=64 time=0.180 ms
^C
--- 10.100.1.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2034ms
rtt min/avg/max/mdev = 0.179/0.215/0.286/0.050 ms

这种点对点的 vxlan 网络只能两两通信,实际用处不大。下面我们介绍多节点怎么组成 vxlan 网络进行通信。
下面是从 ens3 接口抓取到的报文:
image.png

多播模式的 vxlan

如果 vxlan 要使用多播模式,那么底层的网络结构需要支持多播的功能,在本实验中,因为只有两台 VM,又处于同一子网内,所以不需要其他配置。
要组成同一个 vxlan 网络,vtep 必须能感知到彼此的存在。多播组本来的功能就是把网络中的某些节点组成一个虚拟的组,所以 vxlan 最初想到用多播来实现是很自然的事情。
这个实验和前面一个非常相似,只不过主机之间不是点对点的连接,而是通过多播组成一个虚拟的整体。最终的网络架构也很相似(为了简单图中只有两个主机,但这个模型可以容纳多个主机组成 vxlan 网络):
image.png
在两台 VM 执行以下命令:

ip link add vxlan0 type vxlan \
    id 1 \
    dstport 4789 \
    group 239.1.1.1 \
    dev ens3
ip addr add 10.100.1.2/24 dev vxlan0
ip link set vxlan0 up

查看 fdb 表项

 root@henryxzx:~# bridge fdb | grep vxlan
00:00:00:00:00:00 dev vxlan0 dst 239.1.1.1 via ens3 self permanent

在配置完成之后,vtep 通过 IGMP 加入同一个多播网络 239.1.1.1

  1. 发送 ping 报文到 10.100.1.3,查看路由表,报文会从 vxlan0 发出去
  2. 内核发现 vxlan0 的 IP 是 10.100.1.2/24,和目的 IP 在同一个网段,所以在同一个局域网,需要知道对方的 MAC 地址,因此会发送 ARP 报文查询
  3. ARP 报文源 MAC 地址为 vxlan0 的 MAC 地址,目的 MAC 地址为全 1 的广播地址
  4. vxlan 根据配置(VNI 1)添加上头部
  5. 因为不知道对方 vtep 在哪台主机上,根据配置,vtep 会往多播地址 239.1.1.1 发送多播报文
  6. 多播组中所有的主机都会受到这个报文,内核发现是 vxlan 报文,会根据 VNI 发送给对应的 vtep
  7. vtep 去掉 vxlan 头部,取出真正的 ARP 请求报文。同时 vtep 会记录 <源 MAC 地址 - vtep 所在主机 IP 地址> 信息到 fdb 表中
  8. 如果发现 ARP 不是发送给自己的,直接丢弃;如果是发送给自己的,则生成 ARP 应答报文
  9. 应答报文目的 MAC 地址是发送方 vtep 的 MAC 地址,而且 vtep 已经通过源报文学习到了 vtep 所在的主机,因此会直接单播发送给目的 vtep。因此 vtep 不需要多播,就能填充所有的头部信息
  10. 应答报文通过 underlay 网络直接返回给发送方主机,发送方主机根据 VNI 把报文转发给 vtep,vtep 解包取出 ARP 应答报文,添加 arp 缓存到内核。并根据报文学习到目的 vtep 所在的主机地址,添加到 fdb 表中
  11. vtep 已经知道了通信需要的所有信息,后续 ICMP 的 ping 报文都是单播进行的

在这个过程中,在主机上抓包更容易看到通信的具体情况,下面是 ARP 请求报文的详情:
image.png
从上面的通信过程,可以看出不少信息:

  • 通信报文没有任何区别,除了因为 MTU 导致有些报文比较小
  • 然后是 vxlan 头部,我们最关心的字段 VNI 确实是 1
  • 最外层(图中最上面)是 vtep 所在主机的通信报文头部。可以看到这里 IP 地址为多播 239.1.1.1,目的 MAC 地址也是多播对应的地址

通信结束之后,可以在主机上看到保存的 ARP 缓存:

root@henryxzx:~# ip neigh
10.100.1.3 dev vxlan0 lladdr d2:96:a5:fb:78:47 STALE

以及 vtep 需要的 fdb 缓存:

root@henryxzx:~# bridge fdb
d2:96:a5:fb:78:47 ......
00:00:00:00:00:00 dev vxlan0 dst 239.1.1.1 via ens3 self permanent

参考

画图工具