Skip to content

ACL 基础

前七章讲的都是"把包改成什么样"。这一章换个问题:哪些包允许通过。

模型

VPP 的 ACL 和路由器上常见的 ACL 是同一套模型:

text
  一组有序规则,逐条匹配,第一条命中的说了算
  ┌──────────────────────────────────────────────────┐
  │ 规则 0   deny  src 10.10.0.11/32 proto 6 dport 9000 │
  │ 规则 1   permit src 10.10.0.0/24                    │
  │ ─────────────────────────────────────────────────  │
  │ 隐式     deny  (不匹配任何规则 → 丢弃)              │
  └──────────────────────────────────────────────────┘

两个必须先记住的行为:

一旦接口挂上 ACL,不匹配的包就会被丢弃

这是隐式 deny。挂 ACL 之前流量全通,挂上之后只有匹配 permit 规则的能过。

新手最容易犯的错是"我只想拦某一类流量",于是写一条 deny 规则挂上去 —— 结果把所有流量都拦了,因为其他流量不匹配那条 deny,落到隐式 deny 上。

正确写法是 deny 那一条之后再加一条宽泛的 permit。

命令

bash
# 建 ACL(不带 index 就是新建,VPP 分配索引并打印)
set acl-plugin acl <permit|deny|permit+reflect> \
    src <PREFIX> [dst <PREFIX>] [proto X] \
    [sport X[-Y]] [dport X[-Y]] [tcpflags <int> mask <int>] [tag FOO]

# 多条规则用逗号分隔,顺序就是匹配顺序
set acl-plugin acl deny src 10.10.0.11/32 proto 6 dport 9000, permit src 10.10.0.0/24

# 挂到接口
set acl-plugin interface <> <input|output> <acl INDEX> [del]

# 查看
show acl-plugin acl [index N]
show acl-plugin interface

index N 是替换,不是新建

bash
set acl-plugin acl permit src 10.0.0.0/8              # 新建,分配一个索引
set acl-plugin acl index 3 permit src 10.0.0.0/8      # 替换 ACL 3 的内容

如果 ACL 3 不存在,index 3 这条命令不会创建它。这是个常见误解 —— 以为可以指定索引来建 ACL,结果什么都没建成,后面挂接口时又静默失败(见下文)。

上手

应用配置

bash
./labctl reset base
./labctl apply base 12
./labctl verify base 12
bash
vppctl set acl-plugin acl permit src 10.10.0.11/32
vppctl set acl-plugin interface host-eth1 input acl 0

只放行 h1。结果:

text
  h1(规则放行)→ 203.0.113.1:41247
  h2(无规则匹配)→ (不通)
✓ 挂上 ACL 后,不匹配任何规则的流量被丢弃

计数器:ACL 的诊断依据

bash
./labctl vppctl base show errors | grep -i acl
text
  3  acl-plugin-in-ip4-fa   ACL deny packets     error
  5  acl-plugin-in-ip4-fa   ACL permit packets   error
  8  acl-plugin-in-ip4-fa   checked packets      error

三个计数器,含义直白:

计数器用途
checked packetsACL 一共处理了多少包,用来确认 ACL 真的在生效
ACL permit packets放行了多少
ACL deny packets拦了多少 —— 排查"某某连不上"时第一个要看的

节点名里的 in / out 指明方向:acl-plugin-in-ip4-fa 是入向, acl-plugin-out-ip4-fa 是出向。

checked = permit + deny

如果 checked packets 一直是 0,说明 ACL 压根没被执行 —— 可能是没挂上(见下文的静默失败),或者流量没走这个接口。

规则顺序:具体在前,宽泛在后

bash
vppctl set acl-plugin acl \
    deny src 10.10.0.11/32 proto 6 dport 9000, \
    permit src 10.10.0.0/24
text
  0: ipv4 deny   src 10.10.0.11/32 dst 0.0.0.0/0 proto 6 sport 0-65535 dport 9000
  1: ipv4 permit src 10.10.0.0/24  dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535

实测三种流量:

text
  h1 → tcp/9000    命中第 0 条 deny      → 不通
  h1 → udp/9001    不匹配 deny,落到 permit → 通
  h2 → tcp/9000    源地址不匹配 deny,落到 permit → 通

把两条规则顺序反过来的话,permit src 10.10.0.0/24 会先命中, 后面那条 deny 永远轮不到 —— 宽泛规则会把它后面所有具体规则遮住。

一个安全相关的静默失败

挂不存在的 ACL 索引 → 命令成功、ACL 没挂上、流量全放行

bash
vppctl set acl-plugin interface host-eth1 input acl 99

命令没有任何报错。但看一眼实际状态:

bash
vppctl show acl-plugin interface
text
sw_if_index 0:
sw_if_index 1:            ← 空的,什么都没挂

流量畅通无阻。

