主题
实验 14:打通 VXLAN
上一章讲了原理,这一章动手建一条隧道,并且抓包证明封装确实发生了。
起环境
bash
cd labs/learn-ovs/11-vxlan
./start.sh1
2
2
text
overlay:h1 和 h2 要能二层互通
┌──────────────────────────────────────────────┐
│ h1 h2 │
│ 192.168.10.1/24 192.168.10.2/24 │
└──┬───────────────────────────────────────┬───┘
│ │
┌──┴───┐ ┌────┴─┐
│ ovs1 │ eth2 eth1 │ ovs2 │
│ br0 ├──────────────────────────────┤ br0 │
└──────┘ 10.0.0.1/30 10.0.0.2/30 └──────┘
└──── underlay:三层可达 ────┘1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
start.sh 做的事:
- 给 h1、h2 配 overlay 地址(同一网段)
- 给 ovs1 的 eth2、ovs2 的 eth1 配 underlay 地址,这两个接口不进网桥
- 两边各建一个
br0,只接本地主机
启动时它会自己验一遍:
text
== 确认 underlay 通、overlay 不通 ==
underlay 10.0.0.1 → 10.0.0.2 通
overlay h1 → h2 不通(符合预期,还没建隧道)1
2
3
2
3
underlay 地址为什么不进网桥
underlay 是承载网,它要用 Linux 的 IP 路由把隧道包送到对端。接进 OVS 网桥就变成二层端口了,协议栈拿不到它,路由也就无从谈起。
记住这个分工:underlay 归 Linux 协议栈,overlay 归 OVS。
建隧道
两端各一条命令:
bash
# ovs1 上
docker exec clab-vxlan-ovs1 ovs-vsctl add-port br0 vxlan0 \
-- set Interface vxlan0 type=vxlan \
options:remote_ip=10.0.0.2 options:key=100
# ovs2 上
docker exec clab-vxlan-ovs2 ovs-vsctl add-port br0 vxlan0 \
-- set Interface vxlan0 type=vxlan \
options:remote_ip=10.0.0.1 options:key=1001
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
注意 remote_ip 填的是对端的 underlay 地址,两边正好相反。key=100 是 VNI,两端必须一致。
bash
docker exec clab-vxlan-ovs1 ovs-vsctl show1
text
Bridge br0
Port br0
Interface br0
type: internal
Port eth1
Interface eth1
Port vxlan0
Interface vxlan0
type: vxlan
options: {key="100", remote_ip="10.0.0.2"}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
通了:
bash
docker exec clab-vxlan-h1 ping -c 3 192.168.10.21
text
3 packets transmitted, 3 received, 0% packet loss1
h1 和 h2 之间隔着一个三层网络,但它们现在是二层互通的。
抓包看封装
这是本章最值得亲手做的一步。在 underlay 接口上抓包:
bash
docker exec -d clab-vxlan-ovs2 \
sh -c 'timeout 8 tcpdump -i eth1 -n -l -e udp port 4789 > /tmp/cap.txt 2>&1'
sleep 1
docker exec clab-vxlan-h1 ping -c 2 -i 0.3 192.168.10.2
sleep 8
docker exec clab-vxlan-ovs2 cat /tmp/cap.txt1
2
3
4
5
6
2
3
4
5
6
text
aa:c1:ab:43:70:60 > aa:c1:ab:56:75:af, ethertype IPv4, length 148:
10.0.0.1.48601 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 100
aa:c1:ab:8e:a5:78 > aa:c1:ab:a6:45:5a, ethertype IPv4, length 98:
192.168.10.1 > 192.168.10.2: ICMP echo request, id 85, seq 1
aa:c1:ab:56:75:af > aa:c1:ab:43:70:60, ethertype IPv4, length 148:
10.0.0.2.48601 > 10.0.0.1.4789: VXLAN, flags [I] (0x08), vni 100
aa:c1:ab:a6:45:5a > aa:c1:ab:8e:a5:78, ethertype IPv4, length 98:
192.168.10.2 > 192.168.10.1: ICMP echo reply, id 85, seq 11
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
两层结构清清楚楚:
text
外层 10.0.0.1 → 10.0.0.2 UDP 目的端口 4789 ← underlay 地址
VXLAN 头,vni 100 ← 租户标识
内层 192.168.10.1 → 192.168.10.2 ICMP ← overlay 地址,原封不动1
2
3
2
3
再看长度:内层 98 字节,外层 148 字节,正好差 50 —— 和上一章算的封装开销对上了。
注意外层 UDP 的源端口
第一个包源端口 48601,另一个流是 32922。目的端口固定 4789,源端口是内层报文的哈希 —— 这是给 underlay 路由器做 ECMP 用的。
从 OVS 内部看
MAC 表:对端主机从隧道口学到
bash
docker exec clab-vxlan-ovs1 ovs-appctl fdb/show br01
text
port VLAN MAC Age
1 0 aa:c1:ab:8e:a5:78 3 ← h1,从 eth1 学到
2 0 aa:c1:ab:a6:45:5a 3 ← h2,从 vxlan0 学到1
2
3
2
3
h2 的 MAC 从 2 号口(vxlan0) 学到。对 NORMAL 转发逻辑来说,隧道端口和物理端口完全一样 —— 它不知道也不关心那个口背后是一条隧道。
datapath:所有 VXLAN 共用一个设备
bash
docker exec clab-vxlan-ovs1 ovs-dpctl show1
text
port 0: ovs-system (internal)
port 1: br0 (internal)
port 2: eth1
port 3: vxlan_sys_4789 (vxlan: packet_type=ptap)1
2
3
4
2
3
4
注意 datapath 里的名字是 vxlan_sys_4789,不是 vxlan0。
这台机器上所有 VXLAN 隧道共用这一个 datapath 设备,不管你建了多少个隧道端口。区分不同隧道靠的是包上携带的隧道元数据(对端 IP + VNI),不是靠不同的设备。
和 ovs-system 是全机共用的 datapath 是同一个设计思路。
它在 ip link 里是能看到的:
bash
docker exec clab-vxlan-ovs1 ip -br link | grep vxlan1
text
vxlan_sys_4789 UNKNOWN 9e:53:98:6a:de:f2 <BROADCAST,MULTICAST,UP,LOWER_UP>1
(对比 patch 端口:那个在 ip link 里根本不存在。)
trace:封装是一个 datapath 动作
bash
docker exec clab-vxlan-ovs1 ovs-appctl ofproto/trace br0 \
"in_port=1,dl_src=<h1MAC>,dl_dst=<h2MAC>"1
2
2
text
bridge("br0")
-------------
0. priority 0
NORMAL
-> forwarding to learned port
-> output to kernel tunnel
Final flow: unchanged
Datapath actions: set(tunnel(tun_id=0x64,dst=10.0.0.2,ttl=64,
tp_dst=4789,flags(df|key))),31
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
两个关键点:
-> output to kernel tunnel —— NORMAL 决定往 2 号口发,而这个口是隧道。
set(tunnel(tun_id=0x64,dst=10.0.0.2,...)) —— 封装被翻译成了一个 datapath 动作:
tun_id=0x64= 十进制 100,就是 VNIdst=10.0.0.2对端 underlay 地址tp_dst=4789目的 UDP 端口flags(df|key)设了 DF 位(这就是 MTU 问题的来源)
封装不是"另一个模块"做的,它就是流表动作的一部分。
换成 Geneve
改一个字段的事:
bash
docker exec clab-vxlan-ovs1 ovs-vsctl set Interface vxlan0 type=geneve
docker exec clab-vxlan-ovs2 ovs-vsctl set Interface vxlan0 type=geneve1
2
2
立刻就通:
text
2 packets transmitted, 2 received, 0% packet loss1
datapath 里换了个设备:
text
port 4: genev_sys_6081 (geneve: packet_type=ptap)1
端口从 4789 变成 6081,其余一切照旧。隧道类型对上层完全透明。
改回去:
bash
docker exec clab-vxlan-ovs1 ovs-vsctl set Interface vxlan0 type=vxlan
docker exec clab-vxlan-ovs2 ovs-vsctl set Interface vxlan0 type=vxlan1
2
2
自动检查
bash
./verify.sh1
text
[通过] ovs1 上 vxlan0 类型正确
[通过] ovs1 的隧道对端是 10.0.0.2
[通过] 两端 VNI 都是 100
[通过] underlay 仍然通
[通过] overlay:h1 能 ping 通 h2
[通过] underlay 上抓到了 VXLAN 封装包(UDP 4789)1
2
3
4
5
6
2
3
4
5
6
自测清单
- underlay 地址为什么不能接进 OVS 网桥?
remote_ip填的是对端的哪个地址?- 抓到的包,外层和内层分别是什么地址?长度差多少?
- 外层 UDP 源端口为什么每条流都不一样?
- MAC 表里 h2 的 MAC 从几号口学到?
NORMAL知道那是隧道吗? ovs-dpctl show里为什么是vxlan_sys_4789而不是vxlan0?- trace 输出里
tun_id=0x64是什么?
常见问题
隧道建好了但不通
按这个顺序查:
bash
# 1. underlay 通吗(最常见的原因)
docker exec clab-vxlan-ovs1 ping -c 2 10.0.0.2
# 2. 两端 VNI 一致吗
docker exec clab-vxlan-ovs1 ovs-vsctl get Interface vxlan0 options:key
docker exec clab-vxlan-ovs2 ovs-vsctl get Interface vxlan0 options:key
# 3. remote_ip 填对了吗(两边应该互指)
docker exec clab-vxlan-ovs1 ovs-vsctl get Interface vxlan0 options:remote_ip
# 4. underlay 上有封装包吗
docker exec clab-vxlan-ovs2 timeout 5 tcpdump -i eth1 -n -l udp port 47891
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
第 4 步能抓到包但业务不通 → 多半是 VNI 不匹配,或者对端网桥没接对。
ping 通但传文件卡死
MTU 问题,上一章讲过。验证一下:
bash
# 1450 字节的载荷能过
docker exec clab-vxlan-h1 ping -c 2 -M do -s 1400 192.168.10.2
# 1500 字节的过不去
docker exec clab-vxlan-h1 ping -c 2 -M do -s 1472 192.168.10.21
2
3
4
2
3
4
第二条失败就确诊了。改小 overlay MTU:
bash
docker exec clab-vxlan-h1 ip link set eth1 mtu 1450
docker exec clab-vxlan-h2 ip link set eth1 mtu 14501
2
2
隧道端口显示 error
bash
ovs-vsctl show | grep -A3 vxlan01
常见原因:remote_ip 写成了域名(不支持)、options 拼写错误、或者本机没有到 remote_ip 的路由。
抓不到 4789 的包
确认抓的是 underlay 接口(eth2 / eth1),不是 br0 或 vxlan_sys_4789。在 vxlan_sys_4789 上抓到的是解封装后的内层帧,看不到外层。
小结
- 隧道端口就是
type=vxlan的 Interface,remote_ip填对端 underlay 地址,key是 VNI - underlay 地址配在接口上、不进网桥;underlay 归 Linux 路由,overlay 归 OVS
- 抓包能直接看到两层结构,长度差 50 字节
- 外层 UDP 目的端口固定 4789,源端口是内层哈希(为了 ECMP)
- 对
NORMAL来说隧道端口和物理端口没有区别,MAC 学习照常 - datapath 里所有 VXLAN 共用
vxlan_sys_4789一个设备 - 封装是一个 datapath 动作:
set(tunnel(tun_id=...,dst=...)) - 换隧道类型只需改
type字段,对上层完全透明 - 排错顺序:underlay → VNI → remote_ip → 抓包
下一章:实验 15:多租户隔离,用不同的 VNI 在同一条物理链路上跑多个互相隔离的虚拟网络。