Nicholas Clooney

rtc-bridge:从浏览器建立 TCP 隧道,原理解析

我的朋友 Andrew(voltrevo)做了一个叫 rtc-bridge 的项目。它的介绍简洁得很吸引人:让浏览器通过 WebRTC 与本地 TCP 服务通信,无需开放端口,无需公网 IP,也无需在客户端安装任何东西。

我花了一些时间和 AI agent 讨论它的原理,并与自己对 WebRTC 的理解交叉核对,参见我关于 WebRTC 实际工作方式的笔记。下面这份说明,就是我希望一开始就能看到的内容。


核心思路

常见问题是:本地运行着一个服务,可能是数据库、自定义 TCP 服务器,或其他东西;你想从浏览器访问它,却不想把它暴露到互联网。常规方案总有一些要求:安装 VPN 客户端(Tailscale)、开放入站端口(不走中继的 ngrok)、准备域名和 TLS 证书(Caddy),或者让数据经过云端中继(大多数类似 ngrok 的工具)。

rtc-bridge 用 WebRTC 数据通道作为传输方式,绕过了这些要求。WebRTC 连接建立后,就得到一条双向字节流,rtc-bridge 用它代理原始 TCP 流量。运行在本机的节点只发起出站连接,连接到协调器;浏览器也通过协调器协商 WebRTC 连接。握手完成后,协调器完全退出数据传输路径。

可以这样理解:

browser → WebRTC data channel → node → local TCP service

本机没有服务监听入站连接,也不需要改防火墙规则。


三个组件

Node(节点):运行在你的机器上。通过 WebSocket 主动连接协调器,注册可用服务,并在 WebRTC 连接建立后负责实际的 TCP 桥接。节点用 ed25519 密钥对标识身份。

Coordinator(协调器):一个轻量服务器,只负责发现和信令。它知道有哪些节点、各自提供哪些服务,并在浏览器与节点之间转发 WebRTC 握手信息,包括 SDP offer/answer 和 ICE 候选。WebRTC 连接建立后,协调器就退出了。

Browser(浏览器):查询 /services,查看可用服务,向 /offer 发送 SDP offer,建立 WebRTC 连接,然后通过简单的数据通道协议列出服务并打开 TCP 隧道。


连接流程

它遵循标准的 WebRTC 信令流程:

  1. 节点通过 WebSocket 连接协调器,注册自己。
  2. 浏览器请求 /services,发现可用节点和服务。
  3. 浏览器向协调器的 /offer 发送 SDP offer。
  4. 协调器把 offer 转发给目标节点。
  5. 节点返回 SDP answer,双方交换 ICE 候选。
  6. WebRTC 连接建立,可能是直接 P2P,也可能经过 TURN 中继。
  7. 浏览器用数据通道发送控制消息,然后传输原始 TCP 数据。

TCP 桥接开始前,数据通道上有一套轻量控制协议:

  • ping → pong:存活检查。
  • list → 可用服务。
  • challenge / verify:节点用 ed25519 密钥对证明身份。
  • connect <service>:打开 TCP 隧道。

connect 之后,数据通道就变成原始 TCP 流,不再额外加帧。


ed25519 密钥对到底做了什么

节点拥有密钥对,可以通过挑战与响应的交互证明身份。这一点很扎实:它让你能够确认,对话对象确实是预期的那个节点,而不是注册在同一个协调器上的冒牌节点。

但需要明确它的边界:它认证的是节点,不是用户。任何能访问协调器、知道该请求什么的人,都可以尝试连接。节点身份检查防止的是冒充已知节点,却没有回答“这个浏览器究竟有没有权连接”。

项目目前没有处理这部分。如果你考虑将它用于个人用途以外的场景,这就是应该了解的缺口。


关于 P2P

rtc-bridge 经常被描述为点对点工具,协调器“在连接建立后不参与数据传输”。这在一定条件下成立,而条件很重要。

WebRTC 用 ICE 寻找最佳可用路径。它先尝试直连:先是 host 候选,再是 STUN 反射候选。如果成功,通常意味着两端网络限制不多,就能得到真正的 P2P,协调器也确实退出了。

但直连失败时,ICE 会回退到 TURN 中继。在移动网络,以及 NAT 或防火墙限制严格的企业环境中,这很常见。TURN 是双方都连接的中继服务器,这时流量确实会经过服务器。在连接情况难以预测的环境中部署前,应该查看实际仓库,确认 rtc-bridge 是否配置了 TURN 服务器,还是假定直连 P2P 总能成功。

这不是对设计的批评,只是 WebRTC 的现实。直连 P2P 是理想情况,规划时应把中继视为常见情况。


与我现有配置的比较

我家里用 Tailscale,配合 Caddy 做路由。差别如下:

Tailscale + Caddy rtc-bridge
客户端要求 安装 Tailscale 只需浏览器
传输方式 经 Caddy 的 L7 HTTP 经 WebRTC 的 L4 TCP 隧道
对外连接 DNS + TLS 只有出站连接
身份 Tailscale 认证 ed25519 节点密钥对
用户认证 Tailscale ACL 暂无
可靠性 始终可回退到 DERP 取决于 ICE/TURN 配置

概念上的区别是:我的配置是一张有受控入口的私有网络;rtc-bridge 则是从浏览器出发的 TCP 隧道,网络配合时走 P2P,不配合时走中继。它们在用不同方式解决相邻的问题。

rtc-bridge 最大的优势是客户端零安装。如果你想让别人从浏览器访问本地服务,又不想让对方安装任何软件,这是一个很有意思的方案。代价是放弃 VPN 原本自带的认证与可靠性保障。


认证设计问题

和我讨论的一位 agent 提出了一个具体方案:由协调器签发短期 JWT,节点在接受 connect 命令之前验证它。

  1. 用户向协调器完成认证,可以用 OAuth、会话或其他方式。
  2. 协调器检查 ACL,签发一个限定特定节点和服务的签名令牌。
  3. 浏览器先发送 auth <token>,再发送 connect <service>。
  4. 节点验证令牌,节点一侧无需用户数据库。

关键特征是:有效期短、权限范围明确、验证时无需再往返请求协调器。它与为其他服务边界设计访问令牌的思路类似。如果这个项目未来要处理多用户,这是合适的方向。

这是一个设计提案,不是对仓库现有实现的描述。当前版本认证节点,不认证用户。


值得继续关注

核心概念清晰,使用场景也真实存在。无需开放端口,就能从浏览器原生访问本地 TCP 服务,确实很实用。比如本地开发工具、家庭自动化界面,或任何通常要通过 VPN 访问、但又不想要求客户端软件的东西。

在把敏感用途交给它之前,我希望看到两部分:明确的 TURN 配置方案,避免连接悄悄失败;以及上面描述的认证层。这两点都不是设计上的根本限制,只是目前还没有实现。

从 Andrew 的其他作品来看,他对这类问题考虑得很认真。值得持续关注。