Skip to content

实验 3:端口类型 ​

实验 2 里 Interface 表有一列 type,普通网卡是空字符串。这一章把这一列用起来。

Open_vSwitch 表的 iface_types 列出了全部支持的类型:

text
afxdp, bareudp, erspan, geneve, gre, gtpu, internal, ip6erspan, ip6gre,
lisp, patch, srv6, stt, system, tap, vxlan

看着吓人,但日常真正会碰到的只有四类:

类型是什么什么时候用
空(system)一块真实的 Linux 网络设备接物理网卡、veth、tap
internal网桥自己的三层接口给交换机配 IP、做网关
patch两个 OVS 网桥之间的直连拆分网桥职责(OpenStack 的经典布局)
vxlan / geneve / gre隧道端口Overlay 网络(第 4 部分)

这一章做前三种,隧道留到第 4 部分。

这一章要搭的东西 ​

text
   h1 ── eth1 ──┤ br-int ├══ patch ══┤ br-ex ├── eth2 ── h2
                    │
                  mgmt0                 h1 192.168.10.1/24
              (internal)                h2 192.168.10.2/24
           192.168.10.254/24

两个网桥,中间用 patch 连起来。h1 和 h2 仍在同一个二层里,但它们的流量要穿过两个网桥。

为什么要拆成两个网桥

这是 OpenStack 的经典布局:br-int 接所有虚拟机(策略、VLAN、安全组都在这做),br-ex 接物理上联口(只管出去),中间用 patch 连接。

拆开的好处是职责清晰:br-int 的流表可能有上千条业务规则,br-ex 只有几条。混在一个网桥里,流表会变成一锅粥。

起环境 ​

bash
cd labs/learn-ovs/03-port-types
./start.sh

普通端口:type 为空 ​

bash
docker exec clab-port-types-ovs1 ovs-vsctl add-br br-int
docker exec clab-port-types-ovs1 ovs-vsctl add-port br-int eth1
docker exec clab-port-types-ovs1 ip link set eth1 up

这就是实验 1 做过的事。Interface.type 是空字符串,表示"这是一块真实存在的 Linux 网络设备,你自己去内核里找"。

在我们的实验里 eth1 是 containerlab 建的 veth,在物理机上它可能是 enp3s0 这样的网卡,在 KVM 环境里是虚拟机的 tap 设备。对 OVS 来说没区别 —— 只要 ip link 里有,它就能接。

接进来之前,设备必须已经存在

ovs-vsctl add-port br0 eth9 这种设备不存在的情况,命令不会立刻报错,但 ovs-vsctl show 里会出现:

text
Port eth9
    Interface eth9
        error: "could not open network device eth9 (No such device)"

排查时看到 error: 就知道是这个问题。

internal 端口:让交换机自己有个地址 ​

bash
docker exec clab-port-types-ovs1 ovs-vsctl add-port br-int mgmt0 \
  -- set Interface mgmt0 type=internal
docker exec clab-port-types-ovs1 ip link set mgmt0 up
docker exec clab-port-types-ovs1 ip addr replace 192.168.10.254/24 dev mgmt0

internal 端口是 OVS 创建的一块虚拟网卡,一头在网桥里,一头在 Linux 协议栈上。所以:

bash
docker exec clab-port-types-ovs1 ip -br addr show mgmt0
text
mgmt0  UNKNOWN  192.168.10.254/24 fe80::fcf3:77ff:fed1:e806/64

它是个正经的 Linux 设备,能配 IP、能 ping、能跑服务。h1 现在可以 ping 它:

bash
docker exec clab-port-types-h1 ping -c 3 192.168.10.254
text
3 packets transmitted, 3 received, 0% packet loss

你其实已经见过它 ​

add-br 时自动出现的那个和网桥同名的端口,就是 internal 类型:

bash
docker exec clab-port-types-ovs1 ovs-vsctl --columns=name find Interface type=internal
text
name : br-int
name : br-ex
name : mgmt0

br-int 和 br-ex 是建网桥时自动带的,mgmt0 是我们手工加的。

用 br0 还是另建一个 internal 口

两种都能给交换机配地址。差别在于语义:

  • 直接给 br0 配 IP:简单,适合只需要一个管理地址的场景
  • 另建 mgmt0:可以建多个(配合 VLAN tag 做多个网关地址),职责更清楚

