主题
实验 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 mgmt0internal 端口是 OVS 创建的一块虚拟网卡,一头在网桥里,一头在 Linux 协议栈上。所以:
bash
docker exec clab-port-types-ovs1 ip -br addr show mgmt0text
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.254text
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=internaltext
name : br-int
name : br-ex
name : mgmt0br-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三个要点:
- 必须成对。只建一半,那一半永远是 down 的
- 互相指向对方的名字(
options:peer)。写错名字不会报错,只是不通 - 必须用
--合成一个事务。分成两条命令的话,中间那一瞬间patch-to-ex是个普通端口,OVS 会去找一块叫这个名字的真网卡然后报错
建好之后:
bash
$N ovs-vsctl showtext
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: internalh1 现在能穿过两个网桥 ping 到 h2:
bash
docker exec clab-port-types-h1 ping -c 3 192.168.10.2text
3 packets transmitted, 3 received, 0% packet losspatch 端口不是 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 showtext
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-inttext
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 连两个网桥,效果看起来一样。但:
| patch | veth 对 | |
|---|---|---|
| 是 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.shtext
[通过] 存在 br-int
[通过] 存在 br-ex
[通过] patch-to-ex 类型是 patch
[通过] patch-to-ex 的 peer 指向 patch-to-int
[通过] mgmt0 类型是 internal
[通过] h1 能穿过两个网桥 ping 通 h2
[通过] h1 能 ping 通交换机自己的 internal 地址自测清单
type=""的端口,OVS 期望它在哪里存在?internal端口和add-br自动生成的同名端口是什么关系?- patch 端口为什么必须成对建,为什么必须用
--? - patch 端口会出现在
ip link里吗?ovs-dpctl show里呢?dump-ports-desc里呢? - 包穿过两个用 patch 连接的网桥,datapath 里会执行几个动作?
- 什么时候该用 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 的生成规则彻底讲清楚。