我用 tailcat 重构 RustDesk,实现扫码直连远程桌面
内容提要
作者开源了SealDesk项目,用tailcat替换RustDesk的传输层,实现扫码直连远程桌面。主机生成包含公钥、PSK和DERP区域的tc地址二维码,控制端扫码后通过公共DERP节点握手,STUN打洞成功则转为直连WireGuard隧道,对称NAT环境下回退到加密转发,无需自建hbbs/hbbr或注册账号。项目使用cgo封装C-ABI连接Go与Rust,绕过rendezvous_mediator,并解决了Android网络切换掉线问题。目前Linux可作主机和控制端,Android仅支持控制端,项目处于Alpha阶段。
延伸解读
tc 地址:把连接凭证压缩进一个字符串
SealDesk 的核心是 tailcat 生成的 tc 地址,它把 WireGuard 公钥、预共享密钥和 DERP 区域打包成一个约 150 字符的 Base64 字符串。这意味着两端无需查询中央服务器,扫码即获得全部握手材料。但这也意味着二维码本身是敏感信息,一旦泄露,攻击者可能利用其中的密钥发起连接。文章提到临时密码可刷新、地址可重置,旧码随即失效,这为凭证轮换提供了基础。
直连与中继:NAT 穿透的两种路径
连接建立后,两端先在公共 DERP 节点会合,尝试 STUN/UDP 打洞。成功则切换为直连 WireGuard UDP 隧道,延迟和带宽更优;若遇对称 NAT 等无法穿透的环境,则回退到 DERP 加密转发。DERP 只能转发密文,无法解密内容,因此中继路径仍保持端到端加密。这种设计让用户无需自建 hbbs/hbbr,但直连成功率取决于网络环境,对称 NAT 下会始终走中继。
Android 网络切换的适配思路
移动端在 Wi-Fi 与蜂窝间切换时,路径更新不及时容易导致会话中断。Android 又限制应用 UID 访问 netlink,Go 侧 netmon 无法自行枚举网络接口。SealDesk 在 FFI 层增加 TcSetAndroidInterfaces,由 Kotlin 层通过 java.net.NetworkInterface 读取接口信息,并在连通性变化时以 JSON 经 JNI 推送给 netmon。这样网络切换时能及时更新路径,减少重连,但该机制依赖应用层主动上报,若上报延迟仍可能影响体验。
Alpha 阶段的平台与互操作限制
当前 SealDesk 处于 Alpha:Linux 可作主机和控制端,Android 仅支持控制端,Windows 和 macOS 虽继承上游代码但打包与驱动细节未完成。与上游 RustDesk 互操作时走的是其原始直连协议,该路径未加密,SealDesk 会在连接前警告。因此,若在不可信网络中使用互操作模式,需留意明文传输风险。项目适合愿意尝鲜、能接受功能不完整的用户。
Q&A
SealDesk 是什么?它和 RustDesk 有什么关系?
SealDesk 是作者开源的一个远程桌面项目,它用 tailcat 替换了 RustDesk 的传输层,实现了扫码直连远程桌面。主机生成包含公钥、PSK 和 DERP 区域的 tc 地址二维码,控制端扫码后通过公共 DERP 节点握手,STUN 打洞成功则转为直连 WireGuard 隧道,对称 NAT 环境下回退到加密转发,无需自建 hbbs/hbbr 或注册账号。
SealDesk 如何在没有中心服务器的情况下让两端互相找到并连接?
SealDesk 利用 tailcat 的 tc 地址作为完整连接凭证。主机启动时生成一个约 150 字符的 Base64 字符串(以 tc 开头),其中打包了 WireGuard 公钥、预共享密钥(PSK)和用于会合的 DERP 区域。两端通过公共 DERP 节点使用该地址会合,STUN/UDP 打洞成功后流量切换到直连 WireGuard UDP 隧道;仅在对称 NAT 等无法穿透的情况下,才回退到 DERP 转发密文(DERP 无法解密)。整个握手不经过中心服务器,配对码只携带 tc 地址和临时密码。
SealDesk 的扫码连接具体步骤是怎样的?
主机打开应用点击“分享此设备”,生成配对二维码。控制端用手机扫描二维码,或在桌面端使用摄像头、选择二维码图片文件、从剪贴板粘贴截图。在同一局域网内两端无需经过互联网即可互相发现——在主机端批准首次请求并勾选“信任此设备”,下次即可直接连接。临时密码可以刷新,地址可以重置;一旦更改,旧的配对码失效。任一端停止分享,所有会话关闭,后台不会保留任何进程。
SealDesk 如何解决 Android 网络切换导致会话掉线的问题?
Android 在 Wi-Fi 和蜂窝网络之间切换时,路径更新跟不上,会话容易掉线。此外,Android 拒绝应用 UID 访问 netlink,导致 Go 侧的 netmon 无法自行枚举网络接口,需要应用层提供。SealDesk 在 libs/tailcat-ffi/android_interfaces.go 中添加了 TcSetAndroidInterfaces:Kotlin 层通过 java.net.NetworkInterface 读取接口名称和地址,每次连接变化时通过 JNI 以 JSON 形式推送,并注册到 netmon,从而避免移动会话在每次切换时重连。
SealDesk 目前支持哪些平台?有什么限制?
SealDesk 目前处于 Alpha 阶段。Linux 已测试可作为主机和控制端;Android 目前仅支持作为控制端。Windows 和 macOS 继承上游代码,但打包和驱动细节尚未完成。与上游 RustDesk 的互操作使用其原始直连协议,该路径未加密,SealDesk 会在连接前发出警告。
SealDesk 如何通过 C-ABI 连接 Go 和 Rust 代码?
tailcat 是纯 Go 编写的,而 RustDesk 核心是 Rust。在 libs/tailcat-ffi 中,作者用 cgo 封装了一个最小化的 C-ABI:主机调用 TcServe 并传入本地 RustDesk 回环端口,所有隧道连接都转发到该端口,同时返回生成的 tc 地址;控制端调用 TcForward 并传入对端地址,打开本地转发端口,连接该端口即可进入隧道。所有返回的字符串通过 TcFree 释放,避免跨 FFI 边界的指针泄漏。Android 上由于 Go 1.27.1 的 -buildmode=c-archive 在 android/arm64 上返回“不支持”,构建改用 c-shared,Rust 侧相应链接 libtailcat.so。