OVS 里的三层网关通常是后一种:给每个 VLAN 建一个 internal 口,打上 tag,配上网关地址。

patch 端口:两个网桥直连 ​

bash
N="docker exec clab-port-types-ovs1"

$N ovs-vsctl add-port br-int patch-to-ex \
  -- set Interface patch-to-ex type=patch options:peer=patch-to-int

$N ovs-vsctl add-port br-ex patch-to-int \
  -- set Interface patch-to-int type=patch options:peer=patch-to-ex

三个要点:

  1. 必须成对。只建一半,那一半永远是 down 的
  2. 互相指向对方的名字(options:peer)。写错名字不会报错,只是不通
  3. 必须用 -- 合成一个事务。分成两条命令的话,中间那一瞬间 patch-to-ex 是个普通端口,OVS 会去找一块叫这个名字的真网卡然后报错

建好之后:

bash
$N ovs-vsctl show
text
Bridge br-ex
    Port patch-to-int
        Interface patch-to-int
            type: patch
            options: {peer=patch-to-ex}
    Port eth2
        Interface eth2
    Port br-ex
        Interface br-ex
            type: internal
Bridge br-int
    Port patch-to-ex
        Interface patch-to-ex
            type: patch
            options: {peer=patch-to-int}
    Port br-int
        Interface br-int
            type: internal
    Port eth1
        Interface eth1
    Port mgmt0
        Interface mgmt0
            type: internal

h1 现在能穿过两个网桥 ping 到 h2:

bash
docker exec clab-port-types-h1 ping -c 3 192.168.10.2
text
3 packets transmitted, 3 received, 0% packet loss

patch 端口不是 Linux 设备 ​

这是它和 veth 的关键区别。看 ip link:

bash
$N ip -br link | grep -E "patch|mgmt0|br-"
text
br-int  UNKNOWN  2e:2c:50:96:3f:41 <BROADCAST,MULTICAST,UP,LOWER_UP>
br-ex   UNKNOWN  92:7e:71:87:12:4b <BROADCAST,MULTICAST,UP,LOWER_UP>
mgmt0   UNKNOWN  fe:f3:77:d1:e8:06 <BROADCAST,MULTICAST,UP,LOWER_UP>

没有 patch 端口。 三个 internal 口都在,patch 一个都没有。

再看 datapath:

bash
$N ovs-dpctl show
text
system@ovs-system:
  port 0: ovs-system (internal)
  port 1: br-int (internal)
  port 2: br-ex (internal)
  port 3: eth1
  port 4: eth2
  port 5: mgmt0 (internal)

datapath 里也没有 patch 端口。 但它在 OpenFlow 那边是有端口号的:

bash
$N ovs-ofctl dump-ports-desc br-int
text
 1(eth1)
 2(patch-to-ex)
 3(mgmt0)
 LOCAL(br-int)

所以 patch 是一个纯粹的 ovs-vswitchd 内部概念:在 OpenFlow 层面是个真实端口,可以被流表匹配和输出;但它不占 datapath 端口,也不是 Linux 设备。

证据:trace 看得最清楚 ​

bash
$N ovs-appctl ofproto/trace br-int \
  'in_port=1,dl_src=aa:c1:ab:77:7a:a2,dl_dst=aa:c1:ab:9e:83:3f'
text
bridge("br-int")
----------------
 0. priority 0
    NORMAL
     -> forwarding to learned port

bridge("br-ex")
---------------
 0. priority 0
    NORMAL
     -> forwarding to learned port

Final flow: unchanged
Datapath actions: 4

包在两个网桥里各走了一遍流表 —— bridge("br-int") 之后紧跟着 bridge("br-ex"),中间那一跳就是 patch。

但最后一行是 Datapath actions: 4(datapath 的 4 号口 = eth2),一步到位。ovs-vswitchd 在用户态把两个网桥的处理结果合并,最终只往内核缓存里装了一条"从 eth1 进来 → 从 eth2 出去"。

所以 patch 几乎不花性能

两个网桥的转发决策在慢路径里一次算完,装进内核的是合并后的结果。数据面上,穿两个网桥和穿一个网桥的开销是一样的。

放心拆网桥。 用 patch 把职责分开不会带来数据面代价。

patch 和 veth 该选哪个 ​

也可以用一对 veth 连两个网桥,效果看起来一样。但:

