主题
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 interfaceindex 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 12bash
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 acltext
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 packets | ACL 一共处理了多少包,用来确认 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/24text
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 interfacetext
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 5text
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 acltext
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 的对照
| iptables | VPP acl-plugin | |
|---|---|---|
| 规则组织 | 链(chain),可跳转 | 平坦的有序列表,无跳转 |
| 默认策略 | 可设 ACCEPT 或 DROP | 固定隐式 deny |
| 挂载方式 | 按表和链的位置 | 显式挂到接口+方向 |
| 有状态 | -m conntrack --ctstate | permit+reflect |
| 计数器 | 每条规则一个 | 全局 permit/deny/checked |
| 匹配字段 | 极其丰富(可扩展 match) | 五元组 + tcpflags |
两个主要差别:
VPP 没有链和跳转。 复杂策略不能靠 chain 拆分,只能堆在一个平坦列表里, 或者拆到多个接口。
VPP 的计数器不是每规则的。 你能知道"拦了 3 个包", 但不知道是哪条规则拦的。想定位到具体规则,得靠 show acl-plugin tables 看分类表,或者用 trace。
小结
| 模型 | 有序规则,第一条命中生效,末尾隐式 deny |
| 建 ACL | set 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 的执行顺序。