
云计算数据中心里，一台物理服务器上可能同时运行数百个虚拟机或容器，分属不同租户。这些租户需要各自独立的 L2 网络，互相不可见——但物理网络的 VLAN 标签只有 12 位，最多 4096 个网络，远远不够用。Overlay 网络因此而生：把虚拟的 L2 帧封装在物理网络的 UDP/IP 包里传输，让虚拟网络的数量不再受物理硬件限制。

GENEVE 是目前最新、最具扩展性的 Overlay 封装协议，被 OVN、AWS Nitro、Cilium 等主流项目采用。本文从它诞生的历史背景讲起，逐层剖析其协议细节，最后在单台机器上做完整实验。

---

## 1. 历史背景：封装协议的碎片化

在 GENEVE 之前，业界已经有多种 Overlay 封装协议，但各自为政，互不兼容：

| 协议   | 提出者                  | RFC      | 特点                        |
| :----- | :---------------------- | :------- | :-------------------------- |
| NVGRE  | Microsoft               | RFC 7637 | GRE 封装，Key 字段做租户 ID |
| STT    | Nicira (VMware)         | 草案     | TCP-like 封装，利用硬件 TSO |
| VXLAN  | VMware/Cisco            | RFC 7348 | UDP 封装，24 位 VNI，最流行 |
| GENEVE | VMware/MS/Red Hat/Intel | RFC 8926 | UDP 封装，可变长 Options    |

VXLAN 解决了 VLAN 数量不足的问题，但它的 8 字节固定头部没有扩展空间——一旦需要携带额外的元数据（如安全策略 ID、服务链标签、QoS 标记），就只能在内层帧外再套一层封装，或者依赖带外信道。

不同 SDN 控制器（NSX、OpenStack OVN、AWS 等）为了传递各自需要的元数据，纷纷自定义私有格式，导致设备互通困难，硬件卸载芯片无法统一支持。

GENEVE（Generic Network Virtualization Encapsulation）正是为了终结这种碎片化而设计：提供一个统一的、可扩展的封装框架，让各方在同一个协议头部里携带任意元数据，同时对硬件解析友好。

---

## 2. GENEVE 协议详解

### 完整数据包结构

一个携带以太网内层帧的 GENEVE 数据包，从外到内的层次如下：

```log
┌──────────────────────────────────────┐
│  外层以太网头  (14 字节)              │  物理网络寻址
├──────────────────────────────────────┤
│  外层 IP 头    (20/40 字节)          │  VTEP 间路由
├──────────────────────────────────────┤
│  外层 UDP 头   (8 字节, 目的端口 6081)│  多路复用 + ECMP 哈希
├──────────────────────────────────────┤
│  GENEVE 头     (8 字节固定 + Options) │  隧道标识 + 元数据
├──────────────────────────────────────┤
│  内层以太网帧  (≥14 字节)            │  租户虚拟网络
├──────────────────────────────────────┤
│  内层 IP / TCP / 载荷                │
└──────────────────────────────────────┘
```

外层 UDP 使用目的端口 **6081**（VXLAN 是 4789），源端口由发送方基于内层五元组哈希生成，确保 ECMP 等价多路径负载均衡能正确分流。

### GENEVE 固定头部（8 字节）

