Skip to content

读懂 packet trace

前面六章反复用到 show trace,但一直没系统讲过怎么读它。 这一篇把 VPP 的排错工具箱一次性讲清楚 —— 它是 VPP 相比内核转发最大的优势之一。

为什么它这么有用

内核里做 NAT,你能拿到的观测手段大概是 conntrack -Ltcpdump。 包在协议栈里经历了什么,基本靠猜。

VPP 的转发逻辑是一张显式的图(回顾第 0 章), show trace 会把每个包经过的每个节点、以及节点内部发生了什么逐行打出来。

text
  → af-packet-input                收包
  → ethernet-input                 解以太网头
  → ip4-input                      解 IP 头
  → nat-pre-in2out                 判定要做 NAT
  → nat44-ed-in2out                查会话表
  → nat44-ed-in2out-slowpath       没查到,新建会话并完成转换
  → ip4-lookup                     用改过的地址重新查路由
  → ip4-rewrite                    改写链路层头
  → host-eth2-output               发出去

这是一份逐跳的"包的自述"。

基本用法:三条命令

bash
# 1. 清掉旧记录
vppctl clear trace

# 2. 布下抓取(从哪个输入节点开始,抓多少个包)
vppctl trace add af-packet-input 20

# 3. 制造流量后查看
vppctl show trace

输入节点要选对

trace add 的第一个参数是输入节点,不是任意节点。用错了什么都抓不到。

收包方式输入节点
AF_PACKET(本教程)af-packet-input
DPDKdpdk-input
AF_XDPaf-xdp-input
memifmemif-input
虚拟网卡virtio-input / vmxnet3-input

不确定用哪个?show interface rx-placement 会告诉你:

text
Thread 0 (vpp_main):
 node af-packet-input:
    host-eth1 queue 0 (polling)

抓包数量要给够

trace add af-packet-input 20 里的 20 是总数,不是每个接口 20 个。 而且 ARP/ND、回包都算数 —— 一次 ping 三个包,加上 ARP 和回包, 实际可能要十几个槽位。给少了会看不到关键的那一个。

读一条完整的 trace

下面是从实验里抓的一个首包,逐段拆解:

text
00:08:10:962150: ethernet-input
  IP4: aa:c1:ab:c3:66:ff -> aa:c1:ab:27:cc:ec

时间戳 + 节点名,然后是这个节点看到的内容。这里是链路层地址。

text
00:08:10:962158: ip4-input
  UDP: 10.10.0.11 -> 203.0.113.12
    tos 0x00, ttl 64, length 31, checksum 0xe87e
  UDP: 52515 -> 9001
    length 11, checksum 0x5700

原始的五元组和校验和。 这是 NAT 改写之前的样子。

text
00:08:10:962168: nat-pre-in2out
  in2out next_index 2 arc_next_index 10

分流节点:判定这个包要走 in2out 分支。看到 nat-pre-out2in 就说明走的是回程分支。

text
00:08:10:962170: nat44-ed-in2out
  NAT44_IN2OUT_ED_FAST_PATH: sw_if_index 1, next index 3
  search key local 10.10.0.11:52515 remote 203.0.113.12:9001 proto UDP fib 0

search key 这一行价值极高 —— 它显示 VPP 用什么去查会话表。 排查"为什么会话匹配不上"时,把这个 key 和 show nat44 sessions 里的会话逐字段比对, 差异一目了然。

text
00:08:10:962174: nat44-ed-in2out-slowpath
  NAT44_IN2OUT_ED_SLOW_PATH: translation result 'success' via i2of
  i2of match:   saddr 10.10.0.11  sport 52515 daddr 203.0.113.12 dport 9001
       rewrite: saddr 203.0.113.1 sport 52515 daddr 203.0.113.12 dport 9001
  o2if match:   saddr 203.0.113.12 sport 9001 daddr 203.0.113.1 dport 52515
       rewrite: saddr 203.0.113.12            daddr 10.10.0.11   dport 52515

出现 slowpath 说明这是首包,正在新建会话。 i2of / o2if 是刚建好的来回两个方向的改写规则 —— 和 会话表里看到的完全一致。

text
00:08:10:962189: ip4-lookup
  UDP: 203.0.113.1 -> 203.0.113.12
    checksum 0xb691
  UDP: 52515 -> 9001
    checksum 0x2513

改写后的样子。 和前面 ip4-input 那段并排看,就知道 NAT 动了哪几个字段。

只看节点序列

包的内容有时候不重要,你只想知道"它走了哪条路"。过滤一下:

bash
vppctl show trace | grep -E '^Packet |^[0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{6}: '
text
Packet 1
00:00:01:123456: af-packet-input
00:00:01:123460: ethernet-input
00:00:01:123465: ip4-input
00:00:01:123470: nat-pre-in2out
00:00:01:123472: nat44-ed-in2out
00:00:01:123475: nat44-ed-in2out-slowpath
00:00:01:123489: ip4-lookup
00:00:01:123491: ip4-rewrite
00:00:01:123494: host-eth2-output

