主题
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-fashow 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-lookup 和 ip4-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 det44text
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 摘未挂载的实例,都会段错误崩溃。 写自动化脚本时,先查状态再操作不是洁癖,是必须。