```log
 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver|  Opt Len  |O|C|   Rsvd.   |         Protocol Type         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Virtual Network Identifier (VNI)        |    Reserved   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

| 字段          | 位宽 | 说明                                                                                |
| :------------ | ---: | :---------------------------------------------------------------------------------- |
| Ver           |    2 | 协议版本，当前固定为 0                                                              |
| Opt Len       |    6 | Options 长度，单位为 4 字节，取值 0–63，即最多 252 字节 Options                     |
| O             |    1 | OAM 控制包标志，置 1 时内层帧为管理报文而非数据                                     |
| C             |    1 | Critical Options 标志，置 1 时接收方必须理解所有 Critical 选项，否则丢弃            |
| Rsvd          |    6 | 保留，发送方置 0                                                                    |
| Protocol Type |   16 | 内层协议类型：`0x6558` = 透明以太网桥接（最常用），`0x0800` = IPv4，`0x86DD` = IPv6 |
| VNI           |   24 | Virtual Network Identifier，24 位 = 1677 万个虚拟网络                               |
| Reserved      |    8 | 保留，置 0                                                                          |

### TLV Options（0–252 字节）

这是 GENEVE 相对 VXLAN 最核心的差异点。Options 区域由一到多个 TLV（Type-Length-Value）条目组成：

```log
 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Option Class          |      Type     |R|R|R|  Length  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Option Data (变长)                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

| 字段         | 位宽 | 说明                                                                                              |
| :----------- | ---: | :------------------------------------------------------------------------------------------------ |
| Option Class |   16 | 定义选项的组织，类似以太网 OUI。`0x0100` = Linux 内核，`0x0101` = Open vSwitch，`0xFFFE` = 实验用 |
| Type         |    8 | 在该 Class 内的选项编号，最高位为 1 时表示 Critical                                               |
| R            |    3 | 保留                                                                                              |
| Length       |    5 | Option Data 的长度，单位为 4 字节，最多 124 字节                                                  |
| Option Data  | 变长 | 实际携带的元数据内容                                                                              |

**硬件友好性**：不认识某个 Option Class/Type 的设备可以根据 Length 跳过该选项（除非 Critical 位为 1），而不需要解析选项内容。这使得新版本的选项格式不会导致旧设备彻底无法处理数据包，只是跳过未知部分。

### OVN 如何使用 GENEVE Options

OVN（Open Virtual Network）是使用 GENEVE 元数据最典型的案例。它在 Options 中携带两个关键信息：

- **Logical Datapath**：标识数据包当前所在的逻辑网络（相当于租户 VPC）
- **Logical Port**：标识数据包的来源或目的逻辑端口

这让 Hypervisor 上的 OVS 在收到隧道包时，不需要查外部数据库，直接从 GENEVE Options 里读出包的逻辑身份，决定转发策略。这是 GENEVE Options 让 SDN 控制面简化的典型体现。

---

## 3. GENEVE 相对 VXLAN 的核心优势

VXLAN 的 8 字节固定头部里，除了 24 位 VNI，只有一个 I 标志位（表示 VNI 有效），没有任何扩展空间。需要携带元数据时只能走以下绕路方案：

- 在内层帧外再套一层封装（增加头部开销）
- 把元数据编码进 VNI 本身（牺牲 VNI 空间，且语义不清）
- 依赖带外信道（控制平面额外通信）

GENEVE 通过 TLV Options 机制，将这些需求内化到协议本身：

| 能力              |           VXLAN           |         GENEVE         |
| :---------------- | :-----------------------: | :--------------------: |
| VNI 位宽          |           24 位           |         24 位          |
| 最大虚拟网络数    |          1677 万          |        1677 万         |
| 携带元数据        |          不支持           |   最多 252 字节 TLV    |
| 头部长度          |        固定 8 字节        |    8 字节 + Options    |
| 硬件跳过未知选项  |            N/A            |          支持          |
| Critical 选项标记 |          不支持           |          支持          |
| UDP 目的端口      |           4789            |          6081          |
| 主要使用者        | Kubernetes Flannel/Calico | OVN、AWS Nitro、Cilium |

---

## 4. 主流项目中的 GENEVE

**OVN**：默认隧道协议就是 GENEVE，所有 Hypervisor 之间的流量都走 GENEVE 封装，并在 Options 中携带逻辑数据路径和端口信息。

**AWS Nitro**：AWS 的 Nitro 卡（Smart NIC）使用 GENEVE 封装实例之间的网络流量，并通过 Options 传递安全组策略标签，实现硬件卸载时的策略执行。

