主题
实验 8:ofproto/trace 精讲
这个命令从实验 1 起就在用了。这一章把它讲透 —— 它是 OVS 排错里性价比最高的一个工具,值得单独一章。
它到底做了什么
bash
ovs-appctl ofproto/trace br0 "in_port=1,dl_dst=..."ovs-appctl 让 ovs-vswitchd 模拟一个包走一遍完整流表,把每一步打出来。
关键词是模拟:
- 不需要真的发包
- 不影响真实流量
- 不改变任何状态(MAC 表不会因此更新)
- 网络不通、对端没上线、故障难复现,都能用
它和 tcpdump 是互补的:
ofproto/trace | tcpdump | |
|---|---|---|
| 回答 | 按现在的配置,包应该怎么走 | 包实际上有没有到这里 |
| 需要真流量 | 否 | 是 |
| 能看到丢弃原因 | 能(哪条规则丢的) | 不能(只知道没看见) |
| 能看到流表决策 | 能 | 不能 |
排错顺序:先 trace,再 tcpdump。 trace 告诉你配置对不对,tcpdump 告诉你包有没有真的来。
起环境
bash
cd labs/learn-ovs/06-pipeline
./start.sh
./solve.sh # 配好三级流水线三种写法
一、描述一个流
最常用。用实验 5 的匹配字段语法描述包:
bash
ovs-appctl ofproto/trace br0 "in_port=1,dl_dst=aa:c1:ab:65:70:e3"没写的字段一律为 0。所以上面这个包的 dl_type=0x0000 —— 它既不是 IP 也不是 ARP。如果流表按 ip 匹配,这个 trace 就不会命中你以为的规则。
字段写不全是最常见的 trace 误用
bash
# 想测 ICMP,但只写了这些 —— dl_type 是 0,不匹配 ip 规则
ovs-appctl ofproto/trace br0 "in_port=1,dl_dst=..."
# 正确:把协议栈各层都写全
ovs-appctl ofproto/trace br0 "in_port=1,dl_src=...,dl_dst=...,\
dl_type=0x0800,nw_src=192.168.10.1,nw_dst=192.168.10.2,nw_proto=1"trace 结果和实际不符时,先检查是不是字段没写全。
用简写可以省事:
bash
ovs-appctl ofproto/trace br0 "icmp,in_port=1,nw_src=192.168.10.1,nw_dst=192.168.10.2"二、喂一个真实报文
直接给十六进制:
bash
ovs-appctl ofproto/trace br0 in_port=1 \
ffffffffffffaac1ab9805720806000108000604000\
1aac1ab980572c0a80a01000000000000c0a80a02text
Flow: arp,in_port=1,vlan_tci=0x0000,dl_src=aa:c1:ab:98:05:72,
dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.10.1,
arp_tpa=192.168.10.2,arp_op=1,arp_sha=aa:c1:ab:98:05:72,
arp_tha=00:00:00:00:00:00OVS 自己把报文解析成流。这种写法不会漏字段,适合从 tcpdump 抓到的包直接拿来复现。
从 pcap 里取十六进制:
bash
tcpdump -r capture.pcap -c 1 -x三、指定从哪个端口进
bash
ovs-appctl ofproto/trace br0 in_port=1 <报文十六进制>报文里没有"从哪进来"的信息,必须单独给。
读懂输出
拿三级流水线的输出逐行拆:
text
Flow: icmp,in_port=1,vlan_tci=0x0000,dl_src=aa:c1:ab:98:05:72,
dl_dst=aa:c1:ab:65:70:e3,nw_src=192.168.10.1,nw_dst=192.168.10.2,
nw_tos=0,nw_ecn=0,nw_ttl=0,nw_frag=no,icmp_type=0,icmp_code=0
bridge("br0")
-------------
0. ip, priority 100
goto_table:1
1. dl_dst=aa:c1:ab:65:70:e3, priority 100
goto_table:2
2. dl_dst=aa:c1:ab:65:70:e3, priority 100
output:2
Final flow: unchanged
Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=aa:c1:ab:65:70:e3,nw_frag=no
Datapath actions: 3| 部分 | 含义 | 排错时看什么 |
|---|---|---|
Flow: | 补全所有字段后的包 | 确认你描述的包是不是你想的那个 |
bridge("br0") | 在哪个网桥上处理 | 穿 patch 时会出现多个网桥块 |
0. 1. 2. | 表号 | 包走到了第几级 |
ip, priority 100 | 命中的规则:匹配条件 + 优先级 | 是不是你以为的那条 |
goto_table:1 | 执行的动作 | —— |
Final flow: | 处理完后包变成什么样 | unchanged = 没改写 |
Megaflow: | 会装进内核缓存的匹配条件 | 判断缓存粒度 |
Datapath actions: | 最终结论 | drop 还是从几号 datapath 口发出 |
最该看的两行
Datapath actions: —— 一切的结论。
drop→ 包会被丢,往上找是哪条规则干的3→ 从 datapath 3 号口发出(用ovs-dpctl show查是哪个网卡)3,4→ 从两个口都发(组播/泛洪)set(...),3→ 改写了字段再发出
Megaflow: —— 内核缓存会按什么粒度缓存这类包。字段越多,缓存越细,条数越多(实验 4)。
包被改写时
bash
$N ovs-ofctl add-flow br0 \
"priority=100,ip,nw_dst=192.168.10.2,actions=mod_nw_dst:10.0.0.9,\
mod_dl_dst:02:00:00:00:00:99,output:3"
$N ovs-appctl ofproto/trace br0 "icmp,in_port=1,dl_src=aa:c1:ab:98:05:72,\
dl_dst=aa:c1:ab:65:70:e3,nw_src=192.168.10.1,nw_dst=192.168.10.2"text
0. ip,nw_dst=192.168.10.2, priority 100
mod_nw_dst:10.0.0.9
mod_dl_dst:02:00:00:00:00:99
output:3
Final flow: icmp,in_port=1,dl_src=aa:c1:ab:98:05:72,
dl_dst=02:00:00:00:00:99, ← 变了
nw_src=192.168.10.1,
nw_dst=10.0.0.9, ← 变了
...
Megaflow: recirc_id=0,eth,icmp,in_port=1,dl_dst=aa:c1:ab:65:70:e3,
nw_dst=192.168.10.2,nw_frag=no
Datapath actions: set(eth(dst=02:00:00:00:00:99)),
set(ipv4(dst=10.0.0.9)),4三处对照着看:
Final flow里dl_dst和nw_dst都变成了新值Datapath actions里出现了set(...)—— 改写动作被翻译成了 datapath 层面的 setMegaflow里nw_dst=192.168.10.2是改写前的值,因为匹配发生在改写之前
做 NAT 或负载均衡时,Final flow 是验证改写是否正确的最快方式。
穿多个网桥
实验 3 里 patch 端口的 trace:
text
bridge("br-int")
----------------
0. priority 0
NORMAL
-> forwarding to learned port
bridge("br-ex")
---------------
0. priority 0
NORMAL
-> forwarding to learned port
Datapath actions: 4两个 bridge(...) 块,说明包穿过了两个网桥。但 Datapath actions 只有一个动作 —— 两跳在慢路径里被合并了。
排查 OpenStack 这种多网桥环境时,这个输出能直接告诉你包在哪个网桥掉的。
resubmit 的嵌套
text
0. ip, priority 100
resubmit(,1)
1. dl_dst=aa:c1:ab:65:70:e3, priority 100
output:2
output:3
Datapath actions: 3,4缩进表示调用层次。resubmit 里面的处理是嵌套的,返回后继续执行 output:3。
缩进层数异常多,就该怀疑 resubmit 写出了循环。
没有规则匹配时
text
bridge("br0")
-------------
0. No match.
drop
Datapath actions: dropNo match. 说明这张表里一条规则都没匹配上,包被隐式丢弃。
这就是实验 7 强调"每张表都要有 priority=0 兜底"的原因 —— 有兜底的话,trace 会显示命中了哪条规则,比 No match. 明确得多。
排错套路
包不通时,按这个顺序走:
text
① ovs-vsctl show
端口都在吗?有没有 error: 字样?
② ovs-appctl ofproto/trace br0 "<正向的包>"
Datapath actions 是什么?
drop → 看是哪条规则/哪张表,问题在流表
正常 → 继续 ③
③ ovs-appctl ofproto/trace br0 "<反向的包>"
回程也要通。只配单向是最常见的疏漏
④ ARP 能过吗
ovs-appctl ofproto/trace br0 "arp,in_port=1,dl_dst=ff:ff:ff:ff:ff:ff"
ARP 不通,三层就永远不通
⑤ 到这一步 trace 都正常 → 配置没问题,包可能压根没进来
这时候才上 tcpdump双向都要 trace
"能 ping 通"需要来回两个方向都有规则。只 trace 正向是排错时最常见的漏项。
回程的 trace 记得把源和目的对调,in_port 也要换成对方的端口。
其它有用的选项
bash
# 指定连接跟踪的下一个状态(实验 16 会用)
ovs-appctl ofproto/trace br0 "<流>" --ct-next est
# 生成一个能被 OVS 单元测试复现的完整用例
ovs-appctl ofproto/trace br0 "<流>" --generate--generate 会让 trace 真的执行副作用(比如 learn 动作真的生成规则)。默认是不执行的,这也是 trace 安全的原因。
自测清单
- trace 和 tcpdump 分别回答什么问题?该先用哪个?
- trace 里没写的字段默认是什么值?会导致什么问题?
Final flow: unchanged说明什么?Datapath actions里的数字是 OpenFlow 端口号还是 datapath 端口号?- 输出里出现两个
bridge(...)块意味着什么? No match.和命中priority=0的兜底规则,哪个更利于排错?- 为什么 trace 默认不执行
learn动作?
常见问题
trace 结果和实际行为不一致
按可能性排序:
- 字段没写全(最常见)—— 用十六进制报文重试
- 网桥搞错了 —— 多网桥环境要 trace 入口那个网桥
- 内核缓存还是旧的 —— trace 用的是当前流表,实际流量可能还在命中旧缓存。清一下:
ovs-appctl dpctl/del-flows
报错 Bad openflow flow syntax
匹配字段写错。常见原因:
- 四层字段没写协议前缀(
tp_dst=80要写成tcp,tp_dst=80) - 用了当前 OpenFlow 版本不支持的字段
- MAC 地址格式不对
想 trace 但不知道包长什么样
先抓一个真的:
bash
docker exec clab-pipeline-h1 tcpdump -i eth1 -c 1 -x icmp把输出的十六进制拼起来喂给 trace。
小结
ofproto/trace模拟一个包走完流表,不发包、不改状态、不影响生产流量- 三种写法:描述流(注意写全字段)、喂十六进制报文(不会漏字段)、指定
in_port - 最该看的两行:
Datapath actions(最终结论)和Megaflow(缓存粒度) Final flow显示改写后的包,做 NAT 时用它验证- 多个
bridge(...)块 = 穿过多个网桥;缩进 =resubmit的调用层次 No match.表示隐式丢弃,所以每张表都该有兜底规则- 排错顺序:先 trace 配置,再 tcpdump 抓包;正反两个方向都要 trace
第 2 部分到此结束。下一部分进入二层进阶:VLAN、链路聚合、端口镜像。