主题
实验 5:流表模型
第 1 部分里,OVS 的全部转发逻辑就一条规则:
text
priority=0 actions=NORMAL从这一章开始,我们不再让它自己决定,而是自己写规则。这是 OVS 和普通网桥真正拉开差距的地方。
起环境
bash
cd labs/learn-ovs/05-openflow
./start.sh这次是三台主机:
text
h1 h2 h3
192.168.10.1 192.168.10.2 192.168.10.3
│ │ │
eth1 eth2 eth3
└───────── ovs1 br0 ────────┘三个口的 OpenFlow 端口号:
bash
docker exec clab-openflow-ovs1 ovs-ofctl dump-ports-desc br0text
1(eth1)
2(eth2)
3(eth3)
LOCAL(br0)一条流表项的结构
bash
docker exec clab-openflow-ovs1 ovs-ofctl dump-flows br0text
cookie=0x0, duration=0.354s, table=0, n_packets=5, n_bytes=430,
idle_age=0, priority=0 actions=NORMAL拆开看:
text
cookie=0x0 你自己打的标记,OVS 不解释它,用来批量管理规则
duration=0.354s 这条规则存在了多久
table=0 在第几张表
n_packets=5 ← 命中了多少个包。排错时最先看这个
n_bytes=430 命中了多少字节
idle_age=0 距离上次命中过了几秒
priority=0 优先级
(空) 匹配条件。这里为空 = 匹配所有包
actions=NORMAL 动作一条流表项的本质就是三件事:在哪张表、匹配什么、做什么。加上一个优先级来解决"多条都能匹配"的冲突。
匹配:能看的字段
OVS 能匹配的字段有几十个,覆盖二到四层。常用的:
二层
| 字段 | 含义 | 例子 |
|---|---|---|
in_port | 从哪个 OpenFlow 端口进来 | in_port=1 |
dl_src / dl_dst | 源/目的 MAC | dl_dst=aa:bb:cc:dd:ee:ff |
dl_type | 以太网类型 | 0x0800=IPv4、0x0806=ARP、0x86dd=IPv6 |
dl_vlan | VLAN ID | dl_vlan=100 |
dl_vlan_pcp | VLAN 优先级 | dl_vlan_pcp=5 |
三层
| 字段 | 含义 | 例子 |
|---|---|---|
nw_src / nw_dst | 源/目的 IP,支持掩码 | nw_src=10.0.0.0/8 |
nw_proto | 协议号 | 1=ICMP、6=TCP、17=UDP |
nw_tos / nw_ttl | TOS / TTL | nw_ttl=1 |
arp_spa / arp_tpa / arp_op | ARP 的字段 | arp_op=1(请求) |
四层
| 字段 | 含义 | 例子 |
|---|---|---|
tp_src / tp_dst | 源/目的端口 | tp_dst=80 |
tcp_flags | TCP 标志位 | tcp_flags=+syn-ack |
icmp_type / icmp_code | ICMP 类型 | icmp_type=8 |
简写
OVS 提供了一批简写,会自动补上前置条件:
| 简写 | 等价于 |
|---|---|
ip | dl_type=0x0800 |
ipv6 | dl_type=0x86dd |
arp | dl_type=0x0806 |
tcp | dl_type=0x0800,nw_proto=6 |
udp | dl_type=0x0800,nw_proto=17 |
icmp | dl_type=0x0800,nw_proto=1 |
匹配四层字段必须先限定协议
tp_dst=80 单独写是无效的 —— OVS 不知道该去哪里找端口号。必须写成 tcp,tp_dst=80。
同理 nw_dst=1.2.3.4 要写成 ip,nw_dst=1.2.3.4。
这是新手最常见的报错来源。忘了写前缀,OVS 会直接拒绝或者规则永远不命中。
优先级:谁说了算
同一张表里多条规则都能匹配时,优先级高的赢。范围 0–65535,默认 32768。
试一下:
bash
N="docker exec clab-openflow-ovs1"
$N ovs-ofctl add-flow br0 "priority=100,ip,nw_dst=192.168.10.2,actions=output:2"
$N ovs-ofctl add-flow br0 "priority=200,ip,nw_dst=192.168.10.2,tcp,tp_dst=22,actions=drop"
$N ovs-ofctl dump-flows br0text
priority=200,tcp,nw_dst=192.168.10.2,tp_dst=22 actions=drop
priority=100,ip,nw_dst=192.168.10.2 actions=output:2
priority=0 actions=NORMAL一个发往 192.168.10.2:22 的 TCP 包,三条规则都能匹配,但只有 priority=200 那条生效,包被丢掉。发往 192.168.10.2:80 的包不匹配第一条,落到 priority=100,被转发。
优先级的常见约定
写复杂流表时给优先级留出档位,方便以后插规则:
text
0 兜底(drop 或 NORMAL)
1-99 宽泛的默认策略
100-199 正常业务转发
200-299 例外和特殊处理
300+ 安全策略、紧急封禁优先级相同又都能匹配时,OVS 选哪条是未定义的。 不要依赖这种情况。
动作:能做什么
输出
| 动作 | 含义 |
|---|---|
output:N | 从 N 号端口发出 |
output:NXM_... | 从寄存器里存的端口号发出 |
FLOOD | 发给除入口外所有端口(不含被 STP 阻塞的) |
ALL | 发给除入口外所有端口(不管 STP) |
LOCAL | 发给网桥自己的 internal 口 |
IN_PORT | 从进来的那个口原路发回 |
CONTROLLER | 送给控制器 |
drop | 丢弃。其实是"没有任何动作"的写法 |
NORMAL | 交给传统二层转发逻辑 |
改写报文
| 动作 | 含义 |
|---|---|
mod_dl_src / mod_dl_dst | 改 MAC |
mod_nw_src / mod_nw_dst | 改 IP |
mod_tp_src / mod_tp_dst | 改端口号 |
mod_vlan_vid | 改 VLAN ID |
push_vlan / pop_vlan | 加/去 VLAN 标签(实验 10) |
dec_ttl | TTL 减一 |
流程控制
| 动作 | 含义 | 详见 |
|---|---|---|
resubmit(port,table) | 跳到另一张表处理,会返回 | 实验 7 |
goto_table:N | 跳到第 N 张表,不返回 | 实验 7 |
learn(...) | 动态生成新的流表项 | 实验 8 |
ct(...) | 送连接跟踪 | 实验 16 |
多个动作用逗号分隔,按顺序执行:
text
actions=mod_dl_dst:02:00:00:00:00:01,output:2drop 的真相
actions=drop 不是一个真的动作,它是"动作列表为空"的语法糖。这两条等价:
text
actions=drop
actions=所以永远不要写 actions=drop,output:2 —— OVS 会报错。
超时:让规则自己消失
bash
$N ovs-ofctl add-flow br0 \
"priority=300,idle_timeout=10,hard_timeout=30,udp,tp_dst=53,actions=drop"
$N ovs-ofctl dump-flows br0 | grep 300text
idle_timeout=10, hard_timeout=30, priority=300,udp,tp_dst=53 actions=drop| 参数 | 含义 |
|---|---|
idle_timeout=N | 连续 N 秒没有包命中就删除。0 表示不过期 |
hard_timeout=N | 加进来 N 秒后无条件删除,不管有没有流量 |
控制器下发的规则常带 idle_timeout,让不活跃的流自动清理,避免流表无限膨胀。
cookie:给规则贴标签
bash
$N ovs-ofctl add-flow br0 "cookie=0x1234,priority=50,arp,actions=NORMAL"
$N ovs-ofctl dump-flows br0 | grep 0x1234text
cookie=0x1234, priority=50,arp actions=NORMALOVS 完全不解释 cookie 的含义,它就是个 64 位标记。价值在于批量管理:
bash
$N ovs-ofctl del-flows br0 "cookie=0x1234/-1"/-1 是掩码,表示全部位都要匹配。一条命令删掉所有打了这个标记的规则。
控制器通常用 cookie 标识"这批规则是哪个业务下发的",重连后可以精确清理自己的旧规则而不动别人的。
增删改查
bash
# 加
ovs-ofctl add-flow br0 "priority=100,ip,actions=output:2"
# 从文件批量加(每行一条,适合初始化)
ovs-ofctl add-flows br0 flows.txt
# 改:匹配条件相同的规则,动作被替换
ovs-ofctl mod-flows br0 "priority=100,ip,actions=drop"
# 删:按匹配条件
ovs-ofctl del-flows br0 "ip,nw_dst=192.168.10.2"
# 删:清空整张表(连默认的 NORMAL 一起)
ovs-ofctl del-flows br0
# 查:全部
ovs-ofctl dump-flows br0
# 查:只看匹配某个条件的
ovs-ofctl dump-flows br0 "ip,nw_dst=192.168.10.2"
# 查:只看某张表
ovs-ofctl dump-flows br0 table=1筛选查询很实用:
bash
$N ovs-ofctl dump-flows br0 "ip,nw_dst=192.168.10.2"text
priority=200,tcp,nw_dst=192.168.10.2,tp_dst=22 actions=drop
priority=100,ip,nw_dst=192.168.10.2 actions=output:2只列出可能匹配这个条件的规则,流表大了以后离不开它。
del-flows 不加参数会删掉一切
包括默认的 priority=0 actions=NORMAL。执行完网桥就完全不转发了 —— 这正是下一章要做的事,但在生产环境上这么干等于断网。
OpenFlow 版本
bash
$N ovs-vsctl get Bridge br0 protocolstext
[]空表示只启用 OpenFlow 1.0。1.0 没有多级流表、没有 goto_table、匹配字段也少。要用新特性得显式打开:
bash
ovs-vsctl set Bridge br0 protocols=OpenFlow10,OpenFlow13然后命令行也要指定版本:
bash
ovs-ofctl -O OpenFlow13 dump-flows br0为什么 1.0 还能用 resubmit
resubmit 是 Nicira 的私有扩展,不属于标准 OpenFlow,所以在 1.0 下也能用。这是 OVS 特有的能力,实验 7 会详细对比它和 goto_table。
自测清单
- 一条流表项由哪几部分组成?排错时最该先看哪个字段?
tp_dst=80为什么不能单独写?- 两条规则都能匹配同一个包时,怎么决定用哪条?优先级也相同呢?
idle_timeout和hard_timeout有什么区别?actions=drop和actions=有区别吗?- cookie 是给谁用的?
ovs-vsctl get Bridge br0 protocols返回空表示什么?
常见问题
规则加进去了但 n_packets 一直是 0
三种可能,按顺序查:
- 匹配条件写错,包根本不满足 —— 用
ofproto/trace验证 - 被更高优先级的规则截胡 ——
dump-flows看有没有优先级更高又能匹配的 - 流量根本没到这个网桥 —— 检查端口是否 up、接线是否正确
报错 unknown field 或 invalid field
多半是没写协议前缀(tp_dst 前面要有 tcp),或者该字段需要更高的 OpenFlow 版本。
加了规则以后全网不通
检查是不是不小心执行了 del-flows(不带参数),把默认的 NORMAL 删掉了。补回来:
bash
ovs-ofctl add-flow br0 "priority=0,actions=NORMAL"小结
- 流表项 = 表号 + 优先级 + 匹配条件 + 动作,外加 cookie 和超时
n_packets是排错第一现场 —— 规则有没有被命中一看便知- 匹配四层字段必须先限定协议(
tcp,tp_dst=80) - 优先级高的赢;优先级相同又都能匹配时行为未定义,不要依赖
actions=drop就是空动作列表,不能和其它动作并列idle_timeout看流量,hard_timeout看时间- cookie 用于批量管理,
del-flows "cookie=0x1234/-1"精确清理 - 默认只开 OpenFlow 1.0,多级流表需要手动启用 1.3
下一章:实验 6:用流表替掉 NORMAL,把这些拼起来实现一个完整的二层交换机。