Skip to content

Feature 顺序:把散落的坑串起来

这本书里有好几个问题,表面看毫无关联:

text
  第 4 章  det44 关掉了,nat44 却全部失效
  第 6 章  cnat 去程正常,回程不翻译
  第 8 章  ACL 规则写得没错,流量却被拦
  第 8 章  ABF 策略挂上了,下一跳没变

它们其实是同一件事的四种表现:feature 在 arc 上的位置和顺序。

这一篇把这套机制彻底讲清楚。

什么是 feature arc

VPP 的转发是一张图(第 0 章)。但插件不能随便往图里塞节点 —— 那样几十个插件互相之间的顺序就没法管理了。

VPP 的办法是预留若干(arc),每个弧是一段有序的插槽:

text
  ip4-unicast 弧(包从接口进来后经过)
  ┌────────────────────────────────────────┐
  │ 槽位 1  ip4-sv-reassembly-feature       │
  │ 槽位 2  policer-input                   │
  │ 槽位 3  acl-plugin-in-ip4-fa            │
  │ 槽位 4  nat-pre-in2out / nat-pre-out2in │
  │ 槽位 5  ip4-inacl                       │
  │ 槽位 6  ip4-qos-record                  │
  └────────────────────────────────────────┘

          ip4-lookup(查路由)

          ip4-rewrite(改链路层头)

  ┌────────────────────────────────────────┐
  │ ip4-output 弧                           │
  │ 槽位 1  acl-plugin-out-ip4-fa           │
  └────────────────────────────────────────┘

          host-ethX-output(发出去)

插件在编译期声明自己要挂在哪个弧、排在谁前面谁后面。 顺序是固定的,不能通过配置改变。

实测真实顺序

自己看一遍

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

这个实验把 ACL、NAT、Policer 同时挂到接口上,然后打印各个弧的内容。

bash
vppctl show interface features host-eth1

内网口入向:

text
  1. ip4-sv-reassembly-feature
  2. policer-input
  3. nat-pre-in2out

外网口入向:

text
  1. ip4-sv-reassembly-feature
  2. acl-plugin-in-ip4-fa
  3. nat-pre-out2in

外网口出向:

text
  1. acl-plugin-out-ip4-fa

show interface features 的输出是 CRLF 行尾

想写脚本解析它的话,先 tr -d '\r'。不然按行精确匹配永远不成立 —— ip4-unicast: 实际上是 ip4-unicast:\r

这个细节坑了我一次:awk 的 $0 == "ip4-unicast:" 死活匹配不上, cat -A 一看才发现每行都带 ^M

一张表回答"规则该写什么地址"

把顺序翻译成实用结论:

text
  ┌──────────────────┬──────────────────┬────────────────────────────┐
  │ Feature          │ 相对 NAT 的位置    │ 规则里该写什么地址            │
  ├──────────────────┼──────────────────┼────────────────────────────┤
  │ policer-input    │ 之前              │ 内网原始地址;限内网侧速率     │
  │ acl-plugin-in    │ 之前              │ 包进来时线上的地址            │
  │ acl-plugin-out   │ **之后**          │ **NAT 转换后**的地址         │
  │ abf-input        │ **之后**          │ **NAT 转换后**的地址         │
  └──────────────────┴──────────────────┴────────────────────────────┘

这三条都在实验里验证过:

text
  内网口 input  ACL 写 10.10.0.11    ✓ 有效     (第 15 步)
  外网口 output ACL 写 10.10.0.11    ✗ 无效     (第 13 步)
  外网口 output ACL 写 203.0.113.1   ✓ 有效     (第 13 步)
  ABF 的 ACL 写 src 10.10.0.12       ✗ 无效     (第 14 步,带 NAT 时)

一个好记的判据

入向 feature 看到的是"线上的样子",出向 feature 看到的是"改完的样子"。

因为出向弧在 ip4-lookupip4-rewrite 之后 —— 那时候 NAT 早就把地址改完了。

排查方法

现象是"规则看起来完全正确但不生效"时,按这个顺序查:

第 4 步的 trace 最有说服力 —— 它会打印每个节点看到的实际报文内容:

text
00:26:22:597xxx: nat44-ed-in2out-slowpath
  rewrite: saddr 203.0.113.1 ...          ← NAT 在这里改了源地址
00:26:22:597xxx: abf-input-ip4
  ipv4 via 203.0.113.11 host-eth2         ← ABF 看到的已经是改完的包

另一类问题:feature 摘不干净

顺序之外还有一个维度:feature 有没有被正确摘除。

第 4 章的 det44 是最恶劣的例子:

