Skip to content

实验 8:ofproto/trace 精讲 ​

这个命令从实验 1 起就在用了。这一章把它讲透 —— 它是 OVS 排错里性价比最高的一个工具,值得单独一章。

它到底做了什么 ​

bash
ovs-appctl ofproto/trace br0 "in_port=1,dl_dst=..."

ovs-appctl 让 ovs-vswitchd 模拟一个包走一遍完整流表,把每一步打出来。

关键词是模拟:

  • 不需要真的发包
  • 不影响真实流量
  • 不改变任何状态(MAC 表不会因此更新)
  • 网络不通、对端没上线、故障难复现,都能用

它和 tcpdump 是互补的:

ofproto/tracetcpdump
回答按现在的配置,包应该怎么走包实际上有没有到这里
需要真流量否是
能看到丢弃原因能(哪条规则丢的)不能(只知道没看见)
能看到流表决策能不能

排错顺序:先 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\
  1aac1ab980572c0a80a01000000000000c0a80a02
text
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:00

OVS 自己把报文解析成流。这种写法不会漏字段,适合从 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 层面的 set
  • Megaflow 里 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: drop

No 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 安全的原因。

自测清单 ​

  1. trace 和 tcpdump 分别回答什么问题?该先用哪个?
  2. trace 里没写的字段默认是什么值?会导致什么问题?
  3. Final flow: unchanged 说明什么?
  4. Datapath actions 里的数字是 OpenFlow 端口号还是 datapath 端口号?
  5. 输出里出现两个 bridge(...) 块意味着什么?
  6. No match. 和命中 priority=0 的兜底规则,哪个更利于排错?
  7. 为什么 trace 默认不执行 learn 动作?

常见问题 ​

trace 结果和实际行为不一致 ​

按可能性排序:

  1. 字段没写全(最常见)—— 用十六进制报文重试
  2. 网桥搞错了 —— 多网桥环境要 trace 入口那个网桥
  3. 内核缓存还是旧的 —— 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、链路聚合、端口镜像。