Skip to content

端点无关 vs 端点相关

这是整本书里最值得慢读的一篇。它解释的是一个具体问题:

为什么同一个视频通话软件,在家里的 WiFi 上能直连,切到某些公司网络或手机热点上 就必须走中转服务器,画质和延迟都变差?

答案藏在 NAT 的一个设计选择里。这个选择在 VPP 里表现为两个插件:nat44-ednat44-ei

两个维度:映射与过滤

讨论 NAT 行为时有两件独立的事,混在一起谈是大部分困惑的来源:

text
  ┌────────────────────────────────────────────────────────────────┐
  │  映射行为(Mapping)                                            │
  │  内网 (IP:port) 出去时,被分配到哪个外部 (IP:port)?             │
  │  关键问题:访问不同的目的地,会拿到同一个外部端口吗?             │
  ├────────────────────────────────────────────────────────────────┤
  │  过滤行为(Filtering)                                          │
  │  外网主动发进来的包,什么条件下放行?                            │
  │  关键问题:必须是"我联系过的那个地址+端口",还是只要地址对就行?   │
  └────────────────────────────────────────────────────────────────┘

映射决定"我在外网的身份是否稳定"过滤决定"别人能不能主动找到我"。 P2P 打洞需要这两件事同时成立。

会话表的索引键:一切差异的源头

回到上一篇埋的伏笔。ED 的会话表里有这么一行:

text
       external host 203.0.113.11:9001

EI 的会话表里没有这一行。就这一个字段的有无,决定了全部行为差异:

text
  ED(端点相关)会话键 = 5 个值
  ┌────────────┬──────┬──────────────┬──────┬──────┐
  │  内网 IP   │ 内网 │   外网 IP    │ 外网 │ 协议 │
  │            │ 端口 │              │ 端口 │      │
  └────────────┴──────┴──────────────┴──────┴──────┘
                        ↑ 目的也在键里,换个目的就是另一条会话

  EI(端点无关)会话键 = 3 个值
  ┌────────────┬──────┬──────┐
  │  内网 IP   │ 内网 │ 协议 │
  │            │ 端口 │      │
  └────────────┴──────┴──────┘
                        ↑ 和目的无关,一条会话服务所有目的

实测差异一:会话粒度

跑这个实验

bash
./labctl apply base 04     # 切到 nat44-ei
./labctl verify base 04    # 在 EI 和 ED 下跑同一组测试

实验做的事:让 h1 从同一个内网端口 23456 分别访问 s1 和 s2,然后数会话条数。

ED 模式nat44-ed)产生了两条会话:

text
    i2o 10.10.0.11  proto UDP port 23456 fib 0
    o2i 203.0.113.1 proto UDP port 23456 fib 0
       external host 203.0.113.11:9001        ← 给 s1 的
    i2o 10.10.0.11  proto UDP port 23456 fib 0
    o2i 203.0.113.1 proto UDP port 23456 fib 0
       external host 203.0.113.12:9001        ← 给 s2 的

EI 模式nat44-ei)只产生了一条

text
    i2o 10.10.0.11  proto udp port 23456 fib 0
    o2i 203.0.113.1 proto udp port 22324 fib 0
       total pkts 4, total bytes 230          ← 两条流的包都记在这一条上
text
  ┌────────┬──────────┬────────────────────────────────────┐
  │  模式  │ 会话条数 │ 索引方式                            │
  ├────────┼──────────┼────────────────────────────────────┤
  │  ED    │    2     │ 内网端 + 外网端,每个目的一条        │
  │  EI    │    1     │ 只按内网端,一条服务所有目的         │
  └────────┴──────────┴────────────────────────────────────┘

一个容易踩的直觉陷阱

很多资料会说"ED 会给不同目的分配不同的外部端口"。但看上面 ED 的输出: 两条会话用的都是外部端口 23456

原因是 ED 的键里已经包含了目的地址,所以两条会话即使共用同一个外部端口也不会冲突 —— VPP 完全没有必要去换端口。所以外部端口相同还是不同,不是判断 EI/ED 的可靠依据。 可靠依据是会话表的结构,以及下面这个过滤测试。

顺带一个观察:EI 把内网端口 23456 换成了 22324,ED 则原样保留了 23456。 两个插件的端口分配策略不同,这是下一篇的话题。

实测差异二:过滤行为(决定 P2P 能不能打洞)

这是真正决定性的实验。

实验结果:

text
  ED 模式
    内网 10.10.0.11:23456  →  外部 203.0.113.1:23456
    RESULT 超时 没有收到任何包,说明被 NAT 丢弃
    丢包计数:1 nat44-ed-out2in-slowpath  no translation  error

  EI 模式
    内网 10.10.0.11:23456  →  外部 203.0.113.1:64233
    RESULT 收到 来自 203.0.113.11:8888 内容 b'probe\n'
