Skip to content

实验 9:VLAN ​

前面八章里,一个网桥上的所有端口都在同一个二层广播域里。这一章把它切开。

VLAN 解决的是:同一台交换机上,让不同的端口组互相看不见。

起环境 ​

bash
cd labs/learn-ovs/07-vlan
./start.sh

四台主机接在同一个网桥上:

text
   h1 .1 ─┐
   h2 .2 ─┤
   h3 .3 ─┼── ovs1 br0        四台都在 192.168.10.0/24
   h4 .4 ─┘

注意 IP 全在同一个网段

这是刻意的。很多人以为"隔离"就是划分不同网段 —— 那是三层隔离,靠路由。

VLAN 是二层隔离:地址在同一网段,但广播域被切开,包压根到不了对面。这一点区分清楚,后面才不会混。

先确认现在是通的:

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

看一眼 MAC 表:

bash
docker exec clab-vlan-ovs1 ovs-appctl fdb/show br0
text
 port  VLAN  MAC                Age
LOCAL     0  3a:d0:20:0e:93:4b    1
    1     0  aa:c1:ab:f7:40:0f    1
    3     0  aa:c1:ab:5d:d8:be    1
    2     0  aa:c1:ab:2f:34:a5    1
    4     0  aa:c1:ab:11:12:d7    1

VLAN 那一列全是 0 —— 没有 VLAN,所有 MAC 在同一个学习域里。这一列后面会变。

802.1Q 标签长什么样 ​

VLAN 的实现方式是在以太网帧里插一个 4 字节的标签:

text
普通以太网帧
┌──────────┬──────────┬────────┬─────────────────┐
│ 目的 MAC  │ 源 MAC    │ 类型    │ 数据             │
│  6 字节   │  6 字节   │ 2 字节  │                 │
└──────────┴──────────┴────────┴─────────────────┘

带 802.1Q 标签的帧
┌──────────┬──────────┬─────────────────┬────────┬──────────────┐
│ 目的 MAC  │ 源 MAC    │  802.1Q 标签     │ 类型    │ 数据          │
│  6 字节   │  6 字节   │    4 字节        │ 2 字节  │              │
└──────────┴──────────┴────────┬────────┴────────┴──────────────┘
                               │
              ┌────────────────┴─────────────────┐
              │ TPID   │ PCP │ DEI │    VID      │
              │ 0x8100 │ 3位 │ 1位 │   12 位     │
              │ 2 字节 │        2 字节            │
              └──────────────────────────────────┘

  TPID = 0x8100   固定值,标识"这是个 VLAN 标签"
  PCP  = 优先级    0-7,QoS 用
  DEI  = 丢弃位    早期叫 CFI
  VID  = VLAN ID  12 位 → 1..4094(0 和 4095 保留)

VLAN ID 只有 12 位,所以最多 4094 个。 这个限制是第 4 部分要讲 VXLAN 的直接原因 —— 云环境里几万个租户根本不够分。

Access 口和 Trunk 口 ​

这是 VLAN 里唯一需要真正理解的概念。

text
                     ┌─────────────────────────────┐
   h1 ──裸帧────────▶│ access 口 (tag=100)          │
      ◀───裸帧───────│  进:打上 VLAN 100 标签       │
                     │  出:摘掉标签                 │
                     │                             │
                     │        交换机内部             │
                     │    所有帧都带着 VLAN 标签      │
                     │                             │
   sw ◀──带标签的帧──▶│ trunk 口 (trunks=[100,200])  │
                     │  进出都保留标签               │
                     └─────────────────────────────┘
access 口trunk 口
配置字段tag=100trunks=[100,200]
接什么主机、虚拟机另一台交换机、需要多 VLAN 的服务器
帧格式裸帧(不带标签)带 802.1Q 标签
对端知道 VLAN 吗不知道知道
承载几个 VLAN1 个多个

核心:access 口负责加标签和摘标签,主机完全不知道 VLAN 存在。

配 access 口 ​

bash
N="docker exec clab-vlan-ovs1"

$N ovs-vsctl set Port eth1 tag=100
$N ovs-vsctl set Port eth2 tag=100
$N ovs-vsctl set Port eth3 tag=200
$N ovs-vsctl set Port eth4 tag=200

就这四条。注意是 set Port,不是 set Interface —— 实验 2 讲过,VLAN 属性在 Port 层。

bash
$N ovs-vsctl show
text
Bridge br0
    Port eth1
        tag: 100
        Interface eth1
    Port eth2
        tag: 100
        Interface eth2
    Port eth3
        tag: 200
        Interface eth3
    Port eth4
        tag: 200
        Interface eth4
    Port br0
        Interface br0
            type: internal

验证隔离 ​