**Cilium**：CNI 插件支持 GENEVE 模式（`--tunnel=geneve`），作为 VXLAN 的替代，在需要携带 eBPF 元数据时更有优势。

**Open vSwitch（OVS）**：OVS 2.5+ 支持 GENEVE，可以通过 `ovs-vsctl` 创建 GENEVE 类型的 Port，并读写 Options。

---

## 5. 实验：单台物理机验证 VNI 隔离

用两个 network namespace 模拟两台物理主机，通过 veth pair + Linux bridge 构建 underlay，在上面配置两套 GENEVE 隧道（VNI 100 和 VNI 200）。两套隧道使用完全相同的 overlay IP，用 VRF 让内核路由表按 VNI 查各自的路由，演示不同租户 IP 地址空间完全隔离。

### 拓扑

```mermaid
flowchart TB
    subgraph H1["ns-h1（模拟主机 1，underlay: 192.168.100.1）"]
        direction TB
        V100_H1["vrf100\n路由表 100"]
        V200_H1["vrf200\n路由表 200"]
        G0_H1["geneve0\nVNI=100\n10.0.0.1/24"]
        G1_H1["geneve1\nVNI=200\n10.0.0.1/24"]
        V100_H1 --> G0_H1
        V200_H1 --> G1_H1
    end

    BR["br-underlay\nLinux bridge（模拟物理交换机）\nunderlay: 192.168.100.0/24"]

    subgraph H2["ns-h2（模拟主机 2，underlay: 192.168.100.2）"]
        direction TB
        V100_H2["vrf100\n路由表 100"]
        V200_H2["vrf200\n路由表 200"]
        G0_H2["geneve0\nVNI=100\n10.0.0.2/24"]
        G1_H2["geneve1\nVNI=200\n10.0.0.2/24"]
        V100_H2 --> G0_H2
        V200_H2 --> G1_H2
    end

    G0_H1 -- "GENEVE VNI=100\nUDP 6081" --- BR
    G1_H1 -- "GENEVE VNI=200\nUDP 6081" --- BR
    BR -- "GENEVE VNI=100\nUDP 6081" --- G0_H2
    BR -- "GENEVE VNI=200\nUDP 6081" --- G1_H2

    style G0_H1 fill:#4a90d9,color:#fff
    style G0_H2 fill:#4a90d9,color:#fff
    style G1_H1 fill:#e67e22,color:#fff
    style G1_H2 fill:#e67e22,color:#fff
    style BR fill:#27ae60,color:#fff
```

### 搭建 Underlay 网络

```bash
# 创建两个 namespace，分别模拟两台物理主机
ip netns add ns-h1
ip netns add ns-h2

# 创建两对 veth，每对一端留在根 namespace，另一端放入对应的 namespace
# veth1 <-> veth1-br：连接 ns-h1 和 bridge
# veth2 <-> veth2-br：连接 ns-h2 和 bridge
ip link add veth1 type veth peer name veth1-br
ip link add veth2 type veth peer name veth2-br

# 将 veth1/veth2 移入各自的 namespace（模拟主机的物理网卡）
ip link set veth1 netns ns-h1
ip link set veth2 netns ns-h2

# 创建 Linux bridge，模拟连接两台主机的物理交换机
ip link add br-underlay type bridge

# 将根 namespace 侧的 veth 端口接入 bridge
ip link set veth1-br master br-underlay
ip link set veth2-br master br-underlay

# 启动 bridge 及其两个端口
ip link set veth1-br up
ip link set veth2-br up
ip link set br-underlay up

# 启动 ns-h1 内的回环和网卡，并配置 underlay IP
ip netns exec ns-h1 ip link set lo up
ip netns exec ns-h1 ip link set veth1 up
ip netns exec ns-h1 ip addr add 192.168.100.1/24 dev veth1

# 启动 ns-h2 内的回环和网卡，并配置 underlay IP
ip netns exec ns-h2 ip link set lo up
ip netns exec ns-h2 ip link set veth2 up
ip netns exec ns-h2 ip addr add 192.168.100.2/24 dev veth2

# 验证 underlay 连通（两台"主机"能互相 ping 通）
ip netns exec ns-h1 ping -c2 192.168.100.2
```

