主题
端点无关 vs 端点相关
这是整本书里最值得慢读的一篇。它解释的是一个具体问题:
为什么同一个视频通话软件,在家里的 WiFi 上能直连,切到某些公司网络或手机热点上 就必须走中转服务器,画质和延迟都变差?
答案藏在 NAT 的一个设计选择里。这个选择在 VPP 里表现为两个插件:nat44-ed 和 nat44-ei。
两个维度:映射与过滤
讨论 NAT 行为时有两件独立的事,混在一起谈是大部分困惑的来源:
text
┌────────────────────────────────────────────────────────────────┐
│ 映射行为(Mapping) │
│ 内网 (IP:port) 出去时,被分配到哪个外部 (IP:port)? │
│ 关键问题:访问不同的目的地,会拿到同一个外部端口吗? │
├────────────────────────────────────────────────────────────────┤
│ 过滤行为(Filtering) │
│ 外网主动发进来的包,什么条件下放行? │
│ 关键问题:必须是"我联系过的那个地址+端口",还是只要地址对就行? │
└────────────────────────────────────────────────────────────────┘映射决定"我在外网的身份是否稳定",过滤决定"别人能不能主动找到我"。 P2P 打洞需要这两件事同时成立。
会话表的索引键:一切差异的源头
回到上一篇埋的伏笔。ED 的会话表里有这么一行:
text
external host 203.0.113.11:9001EI 的会话表里没有这一行。就这一个字段的有无,决定了全部行为差异:
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 里"设计选择如何决定用户体验"最干净的一个例子。
下一篇回到更具体的问题:那个外部端口到底是怎么挑出来的,以及会话什么时候被回收。