
在上一篇文章里我们看到，GENEVE 可以在两台主机之间建立隧道，用 VNI 区分租户。但谁来决定"哪个包走哪条隧道"、"哪个虚拟机能和哪个虚拟机通信"？这就是 OVS 和 OVN 要解决的问题。

OVS（Open vSwitch）是一套可编程的虚拟交换机实现，提供数据平面；在 Linux 上最常见的快速路径是内核 datapath，也支持纯 userspace datapath。OVN（Open Virtual Network）是构建在 OVS 之上的网络虚拟化系统，提供逻辑交换机、逻辑路由器、ACL 等高层抽象，把"网络意图"翻译成 OVS 能执行的 OpenFlow 规则。

---

## 1. OVS：可编程的虚拟交换机

### 架构

OVS 由三个组件构成：

```mermaid
flowchart TB
    subgraph User["用户态"]
        VSCTL["ovs-vsctl\n配置管理 CLI"]
        OFCTL["ovs-ofctl\nOpenFlow CLI"]
        OVSDB["ovsdb-server\n配置数据库"]
        VSWITCHD["ovs-vswitchd\n主守护进程"]
        VSCTL --> OVSDB
        OFCTL --> VSWITCHD
        OVSDB <--> VSWITCHD
    end
    subgraph Kernel["内核态"]
        DP["openvswitch.ko\n快速转发路径（datapath）"]
    end
    VSWITCHD <-->|"upcall / flow install"| DP

    style OVSDB fill:#4a90d9,color:#fff
    style VSWITCHD fill:#4a90d9,color:#fff
    style DP fill:#27ae60,color:#fff
```

- `ovsdb-server`：存储 OVS 的配置（网桥、端口、接口），使用 OVSDB 协议，`ovs-vsctl` 通过它读写配置。
- `ovs-vswitchd`：核心守护进程，读取 OVSDB 配置，维护 OpenFlow 流表，并将可缓存的转发结果同步到 datapath；当 datapath 无法匹配时，也负责处理 upcall。
- `openvswitch.ko`：Linux 内核 datapath 模块，实现最常见的快速路径。已知流的包直接在 datapath 中转发，未知流的包上报给 `ovs-vswitchd` 处理。

### 核心概念

| 概念       | 说明                                                                                      |
| :--------- | :---------------------------------------------------------------------------------------- |
| Bridge     | 虚拟交换机实例，类似物理交换机。所有端口都挂在 bridge 下                                  |
| Port       | Bridge 上的一个逻辑端口，可以是物理网卡、veth、隧道接口等                                 |
| Interface  | Port 下的实际网络接口；最常见是一对一，但 bond 等场景下一个 Port 也可以包含多个 Interface |
| Flow Table | OpenFlow 流表，定义"匹配条件 → 动作"规则，是 OVS 的核心                                   |
| Controller | 外部 SDN 控制器（如 OVN），通过 OpenFlow 协议向 OVS 下发流规则                            |

### 常用命令

```bash
# 查看所有 bridge 及其端口
ovs-vsctl show

# 创建一个 bridge
ovs-vsctl add-br br0

# 添加物理网卡到 bridge
ovs-vsctl add-port br0 eth0

# 查看流表（所有 table）
ovs-ofctl dump-flows br-int

# 查看某张流表
ovs-ofctl dump-flows br-int table=0

# 查看 datapath 中已缓存的流（内核快速路径，可能包含多个 bridge）
ovs-dpctl dump-flows

# 按 bridge 查看 datapath 流
ovs-appctl dpif/dump-flows br-int

# 查看 OVS 版本信息
ovs-vsctl --version
ovs-appctl version
```

---

## 2. OVN：网络虚拟化的逻辑层

直接用 OVS 管理虚拟网络有明显局限：OpenFlow 规则是"如何转发"的底层指令，而不是"我要一个能互通的二层网络"这样的意图表达。有几十台主机、几百个虚拟机时，手写和维护 OpenFlow 规则极其复杂。

OVN 在 OVS 之上增加了一层抽象：用户只需声明逻辑拓扑（哪些端口在同一个逻辑交换机上、路由器怎么连接），OVN 自动将其翻译成每台 hypervisor 上的 OpenFlow 规则，并通过 GENEVE 隧道实现跨主机通信。

### 架构