text
  ┌────────┬──────────────────────┬────────────────────┐
  │  模式  │ 换源端口打进来        │ 对 P2P 的影响       │
  ├────────┼──────────────────────┼────────────────────┤
  │  ED    │ 丢弃                 │ 打洞失败,必须走中继 │
  │  EI    │ 送达                 │ 打洞可行            │
  └────────┴──────────────────────┴────────────────────┘

ED 丢包时的计数器名字很直白:no translation —— 查不到对应的转换关系。 因为 ED 的会话键里写着"外网端是 203.0.113.11:9999",而进来的包源端口是 8888, 键匹配不上,没有这条会话,丢。

EI 的键里根本没有外网端口这一项,所以只要目的地址和外部端口对得上就能匹配到会话,放行。

严谨一点:VPP 的 nat44-ei 是"地址受限"而非"全锥型"

我们还测过另一种情况:让另一台外部主机 s2(h1 从未联系过)打向那个外部端口。 结果是丢弃

所以 VPP 的 nat44-ei 精确来说是:端点无关映射 + 地址受限过滤。 同一台外部主机换端口可以,换主机不行。这对绝大多数 P2P 打洞已经足够 (打洞双方通过信令服务器交换了彼此的公网地址,之后互相直发)。

对上 RFC 的术语

RFC 3489(STUN)曾经用"锥型 / 对称型"来分类,但那套术语把映射和过滤混在了一起, 容易产生歧义。RFC 4787 把两个维度拆开重新定义,是现在的标准说法:

老术语(RFC 3489)映射行为过滤行为对应 VPP
Full Cone 完全锥型端点无关端点无关(谁都能进)无直接对应
Restricted Cone 受限锥型端点无关地址受限nat44-ei
Port Restricted Cone 端口受限锥型端点无关地址+端口受限无直接对应
Symmetric 对称型端点相关地址+端口受限nat44-ed

P2P 打洞的可行性,从上到下递减。两端都是对称型时,基本只能走中继(TURN)。

这解释了开头那个问题

家用路由器多数是锥型 NAT(映射端点无关),所以视频通话能直连。 运营商级 NAT(CGN)和企业防火墙为了安全和端口复用效率,普遍用对称型 —— 于是同一个软件在那些网络下就退化成走中转服务器。

这不是软件的 bug,是它下面那层 NAT 的性质决定的。

那到底该用哪个

text
  用 nat44-ed(端点相关)当                用 nat44-ei(端点无关)当

  · 这是通用出口网关,用户什么流量都有      · 明确要支持 P2P:VoIP、视频会议、
  · 需要尽可能省端口(同一端口可以对        游戏联机、去中心化应用
    多个目的复用)                        · 用户会抱怨"某某应用连不上"
  · 安全性优先:外部主机想进来必须          · 你在做的是家用/小型办公网关,
    完全对上五元组                        体验优先于极致的端口利用率
  · 不确定选哪个 —— 这是 VPP 的默认        · 需要 EI 独有的功能:会话高可用
    主力实现,功能也最全                    (ha failover)

功能差异也要考虑。ED 独有:twice-NAT、负载均衡静态映射、VRF 多租户表。 EI 独有:会话表高可用同步(nat44 ei ha failover)、可选的端口分配算法 (addr-port-assignment-alg)。第 3 章会展开。

两个插件不能同时启用

它们会抢同一批接口。本教程所有实验步骤开头都调用 nat44_clean 把三个 NAT 插件全关掉, 然后只启用需要的那个,就是为了避免这类冲突。

另外注意命令不通用:EI 的每条命令都多一个 ei。 用 ED 的命令去配 EI,会得到一个不太友好的报错:

text
set interface nat44: add host-eth1 failed

正确写法是 set interface nat44 ei in host-eth1 out host-eth2

小结

text
                     ┌─── 会话键含不含"外网端"?───┐
                     │                             │
                  含(5 元组)                  不含(3 元组)
                     │                             │
                 nat44-ed                      nat44-ei
                     │                             │
       ┌─────────────┴─────┐           ┌───────────┴─────────┐
       │                   │           │                     │
  每个目的一条会话    换端口进来丢弃   一条会话服务所有目的  换端口进来放行
       │                   │           │                     │
       └──── 对称型 ───────┘           └──── 受限锥型 ───────┘
              P2P 需中继                        P2P 可打洞

一个字段的有无,一路推导出会话粒度、过滤严格程度、P2P 可行性、端口利用率。 这是 NAT 里"设计选择如何决定用户体验"最干净的一个例子。

下一篇回到更具体的问题:那个外部端口到底是怎么挑出来的,以及会话什么时候被回收。

实验环境基于 netlab + containerlab + VPP 26.06