Skip to content

实验 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+ compatibility

OVS 直接拒绝。 这是 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

看两个细节:

  1. 缩进。table 1 的处理是嵌套在 table 0 里面的,像函数调用栈
  2. output:3 在 resubmit 之后仍然执行了,最终 Datapath actions: 3,4 —— 包从 eth2 和 eth3 两个口都发出去了

这就是 resubmit 和 goto_table 的本质区别。

该用哪个 ​

goto_tableresubmit
标准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.sh
text
  [通过] table 0 有 goto_table:1
  [通过] table 1 存在转发规则
  [通过] table 2 存在出口规则
  [通过] h1 能 ping 通 h2
  [通过] h2 能 ping 通 h3

自测清单 ​

  1. 为什么用多级流表前必须先设 protocols=OpenFlow13?
  2. trace 输出最左边的 0. 1. 2. 是什么意思?
  3. goto_table:1,output:3 为什么会被 OVS 拒绝?
  4. resubmit(,1),output:3 里的 output:3 会执行吗?
  5. 什么场景下必须用 resubmit 而不能用 goto_table?
  6. 为什么每张表都要有 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 精讲,把这个已经用了七章的命令彻底吃透。