```mermaid
flowchart TB
    subgraph NB["Northbound（用户意图层）"]
        NBDB["OVN NB DB\n逻辑网络配置\n（LS、LR、ACL、LB）"]
        NBCTL["ovn-nbctl\n配置 CLI"]
        NBCTL --> NBDB
    end

    NORTHD["ovn-northd\n翻译引擎：NB → SB"]

    subgraph SB["Southbound（实现层）"]
        SBDB["OVN SB DB\n物理绑定 + 逻辑流表\n（Chassis、Port_Binding、Logical_Flow）"]
    end

    subgraph HV1["Hypervisor 1"]
        CTRL1["ovn-controller"]
        OVS1["OVS + br-int"]
        CTRL1 -->|"OpenFlow 规则"| OVS1
    end

    subgraph HV2["Hypervisor 2"]
        CTRL2["ovn-controller"]
        OVS2["OVS + br-int"]
        CTRL2 -->|"OpenFlow 规则"| OVS2
    end

    NBDB --> NORTHD
    NORTHD --> SBDB
    SBDB --> CTRL1
    SBDB --> CTRL2
    OVS1 <-->|"GENEVE 隧道"| OVS2

    style NBDB fill:#4a90d9,color:#fff
    style SBDB fill:#e67e22,color:#fff
    style NORTHD fill:#8e44ad,color:#fff
    style CTRL1 fill:#27ae60,color:#fff
    style CTRL2 fill:#27ae60,color:#fff
```

数据流方向：

1. 用户通过 `ovn-nbctl` 向 NB DB 写入逻辑网络配置。
2. `ovn-northd` 监听 NB DB 变化，将逻辑网络翻译成 Logical Flow（逻辑流表）写入 SB DB。
3. 每台 hypervisor 上的 `ovn-controller` 监听 SB DB，读取 Logical Flow 和 Port Binding，转换为实际的 OpenFlow 规则下发给本机 OVS。
4. hypervisor 间流量通过 GENEVE 隧道传输；VNI 承载 logical datapath ID，Geneve option 继续携带逻辑 ingress/egress port 等元数据。

---

## 3. OVN 核心概念

### Logical Switch（逻辑交换机）

对应一个二层广播域，类似 VLAN 或 VPC 子网。同一个 Logical Switch 上的端口可以二层互通，不同 Logical Switch 上的端口需要通过 Logical Router 路由。

```bash
# 创建逻辑交换机
ovn-nbctl ls-add ls-web

# 查看所有逻辑交换机
ovn-nbctl ls-list
```

### Logical Switch Port（逻辑交换机端口）

逻辑交换机上的一个端口，对应一个虚拟机或容器的网卡。每个端口有固定的 MAC 地址和 IP 地址，OVN 用这些信息生成 ARP 响应规则（ARP responder），避免广播。

```bash
# 添加逻辑端口，绑定 MAC + IP
ovn-nbctl lsp-add ls-web lsp-vm1
ovn-nbctl lsp-set-addresses lsp-vm1 "02:ac:10:ff:01:01 10.0.1.1"

# 查看端口详情
ovn-nbctl lsp-list ls-web
```

常见端口类型：

| 类型        | 说明                                                         |
| :---------- | :----------------------------------------------------------- |
| `""` (默认) | 普通虚拟机端口                                               |
| `router`    | 连接 Logical Router 的端口，用于网关                         |
| `localnet`  | 连接本地可达的物理二层网络，常用于 provider network          |
| `localport` | 本地端口，流量不走隧道，每台主机独立一份（如 metadata 服务） |
| `vtep`      | 连接硬件 VTEP 设备                                           |

### Logical Router（逻辑路由器）

提供 L3 路由，连接多个 Logical Switch。OVN 支持两种路由模式：

- **分布式路由（Distributed）**：东西向流量（同数据中心内）直接在源 hypervisor 上路由，不需要经过集中节点，延迟低。
- **集中式网关路由（Gateway Router）**：南北向流量（访问外部网络）通过指定的 Gateway Chassis 转发，便于统一做 NAT。

