本页目录

端口与 NAT 穿透

端口是传输层的"多路复用器",NAT 把它变成了互联网最大的 hack。STUN/TURN/ICE 这一套 NAT 穿透工具的本质是:在外网映射和内网地址之间建立一条可预测的路径。

概述

NAT 穿透是当前互联网架构中最大的妥协——IPv4 地址耗尽迫使数十亿设备共享公网 IP,导致"直接连接"变成了例外。在 NAT 之后,两个设备无法直接通信(除非通过中继或打洞)。STUN 帮助设备发现自己的公网映射、TURN 提供不可穿透时的中继、ICE 自动选择最优路径(直连 > 打洞 > 中继)。Tailscale 将这些技术封装成了用户无感的 mesh VPN。

Ephemeral Ports

TCP/UDP 连接中 client 端自动选的 port, 范围由内核管理:

cat /proc/sys/net/ipv4/ip_local_port_range  # 默认: 32768 60999

耗尽风险: 高并发短连接 (每个连接 consuming 一个 ephemeral port + TIME_WAIT 期间的 60s) → 端口用光 → connect() 返回 EADDRNOTAVAIL

缓解: 增大范围 + tw_reuse

NAT 分类 (RFC 4787)

NAT 的映射行为决定了穿透难度:

NAT 映射行为分类:从 Full Cone 到 Symmetric,穿透难度递增 Full Cone(Endpoint-Independent Mapping, EIM) 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext),固定映射 任何外部主机只要知道 Port_ext,就能发包进来 最容易穿透 Address-Dependent Mapping 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) 对 外部 IP_X 同一内部 endpoint 对不同目标 IP → 不同外部 port 仅通信过的主机能回包 Symmetric(Address and Port-Dependent Mapping, APDM) 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) 对 (外部 IP_X,Port_X) 外部端口不可预测,每个目标 port 都不同 最难穿透 由上到下,外部映射的可预测性依次降低 —— 这正是 STUN 打洞从 "总能成功"到"几乎不可能"的分界线。

NAT filtering 行为 (防火墙侧): 类似 mapping, Endpoint-Independent → 最松; Address+Port-Dependent → 最严。

如果 NAT 是 Endpoint-Independent Mapping + Address-Dependent Filtering (最常见 home router), 则 STUN 有效。如果 Symmetric (大多数移动网络), 则需要 TURN 或端口预测。

STUN (RFC 8489)

STUN:先探测公网映射,再让 Peer 直连 ① 探测公网映射(Binding Request) Client → STUN Server Binding Request · 端口 3478/5349(TLS) Server 记录来源 src_ip:port(NAT 外部地址) Response: XOR-MAPPED-ADDRESS=1.2.3.4:56789 ② 告知 Peer,尝试直连 客户端获得公网映射 "从公网看,我是 1.2.3.4:56789" 告诉 Peer 该地址 Peer 直接发 UDP 到 1.2.3.4:56789 Full Cone / 已通信过的 Address-Dependent → 包能进来 STUN 只负责发现地址;能不能真正穿透,取决于对端 NAT 的过滤策略。

TURN (RFC 8656)

STUN 对 Symmetric NAT 无效 — 外部 port 不可预测。TURN 做中继:

TURN:穿不透就中继,永远能通但要付代价 Peer A 发起端 TURN Server 分配 port 50000(Allocate) Peer B 接收端 ① Allocate/Send ② Data Indication

③ 反向同理:Peer B → TURN Server → Peer A

中继延迟 = N × (A↔TURN 延迟 + TURN↔B 延迟) 带宽:TURN server 承担两倍(A→TURN + TURN→B)—— 永远能通,但要付延迟与带宽代价。

ICE (RFC 8445): 候选收集与连接检查

ICE:候选收集定优先级,连通性检查选路径

候选收集 Candidate Gathering(按 type_pref 定优先级) priority = 2²⁴×type_pref + 2⁸×local_pref + (256−component_id)

host 192.168.1.10:50000 · 本地局域网 · type_pref=126(最高)

srflx 1.2.3.4:56789 · STUN 映射 · type_pref=100

relay 5.5.5.5:60000 · TURN 中继 · type_pref=0(最低,仅保底)

连通性检查:对每个 candidate pair 发 STUN Binding Request

逐对发送,选可用的最高优先级 pair host ↔ host 直连 不经过 NAT 仅 srflx ↔ srflx 通 公网 STUN 路径 否则 TURN 中继(保底) Trickle ICE:不等收集完所有候选,边收集边做连通性检查,减少建立时间。

Tailscale 的 NAT 穿透策略

Tailscale NAT 穿透:按优先级逐级尝试,失败才降级 ① 同一局域网 → host-to-host 直连(UDP 或 TCP via tailscaled) 不同局域网,继续下一级 ② 一方公网 IP + Full Cone NAT → STUN → 直接 UDP 双方 Symmetric NAT,继续下一级 ③ 双方 Symmetric NAT → port prediction(生日攻击) 预测失败,继续下一级 ④ 以上都失败 → DERP relay(自建中继)

③ 细节:端口分配模式决定打洞成功率

递增分配(N, N+1, N+2…) 可预测 → 可打洞 随机分配 不可预测 → 打洞不可能 双方同时向预测 port 发 UDP → 命中的 pair 建立通道

参考

  • RFC: 4787, 8489, 8656, 8445, 6887
  • tailscale blog: "How NAT traversal works"

Keywords: ephemeral ports, NAT, Full Cone, Symmetric NAT, STUN, TURN, ICE, hole punching, DERP