ᕕ( ᐛ )ᕗ Jimyag's Blog

OVN Gateway、SNAT 与 DNAT 简介及实验

· 1165 words · ~ 6 min read

在上一篇文章里,我们把范围控制在 OVN 的纯内部逻辑网络:逻辑交换机、逻辑路由器、ACL 和 Port Security。这一篇继续往外走一步,讨论一个更接近真实云网络的问题:

  • 租户网络如何访问外部网络?
  • 外部网络如何访问内部实例?
  • OVN 里的 gateway routerlocalnetSNATDNAT 到底是什么关系?

先把核心关系列清楚:

  • localnet 用来把 OVN 的逻辑交换机接到宿主机可达的物理二层网络;
  • gateway router 是 OVN 里承接南北向流量的逻辑路由器角色;
  • SNAT 解决“内部出去”;
  • DNAT 解决“外部进来”;
  • 更常见的“给内部实例分配一个对外 IP”通常会用 dnat_and_snat

1. 核心概念

localnet

localnet 是一种逻辑交换机端口类型,用来把逻辑交换机接到本机 OVS 上的 provider bridge。它不是普通 VM 端口,也不走 Geneve 隧道,而是直接把某个逻辑二层网络映射到宿主机可达的物理二层网络。

典型配置长这样:

1
2
3
4
ovn-nbctl lsp-add ls-public lsp-public-localnet
ovn-nbctl lsp-set-type lsp-public-localnet localnet
ovn-nbctl lsp-set-addresses lsp-public-localnet unknown
ovn-nbctl lsp-set-options lsp-public-localnet network_name=provider

与此同时,本机 OVS 需要配置:

1
ovs-vsctl set open . external-ids:ovn-bridge-mappings=provider:br-provider

这里的意思是:OVN 里名为 providerlocalnet,映射到本机的 br-provider

注意:

  • 这条命令会直接写入 external-ids:ovn-bridge-mappings
  • 如果你的环境里已经有别的 bridge mapping,直接执行可能会把原值覆盖掉
  • 在共享环境或已有配置的机器上,应该先查看现有值,再决定是追加还是替换

gateway router

gateway router 底层仍然是 Logical_Router,但它不只是一个宽泛的“逻辑角色”描述。在 OVN 架构里,更准确的说法是:当 Logical_Router.options:chassis 被设置为某个 chassis 的名字或 UUID 时,这台逻辑路由器会以中心化方式运行在该 chassis 上,承担南北向流量和 NAT 出入口职责。

可以把它和未设置 options:chassis 的分布式路由器对比理解:

  • 设置了 options:chassis:更接近中心化 gateway router,相关流量在指定 chassis 上集中处理
  • 未设置 options:chassis:更接近分布式路由语义,转发和部分状态处理会分散到各个相关 chassis

下面这套实验是单机环境,只有一个 chassis,因此即使不显式设置 options:chassis,有些场景也可能跑通。但在有多个 chassis 的环境里,最好显式设置。

后面的实验步骤会直接把 lr-edge 绑定到当前 chassis。

在最小拓扑里,它通常有两侧:

  • 一侧连租户内部网络,比如 10.0.1.0/24
  • 一侧连 provider/public 网络,比如 192.168.2.0/24

SNAT、DNAT、dnat_and_snat

OVN 支持 3 种常用 NAT 类型:

类型 作用
snat 内部地址访问外部时,把源地址改成外部地址
dnat 外部访问某个地址时,把目标地址改成内部地址
dnat_and_snat 同时做入站 DNAT 和出站 SNAT,常用来实现 floating IP

官方命令是:

1
2
3
ovn-nbctl lr-nat-add <router> snat <external_ip> <logical_ip_or_cidr>
ovn-nbctl lr-nat-add <router> dnat <external_ip> <logical_ip>
ovn-nbctl lr-nat-add <router> dnat_and_snat <external_ip> <logical_ip> [<logical_port> <external_mac>]