```bash
# 创建逻辑路由器
ovn-nbctl lr-add lr-main

# 添加路由器端口（连接 ls-web，MAC + IP 作为网关地址）
ovn-nbctl lrp-add lr-main lrp-to-web 02:ac:10:ff:01:ff 10.0.1.254/24

# 在 ls-web 上创建连接路由器的端口
ovn-nbctl lsp-add ls-web ls-web-to-lr
ovn-nbctl lsp-set-type ls-web-to-lr router
ovn-nbctl lsp-set-addresses ls-web-to-lr router
ovn-nbctl lsp-set-options ls-web-to-lr router-port=lrp-to-web
```

### Chassis 与 Port Binding

Chassis 是 OVN SB DB 中对一台 hypervisor 的抽象，每台运行 `ovn-controller` 的机器会自动注册为一个 Chassis。

Port Binding 记录某个逻辑端口当前绑定在哪台 Chassis 上，由 `ovn-controller` 在检测到对应 OVS port 时自动完成绑定。绑定信息写入 SB DB 后，其他 hypervisor 上的 `ovn-controller` 就知道"这个逻辑端口在那台机器上"，从而建立 GENEVE 隧道路径。

```bash
# 查看所有 Chassis（hypervisor）
ovn-sbctl show

# 查看端口绑定状态
ovn-sbctl list Port_Binding
```

### ACL（访问控制列表）

OVN ACL 是基于连接跟踪（conntrack）的分布式防火墙规则，作用在 Logical Switch 或 Port Group 上。常见动作有 `allow-related`、`allow`、`allow-stateless`、`drop`、`reject`、`pass`。其中 `allow-related` 是最常用的有状态放行动作，匹配到的新连接会建立 conntrack 状态，回程流量会被自动放行。

```bash
# 放行 ARP（二层必须，否则无法解析 MAC）
ovn-nbctl acl-add ls-web to-lport 1100 "arp" allow-stateless

# 放行到 lsp-vm1 的 HTTP 连接；回程流量会被自动放行
ovn-nbctl acl-add ls-web to-lport 1000 \
    "outport == \"lsp-vm1\" && ip4 && tcp.dst == 80" allow-related

# 其他发往 lsp-vm1 的 IP 流量默认拒绝
ovn-nbctl acl-add ls-web to-lport 900 \
    "outport == \"lsp-vm1\" && ip" drop

# 查看 ACL 列表
ovn-nbctl acl-list ls-web
```

### Load Balancer

OVN 原生支持 L4 负载均衡，将 VIP 流量分发到后端池，底层通过 conntrack 的 DNAT 实现，无需额外组件。

```bash
# 创建 LB：VIP 10.0.0.100:80 → 后端 10.0.1.1:80, 10.0.1.2:80
ovn-nbctl lb-add lb-web 10.0.0.100:80 "10.0.1.1:80,10.0.1.2:80" tcp

# 将 LB 绑定到逻辑交换机
ovn-nbctl ls-lb-add ls-web lb-web

# 查看 LB
ovn-nbctl lb-list
```

由于最小单机 namespace 实验更适合稳定验证逻辑交换机、逻辑路由器、ACL 和 Port Security，本文不再提供负载均衡的实操实验，避免把 gateway router、localnet、distributed gateway port 等更复杂前提混入入门文章。

---

## 4. 实验：单台物理机搭建 OVN 测试环境

下面的实验不再共享一套环境，而是拆成 4 个彼此独立的实验。每个实验都有：

- 独立拓扑
- 独立创建步骤
- 独立验证步骤
- 独立清理步骤

这样做有两个好处：

- 读者可以从任意一个实验开始，不依赖前面已经执行过什么；
- 即使某个实验失败，也不会污染后面的实验环境。

本文包含 4 个实验：

1. 逻辑交换机（二层通信）
2. 逻辑路由器（跨网段通信）
3. ACL 过滤
4. Port Security 防 IP/MAC 伪造

### 环境准备

```bash
# 安装 OVS 和 OVN（Debian/Ubuntu）
apt install -y openvswitch-switch ovn-central ovn-host

# 或 RHEL/CentOS
yum install -y openvswitch ovn-central ovn-host
```

### 启动服务