bash
vppctl det44 plugin disable          # 插件关了
vppctl show det44 interfaces         # 接口列表空了
vppctl show interface features host-eth1 | grep det44
text
  det44-in2out
  det44-in2out                        ← 但节点还在,而且累积了多个

残留的节点会继续抢在 nat44 前面拦包,导致"配置一切正常但流量全不通"。 而且没有在线清理的办法,只能重建 VPP。

对比之下,其他插件的行为:

text
  ┌──────────────┬────────────────────────────────────────┐
  │ 插件          │ disable 后 feature 摘干净了吗            │
  ├──────────────┼────────────────────────────────────────┤
  │ nat44-ed/ei  │ ✓ 干净                                  │
  │ nat64/nat66  │ ✓ 干净(nat66 关掉后 nat64 立刻恢复)     │
  │ acl-plugin   │ ✓ 干净(逐接口摘)                       │
  │ policer      │ ✓ 干净,但**摘未挂载的会崩 VPP**          │
  │ det44        │ ✗ **摘不掉,必须重建 VPP**               │
  └──────────────┴────────────────────────────────────────┘

遇到"配置正常但流量不通",先看 feature arc

bash
vppctl show interface features <> | head -20

看到不该在的节点,就是这个问题。这个检查应该排在 show errors(计数器)之后、trace 之前 —— 因为这类故障 计数器是干净的(包被一个你以为已经关掉的节点吃了)。

插件之间的互斥

同一个弧上的同一个位置,两个插件抢起来会怎样?实测三组:

text
  ┌────────────────────┬──────────────────────────────────────────┐
  │ 组合                │ 结果                                      │
  ├────────────────────┼──────────────────────────────────────────┤
  │ nat44-ed + nat44-ei│ 抢同一批接口,会打架                        │
  │ nat64 + nat66      │ **nat66 盖住 nat64**;                     │
  │                    │ 好在 disable 能干净恢复,不用重建            │
  │ det44 + 任何 nat44 │ det44 残留让 nat44 失效,**必须重建**       │
  ├────────────────────┼──────────────────────────────────────────┤
  │ ACL + NAT + Policer│ ✓ 可以共存,各在各的槽位                    │
  └────────────────────┴──────────────────────────────────────────┘

最后一行是这一步实验验证的:三者同时挂上,流量正常, 各自的功能都生效 —— 因为它们本来就在弧上的不同槽位,不冲突。

会冲突的是同类插件(都想做 NAT 的那几个)。

设计建议

text
  ✓ 该这么做
  ────────────────────────────────────────────────────
  配完任何 feature 都 show interface features 确认一遍
  ACL 规则按挂载方向决定写转换前还是转换后的地址
  清理脚本先查状态再删,不要盲目 del / unapply
  同类插件(NAT 家族)一次只启用一个

  ✗ 别这么做
  ────────────────────────────────────────────────────
  假设 plugin disable 就等于恢复原状(det44 不是)
  在生产设备上"试试另一个 NAT 插件"(切换需重启 VPP)
  写幂等清理脚本时盲删(pnat / policer 会崩)

小结

机制插件挂在预留的 feature arc 上,顺序编译期固定,配置改不了
查看show interface features <接口>(注意 CRLF 行尾)
入向顺序reassembly → policer → ACL → NAT
出向顺序ip4-output 弧上的 ACL,在 NAT 之后
核心结论入向看"线上的样子",出向看"改完的样子"
摘不干净的det44,必须重建 VPP
排查位置排在计数器之后、trace 之前 —— 这类故障计数器是干净的

第 8、9 章到此结束

两章加起来的全貌:

text
  第 8 章 ACL     有序规则 + 隐式 deny · permit+reflect 有状态 · ABF 策略转发
  第 9 章 QoS     DSCP 标记 · Policer 三色限速 · feature 顺序

这两章和前七章的关系,可以用一句话概括:

前七章讲包被改成什么样,这两章讲哪些包能过、以什么优先级过, 而 feature 顺序决定了两者如何叠加。

三个最值得带走的结论:

一、隐式 deny 和静默 fail-open 是一对。 ACL 挂上就是默认拒绝, 但挂一个不存在的索引却是默认放行 —— 前者符合直觉,后者反直觉且危险。 配完一定要 show 一眼。

二、出向规则要写转换后的地址。 这一条能解释 ACL、ABF 相关的大部分 "规则没错但不生效"。判据是看 deny 计数在 -in- 还是 -out- 节点上涨。

三、"删除不存在的东西"是 VPP 26.06 的一类系统性风险。 pnat 删不存在的规则、policer 摘未挂载的实例,都会段错误崩溃。 写自动化脚本时,先查状态再操作不是洁癖,是必须。

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