其中:

  • 单机实验或中心化部署里,常见写法就是前 4 个参数
  • 在多节点分布式场景下,dnat_and_snat 可以额外带上 logical_portexternal_mac
  • 当显式指定这两个参数时,floating IP 的 ARP 应答和相关流量可以更靠近实例所在 chassis 处理

2. 实验目标

这一篇不把实验拆成多个主题,而是围绕一套完整拓扑验证 3 件事:

  1. 内部实例通过 SNAT 访问外部网络
  2. 外部客户端通过 dnat_and_snat 访问内部实例
  3. 理解 localnet + provider bridge + gateway router 的协作关系

实验边界:

  • 这套实验是单机环境,用 namespace 模拟内部实例和外部网络;
  • 实验关注的是概念闭环,不是高可用、BFD、双网关等生产级细节;
  • 本文不再混入 ACL、LB、Port Security 等其他主题。

3. 实验拓扑

  flowchart LR
    subgraph PRIVATE["Logical Switch ls-private (10.0.1.0/24)"]
        VM1["vm1 / lsp-vm1\n10.0.1.10"]
        LSP1["ls-private-to-lr\n(type=router)"]
    end

    subgraph ROUTER["Logical Router lr-edge"]
        LRP1["lrp-private\n10.0.1.1/24"]
        LRP2["lrp-public\n192.168.2.150/24"]
    end

    subgraph PUBLIC["Logical Switch ls-public (192.168.2.0/24)"]
        LSP2["ls-public-to-lr\n(type=router)"]
        LOCALNET["lsp-public-localnet\nlocalnet=provider"]
    end

    subgraph HOST["Host OVS"]
        BR["br-provider"]
    end

    subgraph EXT["Namespace ext"]
        EXTNS["ext\n192.168.2.151"]
    end

    VM1 --- LSP1
    LSP1 --- LRP1
    LRP1 --- LRP2
    LRP2 --- LSP2
    LOCALNET --- BR
    BR --- EXTNS

    style PRIVATE fill:#4a90d9,color:#fff
    style ROUTER fill:#27ae60,color:#fff
    style PUBLIC fill:#e67e22,color:#fff
    style BR fill:#8e44ad,color:#fff

地址规划:

  • 私有网络:10.0.1.0/24
  • 公网/外部网络:192.168.2.0/24
  • vm110.0.1.10
  • ext192.168.2.151
  • lr-edge 内网侧网关:10.0.1.1
  • lr-edge 外网侧地址:192.168.2.150
  • 对外暴露的 floating IP:192.168.2.152

说明:

  • 这里使用 192.168.2.150-152 作为示例地址
  • 在别的环境里复现实验时,应替换成所在二层网络里未被占用的地址

4. 创建实验环境

创建 provider bridge 和外部 namespace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# provider bridge,用来承接 localnet 映射
ovs-vsctl --may-exist add-br br-provider

# 告诉 OVN:network_name=provider 对应 br-provider
# 注意:如果当前已有 ovn-bridge-mappings,这条命令会覆盖原值
ovs-vsctl set open . external-ids:ovn-bridge-mappings=provider:br-provider

# 创建外部 namespace
ip netns add ext
ip link add veth-ext type veth peer name br-ext
ip link set veth-ext netns ext

ip netns exec ext ip addr replace 192.168.2.151/24 dev veth-ext
ip netns exec ext ip link set veth-ext up
ip netns exec ext ip link set lo up

ip link set br-ext up
ovs-vsctl --may-exist add-port br-provider br-ext

创建逻辑网络、路由器和 localnet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
ovn-nbctl ls-add ls-private
ovn-nbctl ls-add ls-public
ovn-nbctl lr-add lr-edge

# 将 lr-edge 显式设为当前 chassis 上的 gateway router
CHASSIS=$(ovn-sbctl --bare --columns=name find Chassis hostname=$(hostname -f))
ovn-nbctl set Logical_Router lr-edge options:chassis="$CHASSIS"

