Skip to content

实验 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)"
done
text
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 br0
text
NXST_FLOW reply (xid=0x4):

空的。现在试试:

bash
docker exec clab-openflow-h1 ping -c 2 -W 1 192.168.10.2
text
2 packets transmitted, 0 received, 100% packet loss

流表为空 = 所有包都被丢弃。 OpenFlow 的默认行为是"没有规则匹配就丢弃",不是"放行"。

这一点和防火墙的默认拒绝策略一样,务必记住。

一个二层交换机需要几条规则 ​

想清楚交换机收到一个包时要做的判断:

text
              收到一个包
                  │
        ┌─────────┴─────────┐
        │ 目的 MAC 是广播/组播? │
        └─────────┬─────────┘
             是 │      │ 否
                ▼      ▼
             泛洪   ┌──────────────────┐
                   │ 目的 MAC 认识吗?   │
                   └────────┬─────────┘
                       是 │     │ 否
                          ▼     ▼
                   发到对应端口   泛洪

翻译成流表就是三类规则,用优先级排开:

优先级匹配动作作用
200广播/组播地址FLOODARP 得能出去
100具体的目的 MACoutput: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.3
text
3 packets transmitted, 3 received, 0% packet loss

三台主机两两互通,而且一条 NORMAL 都没有。

MAC 表始终是空的 ​

bash
$N ovs-appctl fdb/show br0
text
 port  VLAN  MAC                Age

打一轮流量再看:

bash
docker exec clab-openflow-h1 ping -c 3 192.168.10.2
$N ovs-appctl fdb/show br0
text
 port  VLAN  MAC                Age

还是空的。 转发正常工作,但交换机一个 MAC 都没学。

因为 MAC 学习是 NORMAL 动作的一部分,不是网桥的固有行为。删掉 NORMAL,学习也就没了。转发完全由我们写死的规则决定。

看命中计数 ​

bash
$N ovs-ofctl dump-flows br0
text
 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=drop

ping 一来一回各 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.sh
text
  [通过] 默认的 NORMAL 规则已经被删掉
  [通过] 存在广播泛洪规则
  [通过] 存在按目的 MAC 输出到 1 口的规则
  [通过] h1 能 ping 通 h2
  [通过] h1 能 ping 通 h3
  [通过] h2 能 ping 通 h3

自测清单 ​

  1. 流表为空时,包会被转发还是丢弃?
  2. 为什么必须有广播泛洪的规则?没有会怎样?
  3. dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 匹配的是什么?
  4. 删掉 NORMAL 后 MAC 表为什么不再增长?
  5. 这套静态规则有哪四个问题?
  6. 生产环境里普通二层转发该用什么?

常见问题 ​

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:多级流表,把混在一张表里的逻辑拆成流水线。