主题
实验 7:多级流表
实验 6 把所有规则塞进了一张表。规则一多,这张表就会变成一锅粥:转发逻辑、安全策略、VLAN 处理全靠优先级硬挤在一起,改一条不知道会影响什么。
多级流表(pipeline)就是来解决这个问题的:一张表只管一件事,处理完交给下一张。
起环境
bash
cd labs/learn-ovs/06-pipeline
./start.sh拓扑和上一章相同:h1、h2、h3 接在 br0 的 1、2、3 口。
start.sh 多做了一件事:
bash
ovs-vsctl set Bridge br0 protocols=OpenFlow10,OpenFlow13必须启用 OpenFlow 1.3。 多级流表和 goto_table 是 OpenFlow 1.1 才引入的,1.0 里根本没有这个概念。
后面所有命令都要带 -O OpenFlow13:
bash
OF="ovs-ofctl -O OpenFlow13"要搭的流水线
text
包进来
│
▼
┌───────────────────────────────────────────┐
│ table 0 准入 │
│ 只放行 ARP 和 IPv4,其余丢弃 │
│ 放行的 → goto_table:1 │
└───────────────────┬───────────────────────┘
▼
┌───────────────────────────────────────────┐
│ table 1 转发判断 │
│ 广播 → FLOOD(到此结束) │
│ 已知单播 → goto_table:2 │
│ 其余 → drop │
└───────────────────┬───────────────────────┘
▼
┌───────────────────────────────────────────┐
│ table 2 出口 │
│ 按目的 MAC 决定 output:N │
└───────────────────────────────────────────┘这个分层不是我编的,它接近 OpenStack 和 OVN 里真实流水线的骨架(真实的有二三十张表,但分层思路一样)。
写规则
table 0:准入
bash
N="docker exec clab-pipeline-ovs1"
OF="ovs-ofctl -O OpenFlow13"
$N $OF add-flow br0 "table=0,priority=100,arp,actions=goto_table:1"
$N $OF add-flow br0 "table=0,priority=100,ip,actions=goto_table:1"
$N $OF add-flow br0 "table=0,priority=0,actions=drop"这一级只做过滤:不是 ARP 也不是 IPv4 的包直接丢掉。
真实环境里 table 0 通常还会做:反 MAC 欺骗(校验 in_port 和 dl_src 是否对得上)、丢弃畸形包、给流量打上 VLAN 或租户标记。
table 1:转发判断
bash
$N $OF add-flow br0 "table=1,priority=200,dl_dst=ff:ff:ff:ff:ff:ff,actions=FLOOD"
$N $OF add-flow br0 "table=1,priority=100,dl_dst=<h1的MAC>,actions=goto_table:2"
$N $OF add-flow br0 "table=1,priority=100,dl_dst=<h2的MAC>,actions=goto_table:2"
$N $OF add-flow br0 "table=1,priority=100,dl_dst=<h3的MAC>,actions=goto_table:2"
$N $OF add-flow br0 "table=1,priority=0,actions=drop"注意广播那条是 FLOOD,不往下走了。执行了输出动作,流水线到此为止。
table 2:出口
bash
$N $OF add-flow br0 "table=2,priority=100,dl_dst=<h1的MAC>,actions=output:1"
$N $OF add-flow br0 "table=2,priority=100,dl_dst=<h2的MAC>,actions=output:2"
$N $OF add-flow br0 "table=2,priority=100,dl_dst=<h3的MAC>,actions=output:3"
$N $OF add-flow br0 "table=2,priority=0,actions=drop"用 ./solve.sh 可以一次配好(它会自动读取真实 MAC)。
看包怎么走
bash
$N ovs-appctl ofproto/trace br0 \
"in_port=1,dl_src=<h1的MAC>,dl_dst=<h2的MAC>,dl_type=0x0800,\
nw_src=192.168.10.1,nw_dst=192.168.10.2,nw_proto=1"text
bridge("br0")
-------------
0. ip, priority 100
goto_table:1
1. dl_dst=aa:c1:ab:65:70:e3, priority 100
goto_table:2
2. dl_dst=aa:c1:ab:65:70:e3, priority 100
output:2
Final flow: unchanged
Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=aa:c1:ab:65:70:e3,nw_frag=no
Datapath actions: 3最左边的 0. 1. 2. 就是表号。 一眼看清包依次经过了哪三张表、每张表命中了哪条规则。
再看一个被拦住的:
bash
$N ovs-appctl ofproto/trace br0 \
"in_port=1,dl_src=<h1的MAC>,dl_dst=<h2的MAC>,dl_type=0x88cc"0x88cc 是 LLDP,既不是 ARP 也不是 IPv4:
text
bridge("br0")
-------------
0. priority 0
drop
Final flow: unchanged
Datapath actions: drop在 table 0 就被丢了,根本没进后面的表。准入这一级生效了。
多级流表最大的好处是可观测
出问题时 trace 直接告诉你"包在第几张表被谁拦下"。所有规则挤在一张表里的话,你只知道"命中了某条规则",很难看出策略层次。
goto_table 和 resubmit
OVS 有两种跳转方式,语义完全不同。这是本章最需要搞清楚的地方。
goto_table:一去不回
text
actions=goto_table:1- OpenFlow 1.1+ 标准动作
- 只能往后跳(目标表号必须大于当前表)
- 必须是动作列表里的最后一条
- 跳过去就不回来了
试试在它后面接动作:
bash
$N $OF add-flow br0 "table=0,priority=100,ip,actions=goto_table:1,output:3"text
ovs-ofctl: invalid instruction ordering: apply_actions must appear
before goto_table, for OpenFlow 1.1+ compatibilityOVS 直接拒绝。 这是 OpenFlow 规范的硬性要求。
resubmit:像函数调用
text
actions=resubmit(,1),output:3- Nicira 私有扩展,OpenFlow 1.0 下也能用
- 可以往前跳,也可以往回跳(小心死循环)
- 执行完会返回,继续执行后面的动作
实测一下:
bash
$N $OF del-flows br0
$N $OF add-flow br0 "table=0,priority=100,ip,actions=resubmit(,1),output:3"
$N $OF add-flow br0 "table=1,priority=100,dl_dst=<h2的MAC>,actions=output:2"
$N ovs-appctl ofproto/trace br0 "in_port=1,dl_src=<h1MAC>,dl_dst=<h2MAC>,\
dl_type=0x0800,nw_src=192.168.10.1,nw_dst=192.168.10.2,nw_proto=1"text
bridge("br0")
-------------
0. ip, priority 100
resubmit(,1)
1. dl_dst=aa:c1:ab:65:70:e3, priority 100
output:2
output:3
Final flow: unchanged
Datapath actions: 3,4看两个细节:
- 缩进。table 1 的处理是嵌套在 table 0 里面的,像函数调用栈
output:3在 resubmit 之后仍然执行了,最终Datapath actions: 3,4—— 包从 eth2 和 eth3 两个口都发出去了
这就是 resubmit 和 goto_table 的本质区别。
该用哪个
goto_table | resubmit | |
|---|---|---|
| 标准 | OpenFlow 1.1+ | Nicira 扩展 |
| OF 1.0 可用 | 否 | 是 |
| 方向 | 只能往后 | 任意 |
| 执行后 | 不返回 | 返回并继续 |
| 位置 | 必须最后 | 任意位置 |
| 典型用途 | 线性流水线 | 复用子逻辑、组播复制 |
默认用 goto_table —— 它是标准,语义简单,不会写出死循环。
需要 resubmit 的两种情况:
- 复用一段逻辑:多张表都要跑同一套检查,把它放在一张表里,各处
resubmit过去 - 一个包发多份:连续多次
resubmit到不同的表,每次输出到不同端口 —— 这正是 OVS 实现组播和泛洪的内部机制
resubmit 可以写出死循环
table=1 actions=resubmit(,1) 会让 OVS 一直递归。OVS 有递归深度限制(默认 64 层)会保护自己,但包最终会被丢弃,而且白白消耗慢路径 CPU。
trace 时看到大量嵌套缩进,就该怀疑这个问题。
分表的实践建议
表号留空隙。 别用 0、1、2、3 挨着排,用 0、10、20、30。以后要插一级不用重排所有规则。
每张表都要有兜底。 每张表都加一条 priority=0,明确写清没匹配上时怎么办(drop 还是 goto_table:N)。否则包会静默丢弃,排查时一脸茫然。
一张表只做一件事。 表的名字应该能一句话说清:准入、分类、转发、出口策略。说不清就说明该拆了。
用 cookie 标记出处。 每张表的规则用不同的 cookie,方便批量替换:
bash
ovs-ofctl del-flows br0 "cookie=0x10/-1" # 只清 table 10 那批自动检查
bash
./verify.shtext
[通过] table 0 有 goto_table:1
[通过] table 1 存在转发规则
[通过] table 2 存在出口规则
[通过] h1 能 ping 通 h2
[通过] h2 能 ping 通 h3自测清单
- 为什么用多级流表前必须先设
protocols=OpenFlow13? - trace 输出最左边的
0.1.2.是什么意思? goto_table:1,output:3为什么会被 OVS 拒绝?resubmit(,1),output:3里的output:3会执行吗?- 什么场景下必须用
resubmit而不能用goto_table? - 为什么每张表都要有
priority=0的兜底规则?
常见问题
报错 unsupported instruction goto_table
没启用 OpenFlow 1.3,或者命令没带 -O OpenFlow13:
bash
ovs-vsctl set Bridge br0 protocols=OpenFlow10,OpenFlow13
ovs-ofctl -O OpenFlow13 add-flow br0 "table=0,actions=goto_table:1"包在中间某张表消失了
用 trace 定位是哪张表:
bash
ovs-appctl ofproto/trace br0 "<你的包>"看最后停在哪个表号。多半是那张表没有匹配的规则,落到了兜底的 drop;或者压根没写兜底,被隐式丢弃。
dump-flows 输出太长
按表筛选:
bash
ovs-ofctl -O OpenFlow13 dump-flows br0 table=1想看每张表用了多少规则
bash
ovs-ofctl -O OpenFlow13 dump-tables br0 | grep -E "^ *[0-9]+:" | head小结
- 多级流表把混在一起的逻辑拆成一张表一件事的流水线
- 需要先
set Bridge br0 protocols=OpenFlow13,命令带-O OpenFlow13 goto_table:标准、只能往后、必须最后一条、不返回resubmit:Nicira 扩展、OF1.0 可用、可任意跳、会返回并继续执行后续动作- 需要复用子逻辑或把一个包发多份时才用
resubmit,否则用goto_table - trace 的表号前缀让"包在第几级被拦下"一目了然
- 实践:表号留空隙、每表必有兜底、一表一职责、用 cookie 标记出处
下一章:实验 8:ofproto/trace 精讲,把这个已经用了七章的命令彻底吃透。