# 私有网络中的 VM 端口
ovn-nbctl lsp-add ls-private lsp-vm1
ovn-nbctl lsp-set-addresses lsp-vm1 "02:ac:10:ff:01:10 10.0.1.10"

# 逻辑路由器端口
ovn-nbctl lrp-add lr-edge lrp-private 02:ac:10:ff:01:01 10.0.1.1/24
ovn-nbctl lrp-add lr-edge lrp-public 02:ac:10:ff:02:01 192.168.2.150/24

# ls-private -> lr-edge
ovn-nbctl lsp-add ls-private ls-private-to-lr
ovn-nbctl lsp-set-type ls-private-to-lr router
ovn-nbctl lsp-set-addresses ls-private-to-lr router
ovn-nbctl lsp-set-options ls-private-to-lr router-port=lrp-private

# ls-public -> lr-edge
ovn-nbctl lsp-add ls-public ls-public-to-lr
ovn-nbctl lsp-set-type ls-public-to-lr router
ovn-nbctl lsp-set-addresses ls-public-to-lr router
ovn-nbctl lsp-set-options ls-public-to-lr router-port=lrp-public
ovn-nbctl set Logical_Switch_Port ls-public-to-lr options:nat-addresses=router

# ls-public -> provider bridge
ovn-nbctl lsp-add ls-public lsp-public-localnet
ovn-nbctl lsp-set-type lsp-public-localnet localnet
ovn-nbctl lsp-set-addresses lsp-public-localnet unknown
ovn-nbctl lsp-set-options lsp-public-localnet network_name=provider

创建内部实例 namespace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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:10
ip netns exec vm1 ip addr replace 10.0.1.10/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.1 dev veth-vm1

ip link set ovs-vm1 up
ovs-vsctl --may-exist add-port br-int ovs-vm1
ovs-vsctl set interface ovs-vm1 external_ids:iface-id=lsp-vm1

基础连通性检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ovn-sbctl show
ovs-vsctl show

# vm1 应该能 ping 到自己的逻辑网关
ip netns exec vm1 ping -c2 10.0.1.1

# ext 应该能 ping 到公网侧逻辑网关
ip netns exec ext ping -c2 192.168.2.150

# 检查公网侧 peer LSP 是否已启用 NAT 地址同步
ovn-nbctl get Logical_Switch_Port ls-public-to-lr options

5. 实验一:SNAT

目标:让 vm1 访问 ext 时,源地址从 10.0.1.10 变成 192.168.2.150

配置 SNAT

1
2
ovn-nbctl lr-nat-add lr-edge snat 192.168.2.150 10.0.1.0/24
ovn-nbctl lr-nat-list lr-edge

验证

ext 里启动一个简单 HTTP 服务:

1
2
ip netns exec ext sh -c \
  'python3 -m http.server 8080 --bind 192.168.2.151 >/tmp/ovn-ext-http.log 2>&1 &'

然后从 vm1 访问:

1
ip netns exec vm1 curl -s 192.168.2.151:8080 >/dev/null

如果需要观察源地址,可以在 ext 里抓包:

1
ip netns exec ext tcpdump -ni veth-ext tcp port 8080

预期现象:

  • vm1 能访问 192.168.2.151:8080
  • ext 看到的源地址应是 192.168.2.150,而不是 10.0.1.10

6. 实验二:DNAT / Floating IP

dnat 适合说明“外部访问某个地址时,目标地址被改到内部实例”;但在 VM/云网络实践里,更常见的是 dnat_and_snat,也就是 floating IP。

这里用 dnat_and_snat 做入站验证。

补充说明:

  • 这里采用单机环境里更直观的最短命令
  • 在多节点分布式场景下,如果希望 floating IP 的处理更靠近实例所在 chassis,可以显式补上 logical_portexternal_mac
  • 这一节默认你已经完成前面创建阶段里的 options:chassisoptions:nat-addresses=router 配置,否则外部地址可能无法被正确通告和访问