### 配置 VRF + GENEVE

在 ns-h1 和 ns-h2 上各建两个 VRF，每个 VRF 绑定一张独立路由表，再将对应 VNI 的 GENEVE 接口加入该 VRF：

```bash
# ── ns-h1 ────────────────────────────────────────────────────

# 创建 VRF vrf100，使用路由表 100（隔离 VNI 100 租户的路由）
ip netns exec ns-h1 ip link add vrf100 type vrf table 100
# 创建 VRF vrf200，使用路由表 200（隔离 VNI 200 租户的路由）
ip netns exec ns-h1 ip link add vrf200 type vrf table 200
# 启动两个 VRF 接口
ip netns exec ns-h1 ip link set vrf100 up
ip netns exec ns-h1 ip link set vrf200 up

# 创建 GENEVE 隧道接口 geneve0：VNI=100，对端 underlay IP 为 ns-h2（192.168.100.2）
ip netns exec ns-h1 ip link add geneve0 type geneve id 100 remote 192.168.100.2
# 将 geneve0 加入 vrf100，此后 geneve0 的路由只在路由表 100 中查找
ip netns exec ns-h1 ip link set geneve0 master vrf100
# 分配 VNI 100 租户的 overlay IP（10.0.0.1/24）
ip netns exec ns-h1 ip addr add 10.0.0.1/24 dev geneve0
ip netns exec ns-h1 ip link set geneve0 up

# 创建 GENEVE 隧道接口 geneve1：VNI=200，同样指向 ns-h2
ip netns exec ns-h1 ip link add geneve1 type geneve id 200 remote 192.168.100.2
# 将 geneve1 加入 vrf200，路由完全独立于 vrf100
ip netns exec ns-h1 ip link set geneve1 master vrf200
# 分配与 VNI 100 完全相同的 overlay IP（10.0.0.1/24），VRF 保证不冲突
ip netns exec ns-h1 ip addr add 10.0.0.1/24 dev geneve1
ip netns exec ns-h1 ip link set geneve1 up

# ── ns-h2（对称配置）────────────────────────────────────────

ip netns exec ns-h2 ip link add vrf100 type vrf table 100
ip netns exec ns-h2 ip link add vrf200 type vrf table 200
ip netns exec ns-h2 ip link set vrf100 up
ip netns exec ns-h2 ip link set vrf200 up

# VNI=100 隧道，对端 underlay IP 为 ns-h1（192.168.100.1）
ip netns exec ns-h2 ip link add geneve0 type geneve id 100 remote 192.168.100.1
ip netns exec ns-h2 ip link set geneve0 master vrf100
ip netns exec ns-h2 ip addr add 10.0.0.2/24 dev geneve0
ip netns exec ns-h2 ip link set geneve0 up

# VNI=200 隧道，同样指向 ns-h1
ip netns exec ns-h2 ip link add geneve1 type geneve id 200 remote 192.168.100.1
ip netns exec ns-h2 ip link set geneve1 master vrf200
ip netns exec ns-h2 ip addr add 10.0.0.2/24 dev geneve1
ip netns exec ns-h2 ip link set geneve1 up
```

### 验证隔离

`ip vrf exec <vrf名>` 在指定 VRF 的路由上下文中执行命令，相当于切换到对应租户视角：

```bash
# 以 VNI 100 租户身份 ping 10.0.0.2：查 vrf100 路由表 → 走 geneve0（VNI=100）
ip netns exec ns-h1 ip vrf exec vrf100 ping -c3 10.0.0.2 &

# 以 VNI 200 租户身份 ping 同一个 10.0.0.2：查 vrf200 路由表 → 走 geneve1（VNI=200）
ip netns exec ns-h1 ip vrf exec vrf200 ping -c3 10.0.0.2 &
```