bash
docker exec clab-vlan-h1 ping -c 3 192.168.10.2      # 同 VLAN
docker exec clab-vlan-h3 ping -c 3 192.168.10.4      # 同 VLAN
docker exec clab-vlan-h1 ping -c 2 -W 1 192.168.10.3 # 跨 VLAN
text
h1 → h2:  3 packets transmitted, 3 received, 0% packet loss
h3 → h4:  3 packets transmitted, 3 received, 0% packet loss
h1 → h3:  2 packets transmitted, 0 received, 100% packet loss

同组通,跨组不通。 而且没动过任何 IP 配置。

MAC 表现在分域了 ​

bash
$N ovs-appctl fdb/show br0
text
 port  VLAN  MAC                Age
LOCAL     0  3a:d0:20:0e:93:4b   13
    1   100  aa:c1:ab:f7:40:0f    1
    3   200  aa:c1:ab:5d:d8:be    1
    4   200  aa:c1:ab:11:12:d7    0
    2   100  aa:c1:ab:2f:34:a5    0

VLAN 列有值了。MAC 学习是按 VLAN 分开的 —— (VLAN, MAC) 才是一条记录的键。同一个 MAC 出现在两个 VLAN 里是完全合法的两条记录。

隔离是怎么生效的 ​

同 VLAN 的 trace:

bash
$N ovs-appctl ofproto/trace br0 \
  "in_port=1,dl_src=aa:c1:ab:f7:40:0f,dl_dst=aa:c1:ab:2f:34:a5"
text
 0. priority 0
    NORMAL
     -> forwarding to learned port

Datapath actions: 3

正常单播转发。

跨 VLAN 的 trace —— 这个才有意思:

bash
$N ovs-appctl ofproto/trace br0 \
  "in_port=1,dl_src=aa:c1:ab:f7:40:0f,dl_dst=aa:c1:ab:5d:d8:be"
text
 0. priority 0
    NORMAL
     -> no learned MAC for destination, flooding

Datapath actions: push_vlan(vid=100,pcp=0),1,pop_vlan,3

两个关键点:

一、no learned MAC for destination。 h3 的 MAC 明明在表里,但它属于 VLAN 200。从 VLAN 100 的视角看,这个地址不存在 —— 因为查表用的键是 (100, aa:c1:ab:5d:d8:be),而表里只有 (200, aa:c1:ab:5d:d8:be)。

二、flooding 只在 VLAN 100 内。 看 datapath 动作:push_vlan(vid=100) 然后发到 1 号口(br0 internal,带标签),再 pop_vlan 发到 3 号口(eth2,即 h2,摘标签)。

eth3 和 eth4 一个都没有。 泛洪被限制在 VLAN 100 的成员里,这就是隔离的实现方式。

泛洪范围就是 VLAN 的本质

VLAN 隔离不是靠"检查后丢弃",而是靠把广播域切小:查表按 VLAN 分域,泛洪只发给同 VLAN 的口。跨 VLAN 的包不是被拦下的,而是压根没有通往对面的路径。

配 trunk 口 ​

让 h4 同时访问两个 VLAN。先把它改成 trunk:

bash
$N ovs-vsctl clear Port eth4 tag
$N ovs-vsctl set Port eth4 trunks=100,200
text
Port eth4
    trunks: [100, 200]
    Interface eth4

tag 和 trunks 不要同时设

同时设了 tag 和 trunks,OVS 的行为取决于 vlan_mode,容易配出意外结果。

改成 trunk 前先 clear Port eth4 tag。

trunk 口收发的是带标签的帧,所以 h4 那边也得会处理标签 —— 建 VLAN 子接口:

bash
docker exec clab-vlan-h4 ip addr flush dev eth1
docker exec clab-vlan-h4 sh -c "
  ip link add link eth1 name eth1.100 type vlan id 100
  ip link add link eth1 name eth1.200 type vlan id 200
  ip link set eth1.100 up
  ip link set eth1.200 up
  ip addr add 192.168.10.104/24 dev eth1.100
  ip addr add 192.168.10.204/24 dev eth1.200"

现在 h4 一个网卡进了两个 VLAN:

bash
docker exec clab-vlan-h4 ping -c 2 -I eth1.100 192.168.10.1   # → h1,VLAN 100
docker exec clab-vlan-h4 ping -c 2 -I eth1.200 192.168.10.3   # → h3,VLAN 200
text
VLAN 100 → h1:  2 packets transmitted, 2 received, 0% packet loss
VLAN 200 → h3:  2 packets transmitted, 2 received, 0% packet loss

同一块物理网卡,同时属于两个互相隔离的广播域。 这正是虚拟化宿主机上联口的标准做法:一条线承载所有租户的 VLAN。

vlan_mode 的其它取值 ​

Port.vlan_mode 控制端口怎么处理标签:

vlan_mode行为
access只收发裸帧,进来打 tag,出去摘掉。带标签的帧丢弃
trunk只收发带标签的帧,裸帧丢弃
native-taggedtrunk,但裸帧按 tag 处理;出去时 native VLAN 的帧带标签
native-untaggedtrunk,但裸帧按 tag 处理;出去时 native VLAN 的帧不带标签
dot1q-tunnelQinQ,再套一层外层标签

不显式设 vlan_mode 时,OVS 按你配了 tag 还是 trunks 自动推断,绝大多数场景够用。

native-untagged 用于"一条线上既有一个不打标签的管理 VLAN,又有若干打标签的业务 VLAN":

bash
ovs-vsctl set Port eth4 vlan_mode=native-untagged tag=1 trunks=100,200

让 VLAN 之间能通:三层网关 ​

VLAN 是二层隔离,要互通必须过三层。OVS 里的做法是给每个 VLAN 建一个 internal 口当网关:

bash
# VLAN 100 的网关
ovs-vsctl add-port br0 gw100 tag=100 -- set Interface gw100 type=internal
ip link set gw100 up
ip addr add 10.0.100.1/24 dev gw100

# VLAN 200 的网关
ovs-vsctl add-port br0 gw200 tag=200 -- set Interface gw200 type=internal
ip link set gw200 up
ip addr add 10.0.200.1/24 dev gw200

# 打开转发
sysctl -w net.ipv4.ip_forward=1

这时候主机要改成不同网段(10.0.100.x 和 10.0.200.x),把网关指到对应的 internal 口地址。转发由 Linux 内核的路由完成,不是 OVS。

这就是 internal 端口的主要用途

实验 3 说 internal 口"让交换机自己有个三层地址"。给每个 VLAN 建一个带 tag 的 internal 口,就是 OVS 做 VLAN 网关的标准姿势。

自动检查 ​

bash
./verify.sh
text
  [通过] eth1 属于 VLAN 100
  [通过] eth3 属于 VLAN 200
  [通过] 同 VLAN:h1 能 ping 通 h2
  [通过] 同 VLAN:h3 能 ping 通 h4
  [通过] 跨 VLAN:h1 不能 ping 通 h3
  [通过] 跨 VLAN:h2 不能 ping 通 h4

自测清单 ​

  1. VLAN 隔离和"划分不同网段"有什么区别?
  2. 802.1Q 标签有几个字节?VLAN ID 几位?最多几个 VLAN?
  3. access 口和 trunk 口分别接什么?主机知道自己在哪个 VLAN 吗?
  4. tag 配在 Port 表还是 Interface 表?
  5. MAC 表加了 VLAN 列以后,一条记录的键是什么?
  6. 跨 VLAN 的包是被"拦下"的,还是"没有路径"?
  7. 一块物理网卡怎么同时进两个 VLAN?

常见问题 ​

配了 tag 之后连同 VLAN 都不通 ​

先看 MAC 表的 VLAN 列有没有值:

bash
ovs-appctl fdb/show br0

如果还是 0,说明 tag 没生效 —— 检查是不是写成了 set Interface(应该是 set Port)。

再清一下 MAC 表,旧的无 VLAN 记录会干扰:

bash
ovs-appctl fdb/flush br0

trunk 口不通 ​

trunk 口两端都必须处理标签。检查:

  • 对端是不是也配成了 trunk(或者建了 VLAN 子接口)
  • trunks 列表里有没有包含要用的 VLAN ID
  • 有没有残留的 tag 字段(先 clear Port <口> tag)

想看带标签的帧 ​

在 trunk 口对应的设备上抓包:

bash
docker exec clab-vlan-h4 tcpdump -i eth1 -e -n vlan

-e 显示链路层头,才能看到 vlan 100 字样。

VLAN 1 有什么特殊 ​

OVS 里 tag=1 没有特殊含义,但很多物理交换机把 VLAN 1 当默认/管理 VLAN。跟物理设备对接时避开 VLAN 1 比较稳妥。

小结 ​

  • VLAN 是二层隔离:地址可以同网段,但广播域被切开
  • 802.1Q 标签 4 字节,VID 12 位 → 最多 4094 个 VLAN,这是 VXLAN 存在的理由
  • access 口(tag=N)收发裸帧,进打标签出摘标签,主机不知道 VLAN
  • trunk 口(trunks=[...])收发带标签的帧,接交换机或多 VLAN 服务器
  • VLAN 属性配在 Port 表,不是 Interface
  • MAC 学习按 VLAN 分域,键是 (VLAN, MAC)
  • 隔离的实现是把泛洪范围限制在同 VLAN 成员内,不是检查后丢弃
  • VLAN 间互通要过三层,标准做法是给每个 VLAN 建一个带 tag 的 internal 口做网关

下一章:实验 10:用流表手写 VLAN,把这一章 ovs-vsctl 一行搞定的事,用 OpenFlow 从零实现一遍。