这个视图特别适合回答两类问题:

包走到哪一步就没了? 序列在哪个节点戛然而止,问题就在那儿。

快路径还是慢路径? 有没有 -slowpath 那一跳。 第 1 章用这个方法对比过 ping -c 3 的三个包 —— 首包有 slowpath,后续包没有。

trace filter:流量大时只看关心的

生产环境上 trace add 一开,几十个包瞬间抓满,全是无关流量。 trace filter 可以只保留经过特定节点的包:

bash
vppctl trace filter include nat44-ed-in2out 10
vppctl trace add af-packet-input 100
# ... 制造流量 ...
vppctl show trace
vppctl trace filter none        # 用完记得关掉

include <节点> <数量> 表示只保留经过该节点的包。也可以用 exclude 反过来排除。

这在排查"某类流量不通"时很有用

比如只想看走 out2in 分支的包:

bash
vppctl trace filter include nat44-ed-out2in 20

这样 ARP、内网互访之类的噪声就被滤掉了。

pcap trace:把包存成文件

show trace 是文本,看不了字节级细节,也没法给别人。 pcap trace 直接存成标准 pcap 文件:

bash
vppctl pcap trace rx tx max 1000 intfc host-eth2 file nat.pcap
# ... 制造流量 ...
vppctl pcap trace off

文件落在容器里的 /tmp/nat.pcap,拷出来用 Wireshark 打开。

最有价值的用法:抓被丢弃的包

bash
vppctl pcap trace drop max 100 file drops.pcap

只抓被丢弃的包。 这在排查"某些包莫名其妙消失"时几乎是唯一的手段 —— show trace 要你提前知道该抓什么,pcap trace drop 则是守株待兔。

实测效果:

text
Write 2 packets to /tmp/drops.pcap, and stop capture...
bash
ssh chenzhong@192.168.49.182 \
  'docker exec clab-natbase-nat ls -la /tmp/drops.pcap'
text
-rw-r--r-- 1 root root 332 Sep  4 03:21 /tmp/drops.pcap

pcap dispatch trace:两者结合

bash
vppctl pcap dispatch trace on max 1000 file dispatch.pcap buffer-trace af-packet-input 100

这会把包内容 + 它经过的节点序列一起存进 pcap。 用 Wireshark 打开能看到每个包的 VPP 图路径 —— 相当于 show trace 的可视化版本。 排查复杂问题时值得一试。

show runtime:节点级的性能视图

trace 看单个包,show runtime 看整体:

bash
vppctl clear runtime      # 先清零
# ... 跑一段流量 ...
vppctl show runtime
text
  vector rates in 1.0608e5, out 1.0608e5, drop 0.0000e0, punt 0.0000e0
             Name                 State      Calls      Vectors   Packet-Clocks  Vectors/Call
af-packet-input                  polling      455100      38595         3.25e3          .08
ip4-input                        active        36699     254342         3.62e2         6.93
nat44-ed-in2out                  active        36531     200006         6.15e2         5.47
nat44-ed-in2out-slowpath         active            1          1         3.21e4         1.00

四个关键列:

含义怎么用
Calls这个节点被调用了多少次polling 模式下输入节点会有天文数字,是空转
Vectors处理了多少个包和 Calls 一起决定批处理效率
Packet-Clocks每个包消耗的 CPU 时钟找性能瓶颈就看这一列
Vectors/Call平均每次调用处理几个包VPP 性能的核心指标,见性能那一篇

上面这组数据里藏着一个重要事实:

text
  nat44-ed-in2out           615 clocks/包     ← 快路径
  nat44-ed-in2out-slowpath  32100 clocks/包   ← 慢路径,贵 52 倍

标准诊断流程

把这些工具串起来,形成一套固定动作:

第 2 步(看计数器)最关键,因为大部分 NAT 故障都有明确的计数器签名。 下一篇专门讲这些签名。

动手练一遍

bash
./labctl reset base
./labctl apply base 11
./labctl verify base 11

这个实验会故意制造四种典型故障,每种都完整走一遍诊断流程。

小结

工具用途
trace add <输入节点> <数量>抓逐跳路径,排查"包走到哪没了"
trace filter include/exclude流量大时只保留关心的包
pcap trace rx tx intfc X file Y存成 pcap,用 Wireshark 看字节
pcap trace drop只抓被丢弃的包,守株待兔
pcap dispatch trace包内容 + 节点路径一起存
show runtime节点级性能视图,找瓶颈看 Packet-Clocks
show errors错误计数器,诊断第一站

trace 里最该盯的三行:search key(查表用的键)、 i2of/o2if(改写规则)、以及 节点序列(走没走 slowpath、在哪断的)。

下一篇:典型故障速查

实验环境基于 netlab + containerlab + VPP 26.06