Skip to content

实验 18:外部控制器 ​

前面十七章里,流表都是我们手工敲进去的。这一章把决策权交出去 —— 让一个外部程序通过 OpenFlow 协议来管流表。

这就是 SDN 最初的核心主张:控制面和转发面分离。

起环境 ​

bash
cd labs/learn-ovs/15-controller
./start.sh
text
   h1 ── ovs1 ── h2
          │
      OpenFlow (TCP 6653)
          │
      控制器

控制器在哪

实验里控制器跑在 ovs1 容器内部,纯粹为了少搭一个节点。走的是完全相同的 TCP OpenFlow 连接,和跑在另一台机器上没有本质区别 —— 只是 remote_ip 换成了 127.0.0.1。

起一个控制器 ​

OVS 自带一个最小实现:

bash
docker exec -d clab-controller-ovs1 \
  sh -c 'ovs-testcontroller --detach --pidfile ptcp:6653'

ptcp:6653 表示被动监听 TCP 6653(OpenFlow 的标准端口,早期是 6633)。

ovs-testcontroller 的行为是把交换机当学习型网桥:收到不认识的流量就下发一条转发规则。生产环境用的是 ONOS、OpenDaylight、Faucet、Ryu 这些完整控制器,但连接建立和消息交互的过程完全一样。

连上去 ​

bash
docker exec clab-controller-ovs1 \
  ovs-vsctl set-controller br0 tcp:127.0.0.1:6653

确认连上了:

bash
docker exec clab-controller-ovs1 ovs-vsctl get Controller br0 is_connected
text
true

看完整状态:

bash
docker exec clab-controller-ovs1 ovs-vsctl list Controller
text
is_connected     : true
role             : other
status           : {sec_since_connect="0", state=ACTIVE}
target           : "tcp:127.0.0.1:6653"
inactivity_probe : []
max_backoff      : []
字段含义
target控制器地址。tcp: 主动连、ptcp: 被动等、ssl: 加密
is_connected当前是否连着
roleOpenFlow 角色:other / master / slave(多控制器时用)
status连接状态、上次连接/断开距今多久、最后的错误
inactivity_probe多久没消息就发探测,默认 5 秒
max_backoff重连退避上限,默认 8 秒

看控制器下发了什么 ​

打点流量:

bash
docker exec clab-controller-h1 ping -c 3 192.168.10.2
bash
docker exec clab-controller-ovs1 ovs-ofctl dump-flows br0
text
 idle_timeout=60, priority=1,arp,in_port=1,dl_src=aa:c1:ab:4b:16:f8,
   dl_dst=aa:c1:ab:97:ee:67,arp_spa=192.168.10.1,arp_tpa=192.168.10.2,
   arp_op=2 actions=output:2

 idle_timeout=60, priority=1,icmp,in_port=2,dl_src=aa:c1:ab:97:ee:67,
   dl_dst=aa:c1:ab:4b:16:f8,nw_src=192.168.10.2,nw_dst=192.168.10.1,
   icmp_type=0,icmp_code=0 actions=output:1

 idle_timeout=60, priority=1,icmp,in_port=1,...,icmp_type=8 actions=output:2

 priority=0 actions=CONTROLLER:128

最后那条是关键:priority=0 actions=CONTROLLER:128 —— 所有没被其它规则匹配的包,把前 128 字节送给控制器。这叫 table-miss 规则,是反应式(reactive)控制器的入口。

前面三条是控制器在收到 packet-in 之后动态下发的。注意它们的特点:

  • 极其精确:匹配了 in_port、双向 MAC、IP、ICMP 类型
  • 带 idle_timeout=60:60 秒没流量就自动删掉,避免流表无限增长

反应式模型的代价 ​

这个模型有两个明显问题:

一、第一个包的延迟。 要跑一趟控制器,比实验 4 的 upcall 还慢一个数量级(那只到用户态,这要过网络)。