同时在另一个终端抓 underlay 接口上的 GENEVE 包：

```bash
# 抓 bridge 端口上的 UDP 6081 流量，过滤出 Geneve 行和内层 IP 行
tcpdump -i veth1-br -nn udp port 6081 -v 2>/dev/null | grep -E "Geneve|10\.0\.0"
```

实际输出：

```bash
tcpdump -i veth1-br -nn udp port 6081 -v 2>/dev/null | grep -E "Geneve|10\.0\.0"
    192.168.100.2.51913 > 192.168.100.1.6081: Geneve, Flags [none], vni 0xc8
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0xc8
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28083, seq 1, length 64
    192.168.100.2.6677 > 192.168.100.1.6081: Geneve, Flags [none], vni 0xc8
    10.0.0.2 > 10.0.0.1: ICMP echo reply, id 28083, seq 1, length 64
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0x64
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28082, seq 1, length 64
    192.168.100.2.6677 > 192.168.100.1.6081: Geneve, Flags [none], vni 0x64
    10.0.0.2 > 10.0.0.1: ICMP echo reply, id 28082, seq 1, length 64
    192.168.100.1.61607 > 192.168.100.2.6081: Geneve, Flags [none], vni 0x64
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0xc8
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28083, seq 2, length 64
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0x64
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28082, seq 2, length 64
    192.168.100.2.6677 > 192.168.100.1.6081: Geneve, Flags [none], vni 0xc8
    10.0.0.2 > 10.0.0.1: ICMP echo reply, id 28083, seq 2, length 64
    192.168.100.2.6677 > 192.168.100.1.6081: Geneve, Flags [none], vni 0x64
    10.0.0.2 > 10.0.0.1: ICMP echo reply, id 28082, seq 2, length 64
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0xc8
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28083, seq 3, length 64
    192.168.100.1.6677 > 192.168.100.2.6081: Geneve, Flags [none], vni 0x64
    10.0.0.1 > 10.0.0.2: ICMP echo request, id 28082, seq 3, length 64
```

三点可以直接从输出读出：

- `vni 0x64`（十进制 100）和 `vni 0xc8`（十进制 200）交替出现，两条流量并行跑在各自的隧道里，互不干扰。
- 两个 VNI 的内层 IP 完全相同（10.0.0.1 → 10.0.0.2），证明不同租户可以复用同一套地址空间。
- `id 28082` 和 `id 28083` 是两条 ping 各自的 ICMP 标识符，接收端按 VNI 分别解封装到 `geneve0`（vrf100）或 `geneve1`（vrf200），不会混淆。

### 清理实验环境

```bash
# 删除 namespace 时，其中所有接口（含 VRF 和 GENEVE 接口）自动销毁
ip netns del ns-h1
ip netns del ns-h2

# 删除 bridge（根 namespace 侧的 veth 端口随 bridge 一起消失）
ip link del br-underlay
```

---

## 7. 总结

GENEVE 解决的核心问题是：**如何在一个统一的 Overlay 封装协议里，既做到 24 位 VNI 的大规模租户隔离，又能携带任意扩展元数据，同时对硬件解析友好**。

它没有在 VXLAN 上打补丁，而是从头设计了可变长 TLV Options 机制——定义了一个"元数据总线"，让不同的 SDN 系统（OVN、AWS、Cilium 等）各自在 Option Class 命名空间下写入自己需要的元数据，而其他不认识这些选项的设备可以安全跳过。

从实验可以直观看到：两套 GENEVE 隧道使用完全相同的 overlay IP，各自走各自的 VNI 封装，在 underlay 上共用同一条 UDP 通道，在接收端由内核按 VNI 值分发到对应接口——这正是云计算多租户网络隔离的数据平面基础。