在 vm1 上启动服务

1
2
ip netns exec vm1 sh -c \
  'python3 -m http.server 8080 --bind 10.0.1.10 >/tmp/ovn-vm1-http.log 2>&1 &'

配置 floating IP

1
2
ovn-nbctl lr-nat-add lr-edge dnat_and_snat 192.168.2.152 10.0.1.10
ovn-nbctl lr-nat-list lr-edge

多节点分布式场景下,常见写法会进一步写成:

1
ovn-nbctl lr-nat-add lr-edge dnat_and_snat 192.168.2.152 10.0.1.10 lsp-vm1 02:ac:10:ff:01:10

这不是这里这套单机实验的必需项,但在多节点环境里需要知道这层差异。

验证

ext 访问这个 floating IP:

1
2
3
ovn-nbctl show lr-edge
ovn-nbctl get Logical_Switch_Port ls-public-to-lr options
ip netns exec ext curl -s 192.168.2.152:8080

预期现象:

  • 能返回 vm1 上 HTTP 服务的内容
  • 从外部看,访问的是 192.168.2.152
  • 在 OVN 内部,流量被映射到 10.0.1.10

7. 常用排障命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看整个逻辑拓扑
ovn-nbctl show

# 查看路由器 NAT 规则
ovn-nbctl lr-nat-list lr-edge

# 查看所有 Southbound 绑定
ovn-sbctl show

# 查看 br-int / br-provider 配置
ovs-vsctl show

# 查看 provider bridge 上的数据面流
ovs-appctl dpif/dump-flows br-provider

# 在 ext 中查看来自内部实例的 NAT 后流量
ip netns exec ext tcpdump -ni veth-ext

# 在 vm1 中查看默认路由
ip netns exec vm1 ip route

如果实验不通,优先按这个顺序检查:

  1. ovs-vsctl get open . external-ids 中是否包含正确的 ovn-bridge-mappings
  2. ovn-sbctl showlsp-vm1 是否已绑定
  3. ovn-nbctl show lr-edge 中是否已设置 options:chassis
  4. ovn-nbctl get Logical_Switch_Port ls-public-to-lr options 中是否包含 nat-addresses=router
  5. br-provider 是否存在,且 br-ext 是否已加入
  6. vm1ext 的默认路由/IP 是否正确
  7. lr-nat-list lr-edge 是否已包含对应 NAT 规则

8. 清理环境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 删除 NAT 规则
# 注意:只给 router 名称时,会删除该逻辑路由器上的全部 NAT 规则
ovn-nbctl lr-nat-del lr-edge

# 删除 OVS 端口
ovs-vsctl --if-exists del-port br-int ovs-vm1
ovs-vsctl --if-exists del-port br-provider br-ext

# 删除 namespace
ip netns del vm1 2>/dev/null || true
ip netns del ext 2>/dev/null || true

# 删除逻辑网络
ovn-nbctl --if-exists lr-del lr-edge
ovn-nbctl --if-exists ls-del ls-private
ovn-nbctl --if-exists ls-del ls-public

# 删除 provider bridge
ovs-vsctl --if-exists del-br br-provider

# 清理临时文件
rm -f /tmp/ovn-ext-http.log /tmp/ovn-vm1-http.log

9. 总结

  • localnet 负责把逻辑交换机接到真实可达的二层网络
  • gateway router 负责承接南北向流量
  • SNAT 解决“内部出去”
  • DNAT 解决“外部进来”
  • dnat_and_snat 更像日常说的 floating IP

如果只做纯内部逻辑网络实验,上一篇文章里的交换机、路由器、ACL、Port Security 就够了。
继续往“外部出口、浮动 IP、公网接入”推进时,OVN 的 gateway 和 NAT 就是下一步要掌握的内容。


10. 参考

#Linux #OVN #Gateway #NAT #SNAT #DNAT #Localnet