Skip to content

有状态 ACL 与执行顺序

这一篇解决一个经典需求,顺带揭开本书里反复出现的一类问题的根源。

需求:内网能出去,外网不能进来

无状态 ACL 做不到这件事。

回包的目的端口是 41247 —— 一个客户端随机挑的临时端口。 你没法预先知道它,也不可能为每个可能的端口写规则。

无状态方案只能退而求其次:放行所有"高位端口"(1024-65535), 或者匹配 TCP 的 ACK 标志位。前者等于门户大开,后者对 UDP 无效。

permit+reflect

VPP 的解法是放行的同时记下这条流,回程自动放行:

bash
set acl-plugin acl permit+reflect src 203.0.113.1/32

应用配置

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

配置分两条:

bash
# 外网口 output:内网出去的包在这里建会话
vppctl set acl-plugin interface host-eth2 output acl <reflect>

# 外网口 input:外网主动进来的包在这里被拦
vppctl set acl-plugin interface host-eth2 input acl <deny>

结果:

text
── 1) 内网主动出去 —— reflect 应放行回包 ──────
  h1 → 203.0.113.1:41247
✓ 出方向通,回包被 reflect 会话放行

计数器讲清了机制

text
  acl-plugin-out-ip4-fa   new sessions added         1
  acl-plugin-out-ip4-fa   existing session packets   4
  acl-plugin-in-ip4-fa    existing session packets   4     ← 注意这一行
  acl-plugin-in-ip4-fa    restart session timer      1

关键在 acl-plugin-in-ip4-fa existing session packets

回程的包走的是 input 方向,那里挂的是 deny ACL。它没被拦下, 是因为会话查找发生在规则匹配之前 —— 命中已有会话就直接放行, 根本轮不到 deny 规则。

会话表可以直接看:

bash
./labctl vppctl base show acl-plugin sessions
text
Sessions total: add 2 - del 1 = 1
Sessions active: add 2 - deact 1 = 1

会话超时可以调:

bash
set acl-plugin session timeout udp idle <>
set acl-plugin session timeout tcp idle <>
set acl-plugin session timeout tcp transient <>

NAT 的会话老化是同一类机制, 同样要注意"保活间隔必须小于超时"。

ACL 优先于 NAT 的静态映射

实验里配了一条端口转发 203.0.113.1:8080 → h2:9000, 然后让 s1 去连它:

text
── 3) 外网主动进来 —— 即使有 NAT 端口转发也被拦 ──
  s1 尝试连 203.0.113.1:8080(这条端口转发是存在的)
    TCP local 10.10.0.12:9000 external 203.0.113.1:8080 vrf 0
✓ 被 input 的 deny ACL 拦下

从 feature arc 上看得很清楚:

bash
./labctl vppctl base show interface features host-eth2
text
ip4-unicast:
  ip4-sv-reassembly-feature
  acl-plugin-in-ip4-fa      ← ACL 先执行
  nat-pre-out2in            ← NAT 后执行

包还没走到 NAT 就被 ACL 丢了。

排障提示

"配了端口转发但外面连不上"这类问题,除了查 NAT, 也要查 show acl-plugin interface 有没有 ACL 拦在前面 —— 以及 show errorsacl-plugin-in-ip4-fa ACL deny packets 有没有在涨。

这一篇最重要的一点:出向 ACL 看到的是 NAT 后的地址

出方向规则必须写转换后的地址

实验里做了这个对照。把 reflect 规则从 src 203.0.113.1/32 改成内网地址 src 10.10.0.0/24

text
  把 reflect 规则改成匹配内网地址 10.10.0.0/24 试试
✓ 立刻不通了
  acl-plugin-out-ip4-fa   ACL deny packets   3

原因:包到达外网口的 output 弧时,NAT 已经把源地址 从 10.10.0.11 改成了 203.0.113.1。规则写内网网段自然匹配不上, 落到隐式 deny。

完整的对应关系:

text
  ┌──────────────┬────────────────────────────────────────┐
  │ ACL 挂在哪    │ 规则要写什么地址                          │
  ├──────────────┼────────────────────────────────────────┤
  │ 内网口 input  │ 内网真实地址(NAT 还没执行)              │
  │ 外网口 input  │ 外网地址(out2in 的 NAT 还没执行)        │
  │ 外网口 output │ **NAT 转换之后**的地址                   │
  └──────────────┴────────────────────────────────────────┘

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

text
  内网口 input  写 10.10.0.11    ✓ 有效(第 15 步验证)
  外网口 output 写 10.10.0.11    ✗ 无效
  外网口 output 写 203.0.113.1   ✓ 有效

这是 ACL 和 NAT 一起用时最容易出错的地方

症状是"ACL 规则看起来完全正确,但流量就是被拦"。 show acl-plugin acl 里规则写得明明白白, show acl-plugin interface 里也确实挂上了,就是不通。

判断方法:看 ACL deny packets 是在 -in- 还是 -out- 那个节点上涨。 在 -out- 上涨,八成就是地址写成转换前的了。

第 9 章的 feature 顺序会把这套机制完整讲一遍。

一个不工作的特性:macip ACL

VPP 提供了 macip ACL,用途是把 IP 和 MAC 绑定,防止地址欺骗:

bash
set acl-plugin macip acl permit ip 10.10.0.11/32 mac aa:c1:ab:6d:0a:eb mask 48
set acl-plugin macip interface host-eth1 acl 0

在本实验环境(af_packet 三层接口)上它不工作

实测:MAC 和 IP 完全匹配的流量照样被丢弃。

排查过程:

bash
# 1. 确认 VPP 看到的源 MAC 和规则里的一致
vppctl show trace | grep ethernet-input -A1
text
  IP4: aa:c1:ab:6d:0a:eb -> aa:c1:ab:1d:5f:51     ← 和规则里的 MAC 一模一样
bash
# 2. 看计数器
vppctl show errors | grep -i inacl
text
  18   ip4-inacl   input ACL misses   error

ip4-inacl基于 classify 表的老路径,不是 acl-plugin 的快路径。 show acl-plugin macip acl 的输出也印证了这一点:

text
  ip4_table_index 5, ip6_table_index 5, l2_table_index 5

macip 会构建分类表,而这些表的匹配偏移是按 L2/桥接场景设计的。 在 af_packet 三层接口上,分类表匹配不到,全部落到 miss → 丢弃。

结论:macip ACL 面向二层/桥接部署,不适用于本教程这种三层接口。

三层场景下防源地址欺骗的正解是 uRPF —— 它靠路由表反查, 不需要维护任何 IP/MAC 名单,而且工作正常。见本章最后一篇

小结

解决什么内网能出去、外网不能进来,且不必预知回包端口
命令set acl-plugin acl permit+reflect src ...
挂法出方向建会话,入方向拦截
机制会话查找先于规则匹配,命中会话直接放行
计数器new sessions added / existing session packets
与 NAT 的关系入向 ACL 先于 NAT;出向 ACL 后于 NAT
最大的坑出向规则必须写转换后的地址
macip三层接口上不工作,签名是 ip4-inacl: input ACL misses

下一篇:ABF —— 用 ACL 驱动转发

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