```bash
# Debian/Ubuntu 上的 OVS systemd unit 名是 openvswitch-switch
systemctl start openvswitch-switch

# RHEL/CentOS 上通常是 openvswitch
# systemctl start openvswitch

# 启动 OVN 北向/南向数据库和 ovn-northd
systemctl start ovn-central

# 将本机 OVS 接入 OVN（告知 OVN 本机的 underlay IP 和隧道类型）
ovs-vsctl set open . \
    external-ids:ovn-remote=unix:/var/run/ovn/ovnsb_db.sock \
    external-ids:ovn-encap-type=geneve \
    external-ids:ovn-encap-ip=127.0.0.1   # 单机实验用 127.0.0.1

# 启动 ovn-controller（在每台 hypervisor 上运行）
systemctl start ovn-host

# 某些发行版上，修改 external-ids 后重启 ovn-host 更稳妥
systemctl restart ovn-host

# 先看服务状态。Debian 上 ovn-central / ovn-host 显示 active (exited) 是正常的，
# 它们是 oneshot 包装服务，真正常驻的进程是 ovn-northd / ovn-controller / ovsdb-server。
systemctl status openvswitch-switch --no-pager
systemctl status ovn-central --no-pager
systemctl status ovn-host --no-pager

# 再看关键进程是否存在
ps aux | grep -E 'ovs-vswitchd|ovn-controller|ovn-northd|ovsdb-server' | grep -v grep

# 查看 OVS 当前配置
ovs-vsctl show

# 确认 OVN 相关 external-ids 已写入成功
ovs-vsctl get open . external-ids

# 查看 OVN Southbound 中是否已注册本机 Chassis
ovn-sbctl show
```

预期现象：

```
- `openvswitch-switch` 处于 active 状态；
- `ovn-central` 和 `ovn-host` 可能显示为 `active (exited)`，这在 Debian 上是正常的；
- `ps` 能看到 `ovs-vswitchd`、`ovn-controller`、`ovn-northd`、`ovsdb-server`；
- `ovs-vsctl show` 至少能看到 `br-int`；
- `ovs-vsctl get open . external-ids` 中应包含 `ovn-remote`、`ovn-encap-type`、`ovn-encap-ip`；
- `ovn-sbctl show` 在 Chassis 注册成功后会出现类似下面的输出：

Chassis <uuid>
    hostname: <hostname>
    Encap geneve
        ip: "127.0.0.1"
```

补充说明：

- 单机场景里 `br-int` 显示 `state DOWN` 不一定是错误。只要 bridge 已创建、后续端口能正常加入，实验仍然可以继续。
- 如果 `ovn-sbctl show` 为空，通常说明本机还没有成功注册为 Chassis。优先检查：
  - `ovs-vsctl get open . external-ids` 输出中是否真的包含 `ovn-remote`、`ovn-encap-type`、`ovn-encap-ip`；
  - `ovn-controller` 是否正在运行；
  - 写入 `external-ids` 后是否已重启 `ovn-host`。
- `ovn-controller.log` 在启动初期短暂出现 `br-int.mgmt: connection failed (No such file or directory)` 不一定是故障；如果随后很快出现 `connected`，通常说明只是 `br-int` 管理 socket 尚未就绪、后续已自动重连成功。

常用排障命令：

```bash
# 查看 external-ids 是否完整
ovs-vsctl get open . external-ids

# 查看 ovn-controller 最近日志
tail -n 20 /var/log/ovn/ovn-controller.log

# 关注 chassis 是否已注册成功
ovn-sbctl show
```

### 实验一：逻辑交换机（二层通信）

```mermaid
flowchart TB
    subgraph LS["Logical Switch ls-l2-1 (10.0.1.0/24)"]
        VM1["vm1 / lsp-l2-1\n10.0.1.1"]
        VM2["vm2 / lsp-l2-2\n10.0.1.2"]
    end
    VM1 --- VM2
    style LS fill:#4a90d9,color:#fff
```

创建环境：

