主题
实验 6:用流表替掉 NORMAL
实验 5 讲了流表的语法。这一章把它用起来:删掉 NORMAL,用自己写的规则实现一个能用的二层交换机。
做完你会明白两件事:一个二层交换机到底在做什么,以及为什么真实的 SDN 方案不会像这样手写静态规则。
起环境
bash
cd labs/learn-ovs/05-openflow
./start.sh和上一章同一个实验。先记下三台主机的 MAC,后面要用:
bash
for h in h1 h2 h3; do
echo "$h: $(docker exec clab-openflow-$h cat /sys/class/net/eth1/address)"
donetext
h1: aa:c1:ab:52:72:d7 → 接在 eth1,OpenFlow 端口 1
h2: aa:c1:ab:5a:4d:60 → 接在 eth2,OpenFlow 端口 2
h3: aa:c1:ab:99:7d:12 → 接在 eth3,OpenFlow 端口 3你的 MAC 会不一样
下面命令里请用你自己环境里的值。solve.sh 是自动取的,不用手改。
先把交换机弄瘫
bash
N="docker exec clab-openflow-ovs1"
$N ovs-ofctl del-flows br0
$N ovs-ofctl dump-flows br0text
NXST_FLOW reply (xid=0x4):空的。现在试试:
bash
docker exec clab-openflow-h1 ping -c 2 -W 1 192.168.10.2text
2 packets transmitted, 0 received, 100% packet loss流表为空 = 所有包都被丢弃。 OpenFlow 的默认行为是"没有规则匹配就丢弃",不是"放行"。
这一点和防火墙的默认拒绝策略一样,务必记住。
一个二层交换机需要几条规则
想清楚交换机收到一个包时要做的判断:
text
收到一个包
│
┌─────────┴─────────┐
│ 目的 MAC 是广播/组播? │
└─────────┬─────────┘
是 │ │ 否
▼ ▼
泛洪 ┌──────────────────┐
│ 目的 MAC 认识吗? │
└────────┬─────────┘
是 │ │ 否
▼ ▼
发到对应端口 泛洪翻译成流表就是三类规则,用优先级排开:
| 优先级 | 匹配 | 动作 | 作用 |
|---|---|---|---|
| 200 | 广播/组播地址 | FLOOD | ARP 得能出去 |
| 100 | 具体的目的 MAC | output:N | 已知单播 |
| 0 | 什么都不写 | drop | 兜底 |
写规则
广播和组播
bash
$N ovs-ofctl add-flow br0 "priority=200,dl_dst=ff:ff:ff:ff:ff:ff,actions=FLOOD"
$N ovs-ofctl add-flow br0 \
"priority=200,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00,actions=FLOOD"第一条是广播地址。第二条用了掩码匹配:
text
dl_dst = 01:00:00:00:00:00 / 01:00:00:00:00:00
↑ 值 ↑ 掩码掩码只有第一个字节的最低位是 1,意思是"只看这一位"。以太网规定目的 MAC 第一个字节的最低位为 1 就是组播。这一条把所有组播地址一网打尽(IPv6 邻居发现就靠它)。
没有这两条,什么都不通
ARP 请求是广播。ARP 出不去,h1 就永远拿不到 h2 的 MAC,连 IP 包都构造不出来。
配流表时"三层不通"很多时候根子在二层的广播被挡了。先确认 ARP 能过。
已知单播
bash
$N ovs-ofctl add-flow br0 "priority=100,dl_dst=aa:c1:ab:52:72:d7,actions=output:1"
$N ovs-ofctl add-flow br0 "priority=100,dl_dst=aa:c1:ab:5a:4d:60,actions=output:2"
$N ovs-ofctl add-flow br0 "priority=100,dl_dst=aa:c1:ab:99:7d:12,actions=output:3"一台主机一条规则。这就是把 MAC 地址表手工固化成了流表。
兜底
bash
$N ovs-ofctl add-flow br0 "priority=0,actions=drop"真交换机遇到不认识的单播会泛洪。这里故意丢掉,好让"没配规则就不通"这件事看得见。
顺便清掉旧的 MAC 表
bash
$N ovs-appctl fdb/flush br0之前 NORMAL 学到的条目还在,要等 300 秒才老化。清掉它,才能验证"现在真的不靠学习了"。
验证
bash
docker exec clab-openflow-h1 ping -c 3 192.168.10.2
docker exec clab-openflow-h1 ping -c 3 192.168.10.3
docker exec clab-openflow-h2 ping -c 3 192.168.10.3text
3 packets transmitted, 3 received, 0% packet loss三台主机两两互通,而且一条 NORMAL 都没有。
MAC 表始终是空的
bash
$N ovs-appctl fdb/show br0text
port VLAN MAC Age打一轮流量再看:
bash
docker exec clab-openflow-h1 ping -c 3 192.168.10.2
$N ovs-appctl fdb/show br0text
port VLAN MAC Age还是空的。 转发正常工作,但交换机一个 MAC 都没学。
因为 MAC 学习是 NORMAL 动作的一部分,不是网桥的固有行为。删掉 NORMAL,学习也就没了。转发完全由我们写死的规则决定。
看命中计数
bash
$N ovs-ofctl dump-flows br0text
n_packets=2, priority=100,dl_dst=aa:c1:ab:52:72:d7 actions=output:1
n_packets=2, priority=100,dl_dst=aa:c1:ab:5a:4d:60 actions=output:2
n_packets=0, priority=200,dl_dst=ff:ff:ff:ff:ff:ff actions=FLOOD
n_packets=0, priority=0 actions=dropping 一来一回各 2 个包,正好对上。n_packets 让流表的执行情况完全透明 —— 这是传统交换机给不了的可观测性。
trace 确认
bash
$N ovs-appctl ofproto/trace br0 "in_port=1,dl_dst=aa:c1:ab:5a:4d:60"text
bridge("br0")
-------------
0. dl_dst=aa:c1:ab:5a:4d:60, priority 100
output:2
Final flow: unchanged
Megaflow: recirc_id=0,eth,in_port=1,dl_dst=aa:c1:ab:5a:4d:60,dl_type=0x0000
Datapath actions: 3命中哪条规则、执行什么动作,一目了然。
这个方案的问题
它能用,但没法用在生产上:
一、必须提前知道所有 MAC。 新加一台主机就要手工加规则。虚拟机迁移了,规则还得改。
二、流表规模爆炸。 1000 台主机就是 1000 条规则,还只是一个交换机上的。
三、megaflow 变细了。 回想实验 4:现在每条规则都精确匹配 dl_dst,内核缓存也就按 MAC 拆开,条数随主机数线性增长。
四、丢了泛洪学习的鲁棒性。 真交换机遇到不认识的地址会泛洪,对方回包时就学到了。我们这套只会丢包。
真实方案怎么做
方案一:就用 NORMAL
需要普通二层转发的地方,直接 NORMAL。它在 ovs-vswitchd 里是高度优化过的 C 实现,比你手写的流表又快又省内存。
把流表用在 NORMAL 做不到的地方 —— 策略、隔离、改写、隧道。典型写法是特殊流量走自定义规则,其余交给 NORMAL:
text
priority=200, <特殊条件>, actions=<自定义处理>
priority=0, actions=NORMAL方案二:learn 动作
OVS 的 learn 动作可以让流表自己生成流表:收到一个包时,按它的源 MAC 动态创建一条反向转发规则。这是用 OpenFlow 实现 MAC 学习的标准做法。
代价是每个未知包都要走慢路径(learn 无法在内核缓存里执行),所以只适合用在新流量不多的场景。
方案三:控制器
包送 CONTROLLER,控制器决定怎么转发并下发规则。SDN 的经典模型,实验 18 会做。
自动检查
bash
./verify.shtext
[通过] 默认的 NORMAL 规则已经被删掉
[通过] 存在广播泛洪规则
[通过] 存在按目的 MAC 输出到 1 口的规则
[通过] h1 能 ping 通 h2
[通过] h1 能 ping 通 h3
[通过] h2 能 ping 通 h3自测清单
- 流表为空时,包会被转发还是丢弃?
- 为什么必须有广播泛洪的规则?没有会怎样?
dl_dst=01:00:00:00:00:00/01:00:00:00:00:00匹配的是什么?- 删掉
NORMAL后 MAC 表为什么不再增长? - 这套静态规则有哪四个问题?
- 生产环境里普通二层转发该用什么?
常见问题
ping 不通,但流表看起来是对的
先确认 ARP:
bash
docker exec clab-openflow-h1 ip neigh如果是 INCOMPLETE,说明 ARP 没通,检查广播泛洪规则。
再用 trace 逐个方向验证 —— 别忘了回程也要有规则,只配单向必然不通。
FLOOD 和 ALL 有什么区别
FLOOD 会跳过被 STP 阻塞的端口和配置了 no-flood 的端口,ALL 不管这些。日常用 FLOOD。
想恢复成正常交换机
bash
$N ovs-ofctl del-flows br0
$N ovs-ofctl add-flow br0 "priority=0,actions=NORMAL"或者直接 ./stop.sh && ./start.sh。
小结
- 流表为空 = 全部丢弃,OpenFlow 是默认拒绝
- 一个二层交换机最少需要三类规则:广播泛洪、已知单播、兜底
- 组播用掩码
01:00:00:00:00:00/01:00:00:00:00:00一条覆盖 - MAC 学习是
NORMAL的一部分,删掉它就不再学习 n_packets让转发路径完全可观测- 静态规则的四个问题:要预知 MAC、规模爆炸、megaflow 变细、丢失泛洪鲁棒性
- 生产上普通转发用
NORMAL,流表留给策略、隔离、改写和隧道
下一章:实验 7:多级流表,把混在一张表里的逻辑拆成流水线。