P2P 信令与中继服务器技术全面调研
自建 P2P 信令与中继服务器,是跨网互联、远程访问、Mesh VPN 等场景的核心基建。本文系统梳理了从 NAT 穿透协议到产品实践的完整技术图谱,覆盖三个层面:协议标准(STUN/TURN/ICE/BEHAVE)、主流产品(Tailscale、Nebula、NetBird、ZeroTier、Headscale、OpenZiti 等)、以及框架与算法(libp2p、WebRTC、Kademlia DHT)。
所有技术描述均基于 RFC 原文、学术论文、官方文档的一手来源核实,关键统计数据标注出处。
核心概念速查
在深入技术细节前,先用一张表快速理解本文涉及的核心概念。每个概念在后续章节会有详细展开。
| 概念 | 一句话解释 |
|---|---|
| NAT | 网络地址转换,将内网私有 IP 映射为公网 IP,让多台设备共享一个公网出口 |
| STUN | 让设备发现自己公网地址的协议(“我在外界看来是谁?") |
| TURN | 当直连失败时,由服务器中继转发流量的协议(“帮我传话”) |
| ICE | 编排 STUN + TURN 的完整方案,自动找到最优连接路径(“总指挥”) |
| DERP | Tailscale 自研的加密中继,走 HTTPS 443 端口,极难被封 |
| WireGuard | 现代 VPN 协议,约 4000 行代码,使用 Curve25519 + ChaCha20-Poly1305 |
| Noise 协议 | 密码学协议框架,用于构建安全的传输通道,Nebula 基于此实现 |
| 信令通道 | 独立于业务流量的控制通道,用于交换地址、协调打洞(“边带通道”) |
| 打洞(Hole Punching) | 双方同时向对方发包,在各自 NAT 上打开映射,建立直连 |
| 控制平面 / 数据平面 | 控制平面管"谁能连谁”,数据平面管"实际数据怎么走",两者分离 |
| DHT | 分布式哈希表,无中心服务器的 P2P 节点发现与数据定位机制 |
| Mesh VPN | 网状 VPN,节点间直接互联而非星形汇聚,无单点故障 |
| Lighthouse | Nebula 的发现节点,类似 DNS——回答"某节点在哪?" |
| PKI / CA | 公钥基础设施 / 证书权威,用于身份认证与加密 |
| CGNAT | 运营商级 NAT,多用户共享少量公网 IP,是打洞的常见障碍 |
NAT 穿透:直连的第一道门槛
两台分别位于不同 NAT 后的设备要建立 P2P 直连,最大的障碍就是 NAT。NAT(Network Address Translation,网络地址转换)是一种将内网私有 IP 地址映射为公网 IP 地址的技术——家庭路由器和运营商网关都在做这件事。它让多台设备共享一个公网 IP 出口,但也使得外部无法主动向内网发起连接,这是所有穿透技术要解决的核心矛盾。
理解 NAT 的行为模型,是所有穿透技术的理论基础。
NAT 行为的二维描述
RFC 4787(BCP 127)用两个独立维度描述 NAT 行为,取代了早期 Cone/Symmetric(锥形/对称型)的粗粒度分类:
- 映射行为(Mapping Behavior)——同一内部
(IP, port)到不同目的地址通信时,NAT 是否复用同一外部映射?换句话说,你从家里访问 A 网站和 B 网站时,路由器给你分配的外部端口是否相同?- Endpoint-Independent Mapping(端点无关映射):总是复用同一映射(≈ Full Cone 的映射维度)。无论你访问谁,外部端口都一样——这是最好的情况,打洞最容易
- Address-Dependent Mapping(地址相关映射):换目的 IP 则换映射。访问不同 IP 时外部端口不同
- Address and Port-Dependent Mapping(地址和端口相关映射):换目的 IP 或端口则换映射(≈ Symmetric)。这是最差的情况,几乎无法打洞
- 过滤行为(Filtering Behavior)——外部主机向 NAT 映射地址发包时,NAT 何时放行?即,外部来的包要满足什么条件才被转发到内网?
- Endpoint-Independent Filtering(端点无关过滤):任意外部主机可发——最宽松
- Address-Dependent Filtering(地址相关过滤):仅当内部此前向该 IP 发过包——只回信给联系过的人
- Address and Port-Dependent Filtering(地址和端口相关过滤):仅当内部此前向该
(IP, port)发过包——最严格
RFC 4787 REQ-1 明确要求:NAT 必须具备 Endpoint-Independent Mapping,否则几乎所有打洞技术都将失效,被迫依赖中继。
四种经典 NAT 类型
虽然 RFC 3489 的分类已被 RFC 5389 废止(因"造成了大量混淆"),但其四类模型仍是理解打洞失败原因的最佳入门工具:
| 类型 | 映射特征 | 外部可达性 | 打洞可行性 |
|---|---|---|---|
| Full Cone(完全锥形) | 同一内部 (IP,port) → 同一外部 (IP,port) | 任意外部主机可达 | ✅ 最容易 |
| Restricted Cone(受限锥形) | 同上 | 仅限内部曾发过包的 IP | ✅ 需协调 |
| Port Restricted Cone(端口受限锥形) | 同上 | 仅限内部曾发过包的 (IP,port) | ✅ 需精确协调 |
| Symmetric(对称型) | 不同目的 → 不同映射 | 仅接收过包的源 | ❌ 几乎不可能 |
对称型 NAT(Symmetric NAT) 是 P2P 直连的最大障碍——它为每个目的地分配不同的外部端口,使得通过会合服务器交换的公网地址在向对端发包时不再有效。
会合服务器(Rendezvous Server) 是一台位于公网的中介服务器,两端各自向它注册自己的地址信息,它负责将一方的地址告知另一方,为打洞做准备。
UDP 打洞原理
UDP 打洞(Hole Punching)是 NAT 穿越中最简单、最健壮的技术。Bryan Ford 等人在 USENIX ‘05 的经典论文中系统化文档化并实测了这一技术:
sequenceDiagram
participant A as 节点 A
participant S as 会合服务器
participant B as 节点 B
A->>S: 注册(报告自身地址)
B->>S: 注册(报告自身地址)
S-->>A: 返回 B 的公网映射
S-->>B: 返回 A 的公网映射
Note over A,B: 双方同时向对方公网地址发包
A->>B: UDP 包(在本端 NAT 开洞)
B->>A: UDP 包(在本端 NAT 开洞)
Note over A,B: NAT 映射建立,双向通信成功核心流程:两端各自向公网会合服务器注册 → 服务器告知对方的公网反射地址 → 双方同时从本地 socket 向对方发包 → 出站包在各自 NAT 上建立映射(“洞”)→ 后续对端发来的包被 NAT 视为"响应"而放行。
实测数据(Ford et al., USENIX ATC 2005):约 82% 的 NAT 支持 UDP 打洞,约 64% 支持 TCP 打洞。论文还首次可靠展示了基于 TCP simultaneous-open 的 P2P TCP 流建立。
TCP 打洞:可行但脆弱
TCP 打洞利用 RFC 793 定义的 simultaneous-open(同时打开) 模式——正常 TCP 连接是一端发 SYN(主动打开)、另一端回 SYN-ACK(被动打开);而 simultaneous-open 是两端几乎同时互发 SYN,SYN 在网络中交错,各自以 SYN-ACK 响应,连接建立。这要求 NAT 支持端点无关映射且正确处理 simultaneous-open(RFC 5382 REQ-2)。
比 UDP 更脆弱的原因:TCP 状态机复杂(需要正确处理 SYN/SYN-ACK/ACK 三次握手中的各种异常组合)、部分 NAT 会丢弃入站 SYN 或错误转换出站 SYN-ACK。业界推荐以 UDP 为基础协议(如 QUIC over UDP)。
QUIC 是 Google 开发、HTTP/3 使用的传输协议,运行在 UDP 之上,内置加密(TLS 1.3),解决了 TCP 队头阻塞问题。用 UDP 承载既能获得类似 TCP 的可靠传输,又绕过了 TCP 打洞的困难。
标准化穿透协议栈
NAT 穿透技术栈自下而上分为多层,每层解决不同问题:
flowchart TD
A["BEHAVE 行为规范<br/>定义 NAT 该如何表现"] --> B["打洞技术<br/>UDP/TCP Hole Punching"]
B --> C["STUN<br/>发现反射地址"]
C --> D["TURN<br/>中继回退"]
C --> E["ICE<br/>编排 STUN + TURN"]
E --> F["DERP<br/>加密中继补充"]
style A fill:#fff3e0,stroke:#FF9800
style B fill:#e3f2fd,stroke:#2196F3
style C fill:#e3f2fd,stroke:#2196F3
style D fill:#e3f2fd,stroke:#2196F3
style E fill:#f3e5f5,stroke:#9C27B0
style F fill:#e8f5e9,stroke:#4CAF50最底层是 BEHAVE 工作组制定的 NAT 行为规范——它不定义穿越技术,而是定义"一个合格的 NAT 应该怎么表现"。往上是打洞技术(实际穿越手段),再上是 STUN(地址发现工具)和 TURN(中继回退工具),ICE 将这些工具编排为完整方案,最顶层是 Tailscale 的 DERP 补充中继设计。
STUN:发现你的公网地址
RFC 5389(2008),作者 J. Rosenberg 等。STUN(Session Traversal Utilities for NAT)原名 “Simple Traversal of UDP through NAT”,因定位从"完整方案"改为"工具"而更名——它不再是独立解决 NAT 穿越的完整系统,而是一个供其他协议调用的工具。
核心方法是 Binding(绑定):客户端向 STUN 服务器发送 Binding Request 请求,报文经过 NAT 时源地址被 NAT 改写;服务器收到后,将观察到的公网源地址(即 reflexive transport address——反射传输地址,就是"你在 NAT 外面看起来是什么地址")放入 XOR-MAPPED-ADDRESS 属性返回给客户端。
简单说,STUN 就像你问朋友"从我这边打电话给你,你的来电显示是什么号码?"——朋友看到的号码就是你的公网映射地址。
STUN 是一个工具而非完整方案,通过 “STUN usages”(用法)被嵌入 ICE、SIP Outbound 等完整方案中。
重要警示:RFC 5389 明确指出 classic STUN(RFC 3489)的 NAT 类型分类算法"有缺陷",许多真实 NAT 无法干净归类。
TURN:中继回退
RFC 5766(2010),被 RFC 8656 更新。TURN(Traversal Using Relays around NAT)是 STUN 的扩展(大部分报文是 STUN 格式)。当两端位于"行为不良"的 NAT 之后,打洞会失败,此时需要 TURN 中继服务器转发数据包——它就像一个邮局,帮你转交信件。
客户端通过 TURN 命令在服务器上创建 Allocation(分配)——就是在服务器上"租"一个中继端口——获得一个 relayed transport address(中继传输地址)。对端向该地址发包,服务器转发给客户端。
关键机制:
- Permissions(权限):控制哪些对端可以经此中继通信,防止未授权访问
- Channels(通道):一种更高效的数据发送方式,用 4 字节通道号代替每次发送时的完整地址信息,减少开销
- Allocation 刷新:中继分配有生命周期,需定期刷新维持,防止资源泄漏
- 独有特性:单一中继地址可同时与多个对端通信(为 SIP forking——同一呼叫拨给多个目的地——设计)
设计哲学:TURN 服务器带宽成本高(所有流量都经过它),应仅在 ICE 找不到直连路径时作为最后手段使用。
ICE:编排一切
RFC 8445(2018),废止了 RFC 5245。ICE(Interactive Connectivity Establishment)是基于 offer/answer 方法论(一端发出连接提议 offer,另一端回复应答 answer)的 NAT 穿越完整方案,编排 STUN 与 TURN。
flowchart TD
A["收集候选地址<br/>Host · SRFLX · Relay"] --> B["优先级排序<br/>并通过信令通道交换"]
B --> C["STUN 连通性检查<br/>逐一测试候选对"]
C --> D{"找到有效路径?"}
D -->|"是"| E["提名并切换直连 ✓"]
D -->|"否"| F["TURN/DERP<br/>中继回退"]
style A fill:#e3f2fd,stroke:#2196F3
style E fill:#e8f5e9,stroke:#4CAF50
style F fill:#fce4ec,stroke:#f44336ICE 的核心阶段:
- 收集候选地址(Gathering Candidates):每端收集多类候选——
- Host candidates(主机候选):本机所有网络接口地址(如 192.168.1.100)
- Server-Reflexive candidates(服务器反射候选,简称 SRFLX):通过 STUN 获取的 NAT 公网映射地址
- Relayed candidates(中继候选):通过 TURN 获取的中继地址
- 运行中还可能发现 Peer-Reflexive candidates(对端反射候选):连通性检查中对端观察到的地址
- 优先级排序与交换:按公式计算优先级(Host > SRFLX > Relay),通过信令通道(如 SDP——Session Description Protocol,会话描述协议,用于描述连接参数的明文文本格式)交换候选信息
- 连通性检查(Connectivity Checks):用 STUN Binding 请求成对测试候选对——“这条路径通不通?“采用 triggered-check(触发检查)、角色冲突解决(ICE-Controlling / ICE-Controlled,决定谁来主导提名的角色)、
USE-CANDIDATE提名等机制 - 提名与结论(Nominating & Concluding):选定有效对,结束 ICE 处理,释放多余候选
ICE 的价值在于兼容各种网络拓扑——它不假设单一方案有效,而是收集所有可能的路径并逐一测试,最终选择最优的那条。
DERP:Tailscale 的加密中继创新
DERP(Designated Encrypted Relay for Packets)是 Tailscale 自研的中继协议,非 IETF RFC 标准,权威来源为 Tailscale 官方文档与源码。
设计要点:
- 零知识转发:DERP 永不终止或解密 WireGuard 加密——它只是盲目转发已加密流量。Tailscale 私钥从不离开本地设备,DERP 服务器无法解密任何流量,即使 DERP 服务器被攻破也不会泄露通信内容
- 走 HTTPS(TCP 443):443 是 HTTPS 标准端口,几乎所有网络都放行。要封禁 DERP 就得封 443,但这会同时阻断所有正常 Web 访问——几乎不可能在不引起注意的情况下被封禁
- 公钥寻址:使用 curve25519(一种椭圆曲线加密算法,提供 128 位安全强度)公钥作为地址路由数据包——不依赖 IP 地址,公钥就是你的"门牌号”
- 双栈支持:IPv4/IPv6 全支持,可桥接纯 v4 网络与纯 v6 网络
- 多区域选路:协调服务器下发 DERP Map(DERP 服务器列表),客户端按网络延迟选择最近的 home DERP(主 DERP 节点)
DERP 兼作"边带通道(side channel)“用于交换 ip:port 信息以协调打洞时机——绝大多数连接先用 DERP 交换信息,然后升级为直连。
flowchart TD
CO["协调服务器<br/>下发 DERP Map"] --> DA["DERP 区域 A<br/>TCP:443"]
CO --> DB["DERP 区域 B<br/>TCP:443"]
NA["节点 A<br/>硬 NAT 后"] -->|"HTTPS 加密转发"| DA
NB["节点 B<br/>硬 NAT 后"] -->|"HTTPS 加密转发"| DB
style CO fill:#f3e5f5,stroke:#9C27B0
style DA fill:#fff3e0,stroke:#FF9800
style DB fill:#fff3e0,stroke:#FF9800
style NA fill:#e3f2fd,stroke:#2196F3
style NB fill:#e3f2fd,stroke:#2196F3DERP Map 由协调服务器下发,客户端按延迟选择最近的 DERP 节点。当直连 UDP 路径无法建立时(硬 NAT、防火墙阻断 UDP),DERP 在 TCP 443 上转发已加密的 WireGuard 数据包。单节点故障切换同区域其他节点;整区域故障切换最近区域。
控制平面与数据平面:行业架构共识
现代 Mesh VPN 普遍采用控制平面与数据平面分离的架构。这是最重要的架构原则。
- 控制平面(Control Plane):负责"谁能连谁”——设备注册、身份认证、密钥分发、策略下发(ACL——访问控制列表,定义哪些节点可以互通)、NAT 穿越协调。不接触任何业务数据
- 数据平面(Data Plane):负责"实际数据怎么走”——节点间的端到端加密隧道,数据加密/解密/路由全部在节点本地完成
flowchart TD
CS["协调服务器<br/>发现 · 密钥 · ACL · NAT 协调"]
CS -.->|"仅元数据"| A["节点 A"]
CS -.->|"仅元数据"| B["节点 B"]
A ===|"P2P 加密直连<br/>WireGuard/Noise"| B
style CS fill:#fff3e0,stroke:#FF9800
style A fill:#e3f2fd,stroke:#2196F3
style B fill:#e3f2fd,stroke:#2196F3虚线表示控制平面(元数据交换:谁在线、公钥是什么、地址是什么),实线表示数据平面(端到端加密隧道:实际的业务流量)。协调服务器看得见元数据但看不到业务流量——即使协调服务器被入侵,攻击者也无法获取通信内容。
代表产品的架构实践
| 产品 | 控制平面 | 数据平面 | 中继回退 |
|---|---|---|---|
| Tailscale | 官方协调服务器(闭源) | WireGuard(用户态) | DERP(HTTPS/TCP 443) |
| Nebula | Lighthouse(自托管) | Noise 协议(自带实现) | Lighthouse 可配为 relay |
| ZeroTier | Planet Root + Controller | Salsa20/Poly1305(VL1) | Root 转发 + network relays |
| NetBird | 管理服务 + signal + relay | WireGuard | 自建 relay |
| Headscale | 自建控制面(Tailscale 开源替代) | 复用 Tailscale 客户端 | 内嵌 DERP + Peer relays |
| OpenZiti | Controller + Router | 内置 overlay 加密 | Edge Router 转发 |
WireGuard 是现代 VPN 协议,目标是简单(约 4000 行代码,远小于 IPSec 的数十万行)、快速、安全。使用 Curve25519(密钥交换)、BLAKE2(哈希)、ChaCha20-Poly1305(加密)等现代加密原语。Tailscale/NetBird/Netmaker 均以 WireGuard 为数据平面基础。
Noise 协议框架 是构建安全传输协议的密码学框架(WireGuard 也基于它)。Nebula 自带了基于 Noise 的传输实现,而非直接用 WireGuard。
Tailscale:DERP 双层设计的标杆
Tailscale 的 DERP 承担双重角色:(1) 连接协商中介——绝大多数连接仅用 DERP 交换信息后即升级直连;(2) 直连失败时的 fallback 流量中继。连接技术上总是先经 DERP 建立,然后并行尝试直连打洞。成功则无缝切换,典型条件下直连成功率超过 90%。
难点是对称 NAT(hard NAT)随机化源端口映射,使 P2P 几乎不可能;多层 NAT、企业防火墙阻断 UDP、运营商级 CGNAT 会触发 DERP fallback。Tailscale 还赞助了 FreeBSD PF 防火墙的 Endpoint-Independent Mapping (EIM) 补丁,使 pfSense/OPNsense 等设备从对称 NAT 变为锥形 NAT,改善打洞成功率。
Nebula:去中心化 PKI + Lighthouse
Nebula(Slack 开源)基于 Noise 协议框架的互认证 P2P 软件定义网络。
- 去中心化 PKI/CA:每个网络拥有自己的证书权威(CA),证书断言节点 IP、名称与组成员关系。PKI(Public Key Infrastructure,公钥基础设施) 是一套用数字证书管理公钥的体系,CA(Certificate Authority,证书权威) 是签发证书的可信第三方
- Lighthouse(灯塔节点):IP 不变的发现节点,类似 DNS 服务器——回答"主机 X 在哪?“查询,返回其最后已知的外部 endpoint。Lighthouse 默认不转发流量,只做发现协调。当打洞失败时,可将 Lighthouse 配置为 relay
NetBird:全栈开源的 Tailscale 替代
NetBird 是全栈开源 + 完整自建协调基础设施的方案,直击 Tailscale 闭源控制平面痛点。管理服务、signal server(信令服务器)、relay routing(中继路由)全部可自建,适合 GDPR/HIPAA/SOC 2(欧盟数据保护/美国医疗信息/安全审计标准)等合规敏感场景。客户端(含 iOS/Android)也全部开源。
自建友好度排序
flowchart TD
A["完全自建友好<br/>NetBird · Nebula<br/>OpenZiti · Headscale"] --> B["部分自建<br/>ZeroTier<br/>Netmaker"]
B --> C["控制面闭源<br/>Tailscale<br/>(需 Headscale 替代)"]
style A fill:#e8f5e9,stroke:#4CAF50
style B fill:#fff3e0,stroke:#FF9800
style C fill:#fce4ec,stroke:#f44336NetBird 因全栈开源、自带 signal server + relay,是最直接的参考实现。Headscale 实现了 Tailscale 控制平面的开源自建替代,数据平面仍使用官方开源客户端。
去中心化穿越的新范式
libp2p 的 NAT 穿透体系借鉴了 ICE 协议,但去除了对中心化 STUN/TURN 服务器的依赖,采用分布式协调。libp2p 是 Protocol Labs 开发的模块化 P2P 网络栈,被 IPFS、Ethereum 等主要 P2P 网络采用,提供节点发现、连接建立、流多路复用和安全通信等基础工具。
libp2p 三大核心模块
| 模块 | 功能 | 对应 ICE 角色 |
|---|---|---|
| AutoNAT | 判断节点是否在 NAT 后。请求其他 peer 回拨自己的地址——成功=public(公网可达),失败=private(在 NAT 后) | 类似 STUN |
| Identify | 连接建立后双方交换信息,学习对端观察到的自身外部公网 IP:port。利用已有连接即可完成,无需独立的 STUN 服务器基础设施 | 类似去中心化 STUN |
| Circuit Relay v2 | 为 private peer 提供轻量中继。需先获得 reservation(预约),对连接数/时长/数据量严格限制,使多数 public 节点都可作中继而资源开销极小 | 类似 TURN(仅转发信令,非全量流量) |
DCUtR:从中继升级到直连
DCUtR(Direct Connection Upgrade through Relay) 用于将已建立的中继连接升级为直连。它的核心思想是利用已有中继通道协调打洞时机,然后双方同时发起直连拨号:
sequenceDiagram
participant I as 发起方
participant R as 中继节点
participant L as 监听方
I->>R: 建立中继连接
R->>L: 转发连接请求
Note over I,L: 交换 Connect 消息(含各自非中继地址)
Note over I,L: Initiator 测量 RTT,发送 Sync
Note over I,L: 等待半个 RTT 后双方同时拨号
I->>L: 直接拨号(打洞)
L->>I: 直接拨号(打洞)
Note over I,L: 直连建立,中继连接释放关键设计:协议极轻量,正常情况仅需 2 次网络往返,每方向交换 <500 字节。双方同时拨号使得 5-tuple(五元组:源IP、源端口、目的IP、目的端口、协议) 在各自路由器状态表匹配 → 打洞成功。
大规模实测数据
IMC 2026 论文(Trautwein et al.)对 DCUtR 在 IPFS 生产网络进行了大规模测量,基于 440 万次穿越尝试、85,000+ 网络、167 国家:
给定 relay reservation 和公网地址发现成功后,打洞阶段条件成功率 70% ± 7.1%。
TCP 与 QUIC 成功率无统计差异(均约 70%),挑战了"UDP 穿越必然更优"的传统认知——DCUtR 的 RTT 同步机制使两者等效。
97.6% 的成功连接在首次尝试建立。
WebRTC:浏览器中的 P2P
WebRTC(Web Real-Time Communication)是 W3C 标准化的浏览器 P2P 通信框架,支持音视频通话和数据传输。WebRTC 采用 ICE 框架系统性寻找最优通信路径。
一个特别的设计:信令协议本身不由 WebRTC 规定——它只定义如何传输音视频和数据,至于"怎么交换连接信息"留给应用层自行实现(常用 WebSocket——一种在单个 TCP 连接上进行全双工通信的协议)。信令交换使用 SDP(Session Description Protocol) 明文协议,包含可达 IP:port 候选、音视频轨道数、编解码器列表、加密参数等。
WebRTC 的 DataChannel(RFC 8831)支持 P2P 任意数据传输,底层基于 SCTP over DTLS——SCTP(Stream Control Transmission Protocol)是支持多路复用的传输协议,DTLS(Datagram TLS)是 TLS 的 UDP 版本,提供加密。这一组合让浏览器间可以直接传输任意数据。
经典 DHT 算法速览
DHT(Distributed Hash Table,分布式哈希表) 是 P2P 网络的节点发现与数据定位基础——它像一个去中心化的字典,没有中心服务器,每个节点存储一部分数据,通过协同查找定位任意 key。三大经典算法奠定了现代 P2P 系统的路由架构。
Kademlia(2002)
Petar Maymounkov & David Mazières 设计的基于 XOR 度量的 DHT。节点 ID 与 key 共享同一 160 位标识空间,距离定义为两者的 XOR(异或运算:相同为 0,不同为 1)。XOR 距离满足度量空间的数学性质(自反性、对称性、三角不等式),使得路由可以高效收敛。
核心创新:
- 节点可从收到的查询中学习路由信息——与 Chord 不同,Kademlia 的每次查询都在同时更新路由表
- 路由表非刚性——查询可发往区间内任意节点,允许基于延迟选路甚至并行发送异步查询
- k-bucket(k 桶) 结构:每个节点维护一组"桶”,每个桶覆盖一段距离区间,存放到该区间最近的 k 个节点信息。这种结构天然适应节点动态加入/离开
被 eMule(Kad Network)、Ethereum 节点发现协议、BitTorrent Mainline DHT 广泛采用。
Chord(2001, SIGCOMM)
Ion Stoica 等设计的基于**一致性哈希(Consistent Hashing)**的 DHT。一致性哈希是一种特殊的哈希分配策略,使得节点加入/离开时只有最小范围的数据需要重新分配(而非全局重哈希),是分布式系统负载均衡的经典技术。
核心是 finger table(指取表):每个节点维护 O(log N) 个路由表项,指向标识环上特定距离的节点。通信开销和状态随节点数对数增长,即 100 万个节点只需约 20 个表项——极其高效。
Pastry(2001, Middleware)
Rowstron & Druschel 设计的混合路由方案,结合数值路由与前缀匹配。节点 ID 按字符串前缀组织:路由每跳使与目的地的共同前缀增加一位,就像邮政编码逐级定位。节点维护路由表 + 叶子集(leaf set,数字上最近的节点) + 邻居集(neighborhood set,网络延迟上最近的节点),跳数 O(log N)。
中继与会话韧性的工程智慧
Mosh:无状态漫游的优雅范式
Mosh(Mobile Shell)是 MIT 的 Keith Winstein & Hari Balakrishnan 开发的远程终端工具,解决 SSH 在移动网络下频繁断线的问题。Mosh 基于 State Synchronization Protocol(SSP,状态同步协议) 实现——一个运行在 UDP 上的新协议,在 client 和 server 间安全同步终端状态。
其核心创新是无状态漫游(stateless roaming):
每当 server 收到 client 发来的、序列号高于此前所有收到包的认证包时,该包的 IP 源地址即成为 server 出包的新目标地址。
这意味着 client 的 IP 地址变化时(如从 WiFi 切换到 4G,或手机信号切换基站),无需任何重连操作——server 自动跟踪到新地址。client 甚至无需感知自己的公网 IP 已变化。这一思路为 P2P 中继场景下的"会话迁移 / IP 漂移后无缝续接"提供了直接借鉴。
Mosh 还有本地回显(Local Echo) 机制:client 预测用户击键效果并投机显示,无需等待 server 回显 → 弱网下打字响应迅速。
Tor / I2P:分层中继的匿名性参考
Tor(The Onion Router,洋葱路由) 的核心机制是 Circuit(电路) 匿名。Tor 有四类 relay 节点:
- Guard/Entry Relay(入口):知道 client IP 但不知目的地
- Middle Relay(中间):只知前后跳 IP
- Exit Relay(出口):知目的地但不知 client
- Bridge Relay(网桥):用于绕过 Tor 网络封锁
结构特性为"仅前驱/后继知识":每个 relay 只知前一跳和后一跳 IP。只要 guard 和 exit 不共谋,匿名性成立。
I2P(Invisible Internet Project) 使用大蒜路由(Garlic Routing) 和单向隧道(unidirectional tunnels)——分 inbound(入站)和 outbound(出站)隧道,每条隧道仅单向传输。一次完整消息交换需要 4 条隧道。
虽然匿名性不是 P2P 信令的核心目标,但 Tor/I2P 的"零知识中继"理念与 DERP 的"不解密、仅转发"设计异曲同工。
frp / ngrok:反向代理的工程实践
frp(Fast Reverse Proxy) 是 Go 语言编写的开源内网穿透工具。frp 的"控制通道 + 数据隧道复用“分离设计值得借鉴:frpc(客户端,部署在内网)主动出站连接 frps(服务端,部署在公网 VPS)的 bind_port 建立持久 TCP 长连接(控制通道);frpc 告知 frps 自己能代理的服务;外部访问 frps 公网端口时,frps 通过控制通道通知 frpc,数据经隧道复用转发。
这与 P2P 的"信令通道 + 数据通道分离"架构理念一致——控制信令走一条路,业务数据走另一条路。
ngrok 提供类似功能的 SaaS 服务,一键启动,集成认证与监控。bore 是轻量级的 Rust/Tokio 实现替代品。三者的共同模型是集中式中继(与 P2P 直连目标不同),但其反向连接突破入站限制的工程实践可直接借鉴。
自建信令与中继:八条工程启示
基于上述全面调研,总结自建 P2P 信令与中继服务器的八条核心结论:
信令通道是先决条件
所有打洞技术都依赖一个独立的"边带/信令通道"来交换候选地址并协调打洞时机。Tailscale 用协调服务器 + DERP 充当此通道,WebRTC 要求自备 signaling channel。项目设计应首先明确信令服务器的职责边界——它只负责协调,不接触业务流量。
分层回退是标准范式
业界共识是 STUN 反射地址发现 → UDP 打洞 → TURN/DERP 中继的递进回退。ICE 把这套流程标准化;DERP 是 Tailscale 在 TURN 之外的 HTTPS/TCP 中继补充(更难被封)。不要指望一种方案解决所有场景——必须有 fallback。
对称型 NAT 是主要障碍
Endpoint-Dependent Mapping(≈ Symmetric)的 NAT 无法常规打洞,必须依赖中继或端口预测。CGNAT(运营商级 NAT) 让大量用户共享少量公网 IP,进一步加剧了这个问题。BEHAVE(RFC 4787 REQ-1)正是为消除该障碍而要求 NAT 具备端点无关映射。
推荐以 UDP 为基础协议
TCP 打洞可行但脆弱(需 simultaneous-open,成功率约 64%),推荐以 UDP 为基础协议。QUIC over UDP 是现代选择——获得可靠传输的同时避开 TCP 打洞的困难。
中继服务器应"零知识”
DERP 的设计启示:中继服务器应不解密、仅转发加密包;走 443 端口以提升可达性;支持双栈与多区域选路;兼作发现/协调通道。这样即使中继服务器被攻破或被要求交出数据,也无法泄露通信内容。
控制平面与数据平面解耦
Tailscale 的 coordination server、Nebula 的 Lighthouse、ZeroTier 的 root+controller、OpenZiti 的 Controller 均只做协调/发现/密钥/策略,不承载业务流量。这是自建系统应遵循的架构原则——协调服务器可以宕机,只要节点间已建立直连,通信不受影响。
会话韧性设计
Mosh 的无状态漫游(序列号驱动 IP 迁移)为中继场景的断线重连提供优雅范式,值得在自建中继中实现。当用户 IP 漂移(移动网络切换、WiFi 断重连)时,中继服务器自动跟踪新地址,无需重建会话。
自建技术选型优先级
NetBird / Nebula / OpenZiti / Headscale > ZeroTier > Tailscale(控制面闭源)。NetBird 因全栈开源、自带 signal server + relay,是最直接的参考实现。
参考来源
RFC 标准
| RFC | 标题 | 说明 |
|---|---|---|
| RFC 5389 | STUN | Session Traversal Utilities for NAT |
| RFC 5766 | TURN | Traversal Using Relays around NAT |
| RFC 8656 | TURN(更新版) | IPv6/IPv4 支持 |
| RFC 8445 | ICE | Interactive Connectivity Establishment |
| RFC 3489 | Classic STUN | NAT 类型分类(已废止) |
| RFC 5128 | P2P across NAT | 所有已知穿越方法文档化 |
| RFC 4787 | NAT UDP 行为要求 | BCP 127 |
| RFC 5382 | NAT TCP 行为要求 | BCP 142 |
| RFC 8831 | WebRTC Data Channels | P2P 数据通道 |
学术论文
| # | 论文 | 作者 | 年份/会议 |
|---|---|---|---|
| 1 | Peer-to-Peer Communication Across NATs | Ford, Srisuresh, Kegel | USENIX ATC 2005 |
| 2 | Kademlia: A P2P Information System Based on XOR | Maymounkov, Mazières | 2002 |
| 3 | Chord: A Scalable P2P Lookup Service | Stoica et al. | SIGCOMM 2001 |
| 4 | Pastry: Scalable Decentralized Object Location | Rowstron, Druschel | Middleware 2001 |
| 5 | Large-Scale Measurement of NAT Traversal (DCUtR) | Trautwein et al. | IMC 2026 |
| 6 | WireGuard Formal Proof | INRIA | 2019 |
| 7 | Mosh: Interactive Remote Shell for Mobile | Winstein, Balakrishnan | MIT 2012 |
产品文档与源码
| 产品 | GitHub | 协议 | 官方文档 |
|---|---|---|---|
| Tailscale | tailscale/tailscale | BSD-3-Clause | tailscale.com |
| Nebula | slackhq/nebula | MIT | defined.net |
| ZeroTier | zerotier/ZeroTierOne | MPL 2.0 | docs.zerotier.com |
| NetBird | netbirdio/netbird | BSD-3-Clause + AGPLv3(服务端) | netbird.io |
| Headscale | juanfont/headscale | BSD-3-Clause | headscale.net |
| OpenZiti | openziti/ziti | Apache 2.0 | openziti.io |
| Tinc | gsliepen/tinc | GPL-2.0 | tinc-vpn.org |
| libp2p | libp2p/go-libp2p | MIT | docs.libp2p.io |
协议说明:ZeroTier 核心代码采用 MPL 2.0(Mozilla Public License),而非早期文献中常引用的 BSL 1.1。NetBird 采用双许可模式:客户端与核心组件为 BSD-3-Clause,管理服务/signal/relay 等服务端组件为 AGPLv3。