```bash
ovn-nbctl ls-add ls-l2-1
ovn-nbctl lsp-add ls-l2-1 lsp-l2-1
ovn-nbctl lsp-set-addresses lsp-l2-1 "02:ac:10:ff:01:01 10.0.1.1"
ovn-nbctl lsp-add ls-l2-1 lsp-l2-2
ovn-nbctl lsp-set-addresses lsp-l2-2 "02:ac:10:ff:01:02 10.0.1.2"

ip netns add vm1
ip link add veth-vm1 type veth peer name ovs-vm1
ip link set veth-vm1 netns vm1
ip netns exec vm1 ip link set veth-vm1 address 02:ac:10:ff:01:01
ip netns exec vm1 ip addr replace 10.0.1.1/24 dev veth-vm1
ip netns exec vm1 ip link set veth-vm1 up
ip netns exec vm1 ip link set lo up
ip link set ovs-vm1 up
ovs-vsctl add-port br-int ovs-vm1
ovs-vsctl set interface ovs-vm1 external_ids:iface-id=lsp-l2-1

ip netns add vm2
ip link add veth-vm2 type veth peer name ovs-vm2
ip link set veth-vm2 netns vm2
ip netns exec vm2 ip link set veth-vm2 address 02:ac:10:ff:01:02
ip netns exec vm2 ip addr replace 10.0.1.2/24 dev veth-vm2
ip netns exec vm2 ip link set veth-vm2 up
ip netns exec vm2 ip link set lo up
ip link set ovs-vm2 up
ovs-vsctl add-port br-int ovs-vm2
ovs-vsctl set interface ovs-vm2 external_ids:iface-id=lsp-l2-2

ovn-sbctl show
```

验证：

```bash
ip netns exec vm1 ping -c3 10.0.1.2
```

清理环境：

```bash
ovs-vsctl --if-exists del-port br-int ovs-vm1
ovs-vsctl --if-exists del-port br-int ovs-vm2
ip netns del vm1 2>/dev/null || true
ip netns del vm2 2>/dev/null || true
ovn-nbctl --if-exists ls-del ls-l2-1
```

### 实验二：逻辑路由器（跨网段通信）

```mermaid
flowchart LR
    subgraph LS1["Logical Switch ls-l3-1 (10.0.1.0/24)"]
        VM1["vm1 / lsp-l3-1\n10.0.1.1"]
    end
    subgraph LR1["Logical Router lr-l3"]
        GW1["10.0.1.254/24"]
        GW2["10.0.2.254/24"]
    end
    subgraph LS2["Logical Switch ls-l3-2 (10.0.2.0/24)"]
        VM2["vm3 / lsp-l3-2\n10.0.2.1"]
    end
    VM1 --- GW1
    GW1 --- GW2
    GW2 --- VM2
    style LR1 fill:#27ae60,color:#fff
```

创建环境：

```bash
ovn-nbctl ls-add ls-l3-1
ovn-nbctl ls-add ls-l3-2
ovn-nbctl lr-add lr-l3

ovn-nbctl lsp-add ls-l3-1 lsp-l3-1
ovn-nbctl lsp-set-addresses lsp-l3-1 "02:ac:10:ff:01:01 10.0.1.1"
ovn-nbctl lsp-add ls-l3-2 lsp-l3-2
ovn-nbctl lsp-set-addresses lsp-l3-2 "02:ac:10:ff:02:01 10.0.2.1"

ovn-nbctl lrp-add lr-l3 lrp-l3-1 02:ac:10:ff:01:ff 10.0.1.254/24
ovn-nbctl lrp-add lr-l3 lrp-l3-2 02:ac:10:ff:02:ff 10.0.2.254/24

ovn-nbctl lsp-add ls-l3-1 ls-l3-1-to-lr
ovn-nbctl lsp-set-type ls-l3-1-to-lr router
ovn-nbctl lsp-set-addresses ls-l3-1-to-lr router
ovn-nbctl lsp-set-options ls-l3-1-to-lr router-port=lrp-l3-1

ovn-nbctl lsp-add ls-l3-2 ls-l3-2-to-lr
ovn-nbctl lsp-set-type ls-l3-2-to-lr router
ovn-nbctl lsp-set-addresses ls-l3-2-to-lr router
ovn-nbctl lsp-set-options ls-l3-2-to-lr router-port=lrp-l3-2

ip netns add vm1
ip link add veth-vm1 type veth peer name ovs-vm1
ip link set veth-vm1 netns vm1
ip netns exec vm1 ip link set veth-vm1 address 02:ac:10:ff:01:01
ip netns exec vm1 ip addr replace 10.0.1.1/24 dev veth-vm1
ip netns exec vm1 ip link set veth-vm1 up
ip netns exec vm1 ip link set lo up
ip netns exec vm1 ip route replace default via 10.0.1.254 dev veth-vm1
ip link set ovs-vm1 up
ovs-vsctl add-port br-int ovs-vm1
ovs-vsctl set interface ovs-vm1 external_ids:iface-id=lsp-l3-1

ip netns add vm3
ip link add veth-vm3 type veth peer name ovs-vm3
ip link set veth-vm3 netns vm3
ip netns exec vm3 ip link set veth-vm3 address 02:ac:10:ff:02:01
ip netns exec vm3 ip addr replace 10.0.2.1/24 dev veth-vm3
ip netns exec vm3 ip link set veth-vm3 up
ip netns exec vm3 ip link set lo up
ip netns exec vm3 ip route replace default via 10.0.2.254 dev veth-vm3
ip link set ovs-vm3 up
ovs-vsctl add-port br-int ovs-vm3
ovs-vsctl set interface ovs-vm3 external_ids:iface-id=lsp-l3-2
```