这个行为的危险在于它是 fail-open 而不是 fail-close: 自动化脚本里 ACL 索引算错了,防护会静默失效,而不是把流量全拦下。 后者会立刻被发现,前者可能几个月都没人注意。

所以配完 ACL 一定要 show acl-plugin interface 确认真的挂上了, 不能只看命令有没有报错。

ACL 只能建,不能删

acl-plugin 没有删除 ACL 的 CLI

建了就一直在。试图用 del 之类的写法只会建出更多 ACL

bash
vppctl set acl-plugin acl del index 5
text
ACL index:15          ← 它把 "del index 5" 当成规则参数,又新建了一条

set acl-plugin interface ... del 能把 ACL 从接口上摘下来, 但 ACL 对象本身留在 VPP 里。删除只能走 binary API(acl_del)。

两个直接后果:

一、绝对不要在脚本里硬编码 ACL 索引。

bash
vppctl set acl-plugin acl permit src 10.10.0.11/32
vppctl set acl-plugin interface host-eth1 input acl 0    # ✗ 危险

第一次跑,新 ACL 拿到索引 0,看起来没问题。第二次跑, 新 ACL 拿到索引 1,但你挂上去的还是上一轮那条 0 号。 现象是"规则明明改了却不生效",极难往这上面想。

正确做法是接住创建时打印的索引:

bash
idx=$(vppctl set acl-plugin acl permit src 10.10.0.11/32 \
      | tr -d '\r' | sed -n 's/^ACL index:\([0-9]*\).*/\1/p')
vppctl set acl-plugin interface host-eth1 input acl "$idx"

二、清理脚本不能盲目遍历索引。

"把 0 到 15 都摘一遍"这种写法,在索引涨到 20 以上时就漏了。 应该从 show acl-plugin interface 里读出真正挂着的索引:

text
sw_if_index 2:
  input acl(s): 20, 22, 27, 29
  output acl(s): 19, 21, 26, 28

本教程的 acl_clean 就是这么做的 —— 这两个坑我在写实验脚本时都踩了一遍: 先是硬编码 acl 0 导致换个顺序跑就失败, 后是清理循环只覆盖 0-15,索引涨过去之后残留的 ACL 把后续 所有 NAT 实验的流量都拦了。

ACL 残留是"配置正常但不通"的又一个来源

ACL 挂在接口上,而 NAT 的清理命令(nat44 plugin disable)管不着它。 做完 ACL 实验直接去跑 NAT 实验,会看到:

text
  show nat44 interfaces      一切正常
  show nat44 addresses       一切正常
  实际流量                    全不通
  show errors | grep -i nat  干净的

线索在另一个计数器上:

bash
vppctl show errors | grep -i acl
text
  49  acl-plugin-in-ip4-fa  ACL deny packets  error

这和第 7 章的静默故障是同一类问题 —— NAT 计数器干净不代表没被拦,还要看 ACL 和 feature arc。

input 还是 output

同一个接口的两个方向是独立的槽位,可以各挂一个(或多个)ACL:

bash
set acl-plugin interface host-eth2 input  acl 0    # 进这个接口的包
set acl-plugin interface host-eth2 output acl 1    # 从这个接口出去的包

方向的选择不只是"从哪边看"的问题 —— 它决定了规则里该写转换前还是转换后的地址。 和 NAT 一起用时这一点会咬人,下一篇专门讲。

和 iptables 的对照

iptablesVPP acl-plugin
规则组织链(chain),可跳转平坦的有序列表,无跳转
默认策略可设 ACCEPT 或 DROP固定隐式 deny
挂载方式按表和链的位置显式挂到接口+方向
有状态-m conntrack --ctstatepermit+reflect
计数器每条规则一个全局 permit/deny/checked
匹配字段极其丰富(可扩展 match)五元组 + tcpflags

两个主要差别:

VPP 没有链和跳转。 复杂策略不能靠 chain 拆分,只能堆在一个平坦列表里, 或者拆到多个接口。

VPP 的计数器不是每规则的。 你能知道"拦了 3 个包", 但不知道是哪条规则拦的。想定位到具体规则,得靠 show acl-plugin tables 看分类表,或者用 trace。

小结

模型有序规则,第一条命中生效,末尾隐式 deny
建 ACLset acl-plugin acl <动作> src ... [proto/sport/dport],逗号分隔多条
index N替换已有 ACL,不能用来新建
挂载set acl-plugin interface <接口> <input|output> acl N
计数器acl-plugin-in/out-ip4-fa 的 permit / deny / checked
写规则原则具体的在前,宽泛的在后
最危险的坑挂不存在的索引 → 静默 fail-open
无法删除ACL 只能建不能删(CLI),索引持续累积,别硬编码
残留后果NAT 计数器干净但流量被拦,要查 show errors | grep -i acl

下一篇:有状态 ACL 与 NAT 的执行顺序

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