patchveth 对
是 Linux 设备否是
占 datapath 端口否是(两个)
数据面开销几乎为零(用户态合并)每个包真的过一遍内核网络栈
能 tcpdump 抓包不能能
能配 IP不能能

连接两个 OVS 网桥,一律用 patch。 只有在需要抓包调试,或者一端不是 OVS(比如要接进一个 Linux bridge 或某个 netns)时才用 veth。

三种端口的对照 ​

text
                    ┌─────────────────────────────────────────┐
   普通端口          │  eth1  (type="")                        │
   type=""          │   └─ 一块真实的 Linux 设备               │
                    │      veth / 物理网卡 / tap              │
                    │      在 ip link ✓   在 dpctl ✓          │
                    └─────────────────────────────────────────┘

                    ┌─────────────────────────────────────────┐
   internal 端口     │  mgmt0 (type=internal)                  │
                    │   └─ OVS 造出来的虚拟网卡                │
                    │      一头在网桥,一头在协议栈             │
                    │      能配 IP    在 ip link ✓  在 dpctl ✓ │
                    └─────────────────────────────────────────┘

                    ┌─────────────────────────────────────────┐
   patch 端口        │  patch-to-ex (type=patch)               │
                    │   └─ 纯 ovs-vswitchd 内部概念            │
                    │      必须成对,互指 peer                 │
                    │      在 ip link ✗   在 dpctl ✗          │
                    │      在 OpenFlow 端口列表 ✓             │
                    └─────────────────────────────────────────┘

自动检查 ​

bash
./verify.sh
text
  [通过] 存在 br-int
  [通过] 存在 br-ex
  [通过] patch-to-ex 类型是 patch
  [通过] patch-to-ex 的 peer 指向 patch-to-int
  [通过] mgmt0 类型是 internal
  [通过] h1 能穿过两个网桥 ping 通 h2
  [通过] h1 能 ping 通交换机自己的 internal 地址

自测清单 ​

  1. type="" 的端口,OVS 期望它在哪里存在?
  2. internal 端口和 add-br 自动生成的同名端口是什么关系?
  3. patch 端口为什么必须成对建,为什么必须用 --?
  4. patch 端口会出现在 ip link 里吗?ovs-dpctl show 里呢?dump-ports-desc 里呢?
  5. 包穿过两个用 patch 连接的网桥,datapath 里会执行几个动作?
  6. 什么时候该用 veth 而不是 patch?

常见问题 ​

patch 端口建好了但不通 ​

九成是 peer 名字写错。检查两边是否互指:

bash
$N ovs-vsctl get Interface patch-to-ex options:peer
$N ovs-vsctl get Interface patch-to-int options:peer

两条输出应该正好是对方的名字。

想抓 patch 端口上的包 ​

抓不了,它不是 Linux 设备。两个办法:

  • 用实验 12 的 mirror 功能把流量镜像到一个能抓的口
  • 临时把 patch 换成 veth 对

internal 端口配了 IP 但 ping 不通 ​

检查三件事:

bash
$N ip link set mgmt0 up                    # 接口是否 up
$N ip -br addr show mgmt0                  # 地址是否配上
$N ovs-appctl ofproto/trace br-int 'in_port=1,dl_dst=<mgmt0的MAC>'

trace 的结果应该是 Datapath actions: 指向 mgmt0 对应的 datapath 端口号。

端口显示 error: could not open network device ​

设备不存在。add-port 只是往数据库里写一条记录,OVS 之后才去内核里找这块设备。名字写错或者设备还没创建都会这样。

小结 ​

  • Interface.type 决定端口的种类,Open_vSwitch.iface_types 是完整清单
  • 普通端口(type="")对应一块真实的 Linux 设备,veth / 物理网卡 / tap 都算
  • internal 端口是 OVS 造的虚拟网卡,能配 IP,用来让交换机参与三层
  • patch 端口连接两个 OVS 网桥:必须成对、互指 peer、用 -- 一次建好
  • patch 不是 Linux 设备,也不占 datapath 端口,只在 OpenFlow 层面存在
  • 穿过 patch 的转发在慢路径里被合并,数据面开销几乎为零 —— 放心拆网桥
  • 连两个 OVS 网桥一律用 patch;需要抓包或对端不是 OVS 时才用 veth

下一章:实验 4:慢路径与快路径,把 upcall 的全过程和 megaflow 的生成规则彻底讲清楚。