验证：

```bash
ip netns exec vm1 ping -c3 10.0.2.1
```

清理环境：

```bash
ovs-vsctl --if-exists del-port br-int ovs-vm1
ovs-vsctl --if-exists del-port br-int ovs-vm3
ip netns del vm1 2>/dev/null || true
ip netns del vm3 2>/dev/null || true
ovn-nbctl --if-exists lr-del lr-l3
ovn-nbctl --if-exists ls-del ls-l3-1
ovn-nbctl --if-exists ls-del ls-l3-2
```

### 实验三：ACL 过滤

```mermaid
flowchart TB
    subgraph LS["Logical Switch ls-acl (10.0.1.0/24)"]
        VM1["vm1 / lsp-acl-1\n10.0.1.1"]
        VM2["vm2 / lsp-acl-2\n10.0.1.2"]
        VM3["vm3 / lsp-acl-3\n10.0.1.3"]
    end
    VM2 -->|"ICMP allowed"| VM1
    VM3 -. "ICMP denied" .-> VM1
    style LS fill:#4a90d9,color:#fff
```

创建环境：

```bash
ovn-nbctl ls-add ls-acl
ovn-nbctl lsp-add ls-acl lsp-acl-1
ovn-nbctl lsp-set-addresses lsp-acl-1 "02:ac:10:ff:01:01 10.0.1.1"
ovn-nbctl lsp-add ls-acl lsp-acl-2
ovn-nbctl lsp-set-addresses lsp-acl-2 "02:ac:10:ff:01:02 10.0.1.2"
ovn-nbctl lsp-add ls-acl lsp-acl-3
ovn-nbctl lsp-set-addresses lsp-acl-3 "02:ac:10:ff:01:03 10.0.1.3"

ip netns add vm1
ip link add veth-vm1 type veth peer name ovs-vm1
ip link set veth-vm1 netns vm1
ip netns exec vm1 ip link set veth-vm1 address 02:ac:10:ff:01:01
ip netns exec vm1 ip addr replace 10.0.1.1/24 dev veth-vm1
ip netns exec vm1 ip link set veth-vm1 up
ip netns exec vm1 ip link set lo up
ip link set ovs-vm1 up
ovs-vsctl add-port br-int ovs-vm1
ovs-vsctl set interface ovs-vm1 external_ids:iface-id=lsp-acl-1

ip netns add vm2
ip link add veth-vm2 type veth peer name ovs-vm2
ip link set veth-vm2 netns vm2
ip netns exec vm2 ip link set veth-vm2 address 02:ac:10:ff:01:02
ip netns exec vm2 ip addr replace 10.0.1.2/24 dev veth-vm2
ip netns exec vm2 ip link set veth-vm2 up
ip netns exec vm2 ip link set lo up
ip link set ovs-vm2 up
ovs-vsctl add-port br-int ovs-vm2
ovs-vsctl set interface ovs-vm2 external_ids:iface-id=lsp-acl-2

ip netns add vm3
ip link add veth-vm3 type veth peer name ovs-vm3
ip link set veth-vm3 netns vm3
ip netns exec vm3 ip link set veth-vm3 address 02:ac:10:ff:01:03
ip netns exec vm3 ip addr replace 10.0.1.3/24 dev veth-vm3
ip netns exec vm3 ip link set veth-vm3 up
ip netns exec vm3 ip link set lo up
ip link set ovs-vm3 up
ovs-vsctl add-port br-int ovs-vm3
ovs-vsctl set interface ovs-vm3 external_ids:iface-id=lsp-acl-3

# 只放行从 lsp-acl-2 发往 lsp-acl-1 的 ICMP，新连接命中后回程流量会自动放行
ovn-nbctl acl-add ls-acl to-lport 1100 "arp" allow-stateless
ovn-nbctl acl-add ls-acl to-lport 1000 \
    "outport == \"lsp-acl-1\" && inport == \"lsp-acl-2\" && ip4 && icmp4" allow-related

ovn-nbctl acl-add ls-acl to-lport 900 \
    "outport == \"lsp-acl-1\" && ip" drop
```