二、缓存爆炸。 每条流一条精确规则,正是实验 4警告过的情况 —— megaflow 被拆得极细,内核缓存条数随连接数线性增长。

别在高流量路径上用 CONTROLLER

actions=CONTROLLER 意味着每个匹配的包都要过一趟慢路径加网络往返。table-miss 规则用它是可以的(只有新流量会命中),但绝不能用在业务流量上。

生产环境的主流做法是主动式(proactive):控制器在启动时就把完整策略下发好,运行时不再有 packet-in。OVN 就是纯主动式的。

fail_mode:控制器挂了怎么办 ​

这是生产环境最需要想清楚的配置。

bash
ovs-vsctl set-fail-mode br0 standalone   # 或 secure
模式控制器断开后适合
standalone(默认)回落成普通二层交换机,继续转发可用性优先
secure保持现有流表,不新增任何规则;流表空就全丢安全优先

实测 standalone ​

bash
ovs-vsctl set-fail-mode br0 standalone
pkill -f ovs-testcontroller

等一会儿再 ping:

text
3 packets transmitted, 3 received, 0% packet loss

还是通的。 但流表是空的:

bash
ovs-ofctl dump-flows br0
text
NXST_FLOW reply (xid=0x4):

一条都没有,却能转发?用 trace 看:

bash
ovs-appctl ofproto/trace br0 "in_port=1,dl_src=<h1MAC>,dl_dst=<h2MAC>"
text
bridge("br0")
-------------
 0. priority 15790320
    NORMAL
     -> forwarding to learned port

Datapath actions: 3

一条看不见的规则

priority 15790320 —— 这是 OVS 在 standalone 回落时装的内部规则,优先级远高于任何用户规则(OpenFlow 优先级最大 65535,这个值超出了正常范围)。

它不出现在 ovs-ofctl dump-flows 里,因为那只显示 OpenFlow 可见的流表。只有 ofproto/trace 能看到它。

排查"流表是空的但还在转发"这类怪事时,记得用 trace 而不是 dump-flows。

回落不是立刻发生的 ​

实测大约 15 秒。OVS 要等 inactivity_probe 超时、重连退避几轮,确认连接真的没了才回落。

所以 verify.sh 里用的是轮询而不是固定 sleep:

bash
for _ in $(seq 1 30); do
  ovs-vsctl get Controller br0 is_connected | grep -q false \
    && ping -c 1 -W 1 192.168.10.2 >/dev/null 2>&1 && break
  sleep 1
done

测异步状态变化时,轮询到条件成立比猜一个 sleep 时间可靠得多。

实测 secure ​

bash
ovs-vsctl set-fail-mode br0 secure
ovs-ofctl del-flows br0
text
2 packets transmitted, 0 received, 100% packet loss

完全不通。 secure 模式下 OVS 不会自作主张加任何规则,流表空就是全丢。

怎么选 ​

standalone:控制器挂了网络还能用,但策略全部失效 —— 原本被隔离的租户会突然互通。这在多租户环境里是严重的安全事故。

secure:控制器挂了网络就断,但不会泄露。配合流表持久化和控制器高可用使用。

多租户/有隔离要求的场景一律用 secure。 这也是 OVN 的选择。

多控制器与角色 ​

可以配多个控制器做高可用:

bash
ovs-vsctl set-controller br0 tcp:10.0.0.10:6653 tcp:10.0.0.11:6653

OpenFlow 1.3 定义了三种角色:

角色权限
master全部权限,同一时刻只能有一个
slave只读,收不到 packet-in
equal(OVS 里显示为 other)和 master 同等权限,可以有多个

角色由控制器自己声明,OVS 负责保证只有一个 master。

其它连接方式 ​

bash
# 主动连出去(最常见)
ovs-vsctl set-controller br0 tcp:10.0.0.10:6653

# 被动等控制器连进来
ovs-vsctl set-controller br0 ptcp:6653

