Hotdry.

Article

RustDesk P2P打洞与NAT穿透工程实现:relay fallback机制与连接延迟优化

深入解析RustDesk开源远程桌面的UDP打洞流程、hbbs/hbbr服务器架构设计,以及P2P失败时的relay回退策略与延迟优化实践。

2026-04-18systems

在远程桌面场景中,网络穿透能力直接决定了产品的可用性与用户体验。RustDesk 作为开源远程桌面解决方案,其 P2P 打洞与 NAT 穿透机制值得深入分析。本文从工程实现角度,解析其核心架构、打洞流程、relay 回退机制以及连接延迟的优化策略。

核心架构:hbbs 与 hbbr 的双角色设计

RustDesk 服务端由两个核心组件构成:hbbs(ID/Rendezvous Server)和hbbr(Relay Server)。这种职责分离的设计体现了对网络穿透失败场景的充分考虑。

hbbs 承担三项关键职能:客户端注册与 ID 分发、NAT 类型检测、以及打洞过程中的信令转发。hbbr 则作为中继服务器,在 P2P 直连失败时提供流量转发能力。从端口配置来看,标准部署需要开放 21115(TCP,NAT 类型测试)、21116(TCP/UDP,信令与打洞)、21117(TCP,中继服务)、21118(hbbs WebSocket API)和 21119(hbbr WebSocket API)。其中 21116 的 UDP 端口是打洞成功的关键前提。

客户端首次启动时会连接 hbbs 服务器完成注册,获取唯一的设备 ID,同时上报自身的 NAT 类型信息。hbbs 将这些信息存储在 SQLite 数据库(db_v2.sql3)中,为后续的打洞决策提供数据支撑。值得注意的是,RustDesk 默认优先尝试 UDP 打洞,只有在 UDP 路径无法建立时才考虑 TCP 打洞或 relay 回退。

UDP 打洞流程深度解析

UDP 打洞的核心原理是利用 NAT 设备的会话表超时机制。当内网客户端 A 向外部服务器发送 UDP 数据包时,NAT 设备会为这次会话创建一个映射条目,允许外部服务器通过该公网 IP: 端口向内网客户端回复数据。利用这一特性,两个位于不同 NAT 后的客户端可以先后向对方发送数据包,从而在各自的 NAT 上 “凿出” 一个可供对方进入的 “洞”。

RustDesk 的打洞实现位于客户端的rendezvous_mediator.rs模块中,具体入口为handle_punch_hole函数。完整流程如下:当客户端 A 需要连接客户端 B 时,A 首先向 hbbs 发送连接请求;hbbs 接收到请求后,将 B 的公网地址(socket_addr)和 NAT 类型信息封装在 PunchHole 消息中转发给 B;同时,hbbs 也将 A 的信息发送给 B。收到打洞指令后,双方客户端开始向对方的公网地址发送 UDP 探测包。

这个过程中的关键细节在于打洞时机与验证逻辑。handle_punch_hole函数会解析对端的 socket 地址,并尝试建立一条临时的 TCP 连接来验证路径可行性 —— 这一步称为 “TCP poke”,用于穿透那些只允许 TCP 出站的严格防火墙。如果 TCP 路径验证成功,系统会发送 PunchHoleAck 确认打洞成功,并在此基础上建立 KCP(一种基于 UDP 的可靠传输协议)通道用于后续数据传输。

打洞结果取决于多种因素。完全圆锥型 NAT(Full Cone NAT)最容易打洞成功,限制圆锥型次之,而对称型 NAT(Symmetric NAT)最难穿透 —— 因为对称型 NAT 为每个对端分配不同的外部端口,导致打洞请求的响应端口与实际通信端口不一致。此外,企业级防火墙的严格出站策略也可能完全阻断 UDP 流量。

Relay 回退机制与连接策略

当 UDP 打洞失败时,RustDesk 会自动切换到 relay 模式。这种优雅降级的设计确保了连接的可用性,但同时也意味着延迟增加和带宽成本上升。

Relay 回退的触发条件主要有三类:打洞超时(通常为数秒)、NAT 类型不兼容(如对称型 NAT),以及防火墙阻断 UDP/TCP 打洞尝试。在 relay 模式下,客户端 A 和客户端 B 分别与 hbbr 建立连接,所有数据流量都通过 hbbr 进行转发。由于 hbbr 通常部署在公网可访问的服务器上,这种模式可以穿透任何 NAT 和防火墙,但延迟会增加一跳。

从工程实践角度看,relay 模式下的流量路径为:客户端 A → NAT → hbbr → NAT → 客户端 B。这意味着数据需要经历两次公网传输,延迟大致是 P2P 直连的两倍。对于远程桌面这种强交互场景,延迟直接影响用户体验。因此,RustDesk 的设计哲学是优先尝试 P2P,relay 作为最后手段

连接延迟优化实践

针对连接建立延迟,RustDesk 在工程实现上做了多层次优化。

第一层优化是并行探测策略。客户端在收到打洞指令后,会同时向多个可能的地址组合发送探测包,包括公网 IP: 端口、局域网 IP(如对端在同一局域网场景)等。这种并行探测可以将打洞成功的时间从等待单次 RTT 缩短到取最小值。

第二层优化是打洞超时控制。代码中设置了合理的超时阈值(通常为 3-5 秒),避免在打洞失败时无限等待。超时后立即触发 relay 回退,确保用户不会长时间卡在连接建立阶段。

第三层优化是NAT 类型预判。hbbs 在首次注册阶段就会检测客户端的 NAT 类型,并存储在数据库中。当连接请求到达时,hbbs 可以根据对端的 NAT 类型预判打洞成功率 —— 如果双方都是对称型 NAT,直接跳过打洞尝试并立即启用 relay,可以节省无效的打洞等待时间。

第四层优化是连接会话复用。对于需要频繁连接的场景(如多次远程会话),已经建立的 P2P 通道会被保持一段时间,避免重复打洞。这种设计在连续使用场景下可以显著降低连接延迟。

部署注意事项

自建 RustDesk 服务时,网络配置需要特别注意以下几点:确保防火墙放行 21116 UDP 端口,这是打洞成功的必要条件;如果打洞失败率较高,需要检查两端网络是否存在对称型 NAT 或严格防火墙;在低延迟要求场景下,可以考虑使用中继服务器但需注意带宽成本。

总体而言,RustDesk 的 P2P 打洞与 NAT 穿透实现体现了务实的工程态度 —— 以 P2P 直连为最优目标,以 relay 回退为可用性保障,通过多层次的超时控制和预判机制优化连接延迟。这种设计思路对于构建高可用网络应用具有重要的参考价值。

资料来源:RustDesk GitHub 仓库(https://github.com/rustdesk/rustdesk)及官方自托管文档。

systems