验证：

```bash
# vm2 -> vm1 应该通
ip netns exec vm2 ping -c2 10.0.1.1

# vm3 -> vm1 应该不通
ip netns exec vm3 ping -c2 -W1 10.0.1.1
```

清理环境：

```bash
ovs-vsctl --if-exists del-port br-int ovs-vm1
ovs-vsctl --if-exists del-port br-int ovs-vm2
ovs-vsctl --if-exists del-port br-int ovs-vm3
ip netns del vm1 2>/dev/null || true
ip netns del vm2 2>/dev/null || true
ip netns del vm3 2>/dev/null || true
ovn-nbctl --if-exists ls-del ls-acl
```

### 实验四：Port Security 防 IP/MAC 伪造

```mermaid
flowchart TB
    subgraph LS["Logical Switch ls-ps (10.0.1.0/24)"]
        VM1["vm1 / lsp-ps-1\n10.0.1.1"]
        VM2["vm2 / lsp-ps-2\n10.0.1.2"]
    end
    VM1 -. "spoof 10.0.1.99" .-> VM2
    style LS fill:#4a90d9,color:#fff
```

创建环境：

```bash
ovn-nbctl ls-add ls-ps
ovn-nbctl lsp-add ls-ps lsp-ps-1
ovn-nbctl lsp-set-addresses lsp-ps-1 "02:ac:10:ff:01:01 10.0.1.1"
ovn-nbctl lsp-add ls-ps lsp-ps-2
ovn-nbctl lsp-set-addresses lsp-ps-2 "02:ac:10:ff:01:02 10.0.1.2"

ip netns add vm1
ip link add veth-vm1 type veth peer name ovs-vm1
ip link set veth-vm1 netns vm1
ip netns exec vm1 ip link set veth-vm1 address 02:ac:10:ff:01:01
ip netns exec vm1 ip addr replace 10.0.1.1/24 dev veth-vm1
ip netns exec vm1 ip link set veth-vm1 up
ip netns exec vm1 ip link set lo up
ip link set ovs-vm1 up
ovs-vsctl add-port br-int ovs-vm1
ovs-vsctl set interface ovs-vm1 external_ids:iface-id=lsp-ps-1

ip netns add vm2
ip link add veth-vm2 type veth peer name ovs-vm2
ip link set veth-vm2 netns vm2
ip netns exec vm2 ip link set veth-vm2 address 02:ac:10:ff:01:02
ip netns exec vm2 ip addr replace 10.0.1.2/24 dev veth-vm2
ip netns exec vm2 ip link set veth-vm2 up
ip netns exec vm2 ip link set lo up
ip link set ovs-vm2 up
ovs-vsctl add-port br-int ovs-vm2
ovs-vsctl set interface ovs-vm2 external_ids:iface-id=lsp-ps-2

ovn-nbctl lsp-set-port-security lsp-ps-1 "02:ac:10:ff:01:01 10.0.1.1"
```

验证：

```bash
# 正常地址下应可访问 vm2
ip netns exec vm1 ping -c2 10.0.1.2

# 伪造源 IP 后应失败
ip netns exec vm1 ip addr flush dev veth-vm1
ip netns exec vm1 ip addr add 10.0.1.99/24 dev veth-vm1
ip netns exec vm1 ping -c2 -W1 10.0.1.2

# 如需继续在当前环境中排查，可恢复原地址
ip netns exec vm1 ip addr flush dev veth-vm1
ip netns exec vm1 ip addr add 10.0.1.1/24 dev veth-vm1
ip netns exec vm1 ip link set veth-vm1 up
```

清理环境：

```bash
ovs-vsctl --if-exists del-port br-int ovs-vm1
ovs-vsctl --if-exists del-port br-int ovs-vm2
ip netns del vm1 2>/dev/null || true
ip netns del vm2 2>/dev/null || true
ovn-nbctl --if-exists ls-del ls-ps
```