# 加密连接(生产环境应该用这个)
ovs-vsctl set-controller br0 ssl:10.0.0.10:6653
ovs-vsctl set-ssl <私钥> <证书> <CA证书>

明文 OpenFlow 等于把交换机的控制权裸奔

tcp: 连接没有任何认证和加密。任何能连到 6653 端口的人都能改流表。

生产环境用 ssl:,或者至少把控制网络严格隔离。

常用的控制器 ​

控制器语言特点
ovs-testcontrollerCOVS 自带,只做学习型交换,仅供测试
FaucetPython轻量、面向生产、配置驱动
ONOSJava电信级,功能全,重
OpenDaylightJava生态大,重
RyuPython写自定义控制器的经典框架,已停止维护
OVNC严格说不是 OpenFlow 控制器,见下一章

自动检查 ​

bash
./verify.sh
text
  [通过] br0 配了控制器
  [通过] 控制器连接已建立
  [通过] 控制器接管后仍然连通
  [通过] 流表里出现了控制器下发的规则
  [通过] standalone 模式:控制器断了仍然转发
  [通过] secure 模式:控制器断了就不转发

自测清单 ​

  1. tcp: 和 ptcp: 有什么区别?
  2. priority=0 actions=CONTROLLER:128 这条规则的作用是什么?
  3. 反应式模型有哪两个代价?
  4. standalone 模式下流表是空的,为什么还能转发?怎么看到那条规则?
  5. 控制器断开后多久才回落?为什么不能用固定 sleep 来测?
  6. 多租户环境该用哪种 fail_mode?为什么?
  7. 为什么生产环境不该用 tcp:?

常见问题 ​

is_connected 一直是 false ​

bash
ovs-vsctl list Controller | grep status

status 里的 last_error 会告诉你原因:

  • Connection refused → 控制器没起来,或端口不对
  • No route to host → 网络不通
  • 一直 state=BACKOFF → 反复重连失败,检查防火墙

连上了但流量不通 ​

看有没有 table-miss 规则:

bash
ovs-ofctl dump-flows br0 | grep CONTROLLER

没有的话,控制器不会收到 packet-in,自然不会下发规则。有些控制器需要显式配置才会装这条。

控制器一断网络就全断 ​

fail_mode 是 secure 且流表为空。这是预期行为。要么改成 standalone,要么保证控制器高可用。

流表条数暴涨 ​

反应式控制器的典型问题。每条流一条规则,加上 idle_timeout 不够短的话会堆积。

bash
ovs-ofctl dump-flows br0 | wc -l
ovs-dpctl show | grep flows

治本办法是改用主动式下发。

想看 OpenFlow 消息交互 ​

bash
ovs-ofctl snoop br0

这会连上实验 2 提到的 br0.snoop 套接字,实时打印交换机和控制器之间的所有 OpenFlow 消息。调试控制器时非常有用。

小结 ​

  • 控制器通过 OpenFlow over TCP 6653 管理流表,set-controller 一条命令接入
  • ptcp: 被动监听,tcp: 主动连出,生产环境用 ssl:
  • priority=0 actions=CONTROLLER:128 是反应式模型的入口(table-miss)
  • 反应式的代价:第一个包延迟高 + 每条流一条精确规则导致缓存爆炸
  • 生产环境主流是主动式:启动时下发完整策略,运行时无 packet-in
  • standalone 回落成普通交换机(策略失效,有安全风险)
  • secure 保持流表不新增(流表空就全断)
  • 多租户环境必须用 secure
  • standalone 回落靠一条优先级 15790320 的隐藏 NORMAL 规则,dump-flows 看不到,只有 ofproto/trace 能看到
  • 回落约需 15 秒,测异步状态要轮询而不是固定 sleep
  • ovs-ofctl snoop br0 可以实时看 OpenFlow 消息

下一章:实验 19:用户态 datapath。