---

## 5. 常用诊断命令

```bash
# ── OVS ────────────────────────────────────────────────────────

# 查看 OVS 整体配置（bridge、port、interface）
ovs-vsctl show

# 查看 br-int 上的所有 OpenFlow 流表
ovs-ofctl dump-flows br-int

# 查看内核 datapath 中已缓存的流（可能包含多个 bridge）
ovs-dpctl dump-flows

# 按 bridge 查看 datapath 流
ovs-appctl dpif/dump-flows br-int

# 查看某个接口的统计信息
ovs-vsctl list interface ovs-vm1

# ── OVN NB（逻辑层）────────────────────────────────────────────

# 查看整个逻辑拓扑
ovn-nbctl show

# 查看逻辑流表（OVN 翻译后的中间表示，比 OpenFlow 可读）
ovn-sbctl lflow-list

# 按逻辑交换机过滤（将 ls-l2-1 替换成当前实验中的交换机名）
ovn-sbctl lflow-list ls-l2-1

# 查看 ACL（将 ls-acl 替换成当前实验中的交换机名）
ovn-nbctl acl-list ls-acl

# 查看负载均衡器（概念参考；本文实验部分不再包含 LB 实操）
ovn-nbctl lb-list

# ── OVN SB（物理绑定层）────────────────────────────────────────

# 查看所有 Chassis（hypervisor）及其上的端口绑定
ovn-sbctl show

# 查看端口绑定详情（确认逻辑端口落在哪台 Chassis）
ovn-sbctl list Port_Binding

# 查看 IP 到 MAC 的绑定信息（ARP/ND 相关）
ovn-sbctl list MAC_Binding

# ── 联合调试 ────────────────────────────────────────────────────

# 追踪一个包在 OVN 逻辑网络中的路径（下面以实验一的 ls-l2-1 为例）
ovn-trace ls-l2-1 'inport == "lsp-l2-1" && eth.src == 02:ac:10:ff:01:01 &&
    eth.dst == 02:ac:10:ff:01:02 && ip4.src == 10.0.1.1 &&
    ip4.dst == 10.0.1.2 && ip.ttl == 64 && icmp4'
```

`ovn-trace` 是调试利器，它模拟一个假想数据包在 OVN 逻辑流表中的完整匹配过程，输出每一步命中的规则和动作，不需要真实发包。

---

## 6. 总结

|            | OVS                                    | OVN                                   |
| :--------- | :------------------------------------- | :------------------------------------ |
| 定位       | 数据平面：可编程虚拟交换机             | 控制平面：网络虚拟化逻辑层            |
| 配置层级   | OpenFlow 规则（底层）                  | 逻辑交换机/路由器/ACL（高层意图）     |
| 管理工具   | `ovs-vsctl`、`ovs-ofctl`               | `ovn-nbctl`、`ovn-sbctl`              |
| 隧道协议   | 支持 GRE/VXLAN/GENEVE                  | 默认使用 GENEVE                       |
| 路由       | 无（需外部控制器）                     | 支持分布式路由和集中式网关            |
| 状态防火墙 | 无（需 conntrack 配合）                | 原生 ACL，基于 conntrack              |
| 负载均衡   | 无                                     | 原生 L4 LB                            |
| 适用场景   | 单机虚拟交换、需要精细控制 OpenFlow 时 | 多主机虚拟网络、OpenStack、Kubernetes |

OVS 是"如何转发"的执行层，OVN 是"转发什么"的决策层。两者配合，OVN 把网络管理员的意图翻译成 OVS 能执行的规则，OVS 负责高速转发。

---

## 7. 参考

- [Open vSwitch FAQ: Design](https://docs.openvswitch.org/en/latest/faq/design/)
- [Open vSwitch without Kernel Support](https://docs.openvswitch.org/en/latest/intro/install/userspace/)
- [OVN Architecture](https://www.ovn.org/support/dist-docs/ovn-architecture.7.html)
- [OVN Northbound DB Schema](https://www.ovn.org/support/dist-docs/ovn-nb.5.html)
- [OVN NBCTL Manual](https://www.ovn.org/support/dist-docs/ovn-nbctl.8.html)

