Show HN: Tailcat —— 像 netcat,但跑在 Tailscale 的数据平面上

查看原文 HN 讨论

文章摘要

Tailcat 是 Tailscale 在 2026 年 8 月的 TailscaleUp 大会上开源的一个小工具,自我介绍是一句很妙的口号:「Tailscale without Tailscale, by Tailscale」(由 Tailscale 出品的、不需要 Tailscale 的 Tailscale)。它把 Tailscale 的开源组件重新组装成一个类似 netcat 的命令行工具,但底层跑的是 Tailscale 的数据平面(内部代号 magicsock)——两台机器之间建立点对点的 WireGuard 加密隧道,用 DERP 中继作为 NAT 打洞的旁路信道,并在打洞失败时充当最后的兜底转发。区别在于它完全不使用 Tailscale 的控制平面:所有连接元数据由你自己带外(out of band)交换,怎么传都行。

用法模型极简:一端跑 tailcat 服务端(监听方),它会打印出一个短短的连接令牌;另一端把这个令牌交给客户端就连上了。全程 WireGuard 端到端加密。你不需要 Tailscale 账号,不需要 root/管理员权限,它不改动路由表、不碰 DNS——纯粹是一个用户态的 Go 库加 CLI。默认可以用官方提供的免费限速 DERP 中继(默认 DERP map 在 tailcat.dev/derpmap.json),也可以完全自己跑 derper。仓库里还有一个把 tailcat 编译成 WebAssembly 的浏览器实验版,可以和 CLI 互传文件和文本(浏览器侧目前只走 DERP 中继,直连要等 WebRTC 支持)。

功能面比 netcat 宽得多。除了最基本的 stdin/stdout 对拷,还能用 --serve=8080,8443(或 --serve=all)把本地 TCP 端口透过隧道暴露出去;用 --serve=no-auth-ssh 直接跑一个免认证 SSH 服务端(想要认证就 --serve=22 反代系统 SSH);ping --until-direct 探测连通性并报告每个响应是走 DERP 还是直连路径;socks 子命令通过隧道起一个 SOCKS5 代理跑任意命令,而且令牌本身可以当 URL 主机名用;--serve=exit-node 让客户端把服务端所在网络当出口节点用。parseresolve 两个子命令则用于查看和展开令牌内容。

密钥管理的设计尤其值得一看,README 把安全语义讲得很清楚。默认是临时密钥:每次启动生成一把内存里的新密钥,打印一个从未有人见过的地址,进程退出后密钥丢弃、地址永久作废——所以分享这个地址只会指向那一次运行,这是安全的默认值。另一种是 tailcat genkey 生成的保存密钥:地址跨重启稳定,代价是任何你曾经分享过这个地址的人都能连上未来任何一次用同一把密钥起的服务,除非用 --allow 限制允许的客户端。CLI 启动时会明确告诉你用的是哪一种。令牌还可以发布成 DNS TXT 记录,之后凡是接受令牌的地方都能直接写域名。

README 里最漂亮的例子是「通过 DNS 暴露的受保护 SSH 服务」:客户端先 genkey --client 生成身份密钥对并把公钥给服务端,服务端用 genkey --fixed-region 生成绑定到最近 DERP 区域的密钥,然后 --serve=22 --allow=nodekey:... 只对那一个客户端开放,令牌发布成 TXT 记录,客户端只需 tailcat ssh my-server.example.com。效果是:服务器上没有任何对外开放的入站端口,不需要端口转发也不需要端口敲门,WireGuard 在 SSH 服务看到任何一个包之前就完成了客户端认证;其他人的握手会被静默忽略,他们甚至无法得知这里跑着一个 SSH 服务。

技术栈层面,它复用了 Tailscale 客户端的四块组件:用户态 WireGuard(不用内核 TUN/TAP,所以不需要 root)、magicsock(基于 STUN 的端点发现和 UDP 打洞,在直连 UDP 和 DERP 之间多路复用)、gVisor 的用户态 TCP/IP 协议栈 Netstack(这是它能在不做任何操作系统网络配置的前提下收发 TCP 连接的关键)、以及 DERP 中继协议。连接流程被拆成六步:服务端起来、连 DERP、打印令牌;客户端解析令牌拿到公钥和区域,生成临时密钥对连同一个 DERP;客户端通过 DERP 发一条名为「Meow」的发现握手消息(携带自己的节点公钥),服务端把它加进 WireGuard peer 列表并重配引擎,回一条「Meowed」;WireGuard 标准握手完成、隧道打通;与此同时双方通过 DERP 互相通告 UDP 端点(STUN 学到的公网地址加本地接口地址)跑 disco 打洞协议,成功就升级为直连,失败就继续走 DERP;最后由 gVisor 在两端处理 TCP 连接建立,服务端按端口把入站连接分派给不同的处理器。连接令牌本身是 tc 前缀加 base64 编码的 CBOR,装着服务端的 Curve25519 公钥和 DERP 信息,只引用区域 ID 时约 50 字节。

作者对稳定性的态度很坦白:Tailcat 免费,但不承诺任何 API 或 CLI 稳定性,Go API、CLI 参数、输出格式和线格式都可能改;公共 DERP 中继没有 SLA 也没有吞吐目标,可以随时被撤销。README 结尾有一段项目史:它 2023 年 9 月诞生于一趟长途航班上,最初叫「derpcat」,第一次跑通的那条提交信息写着「UA 605 PDX-ORD 飞往爱尔兰途中,爽,没买 wifi」。后来它随 Tailscale 内部演进多次腐烂,最近才被复活并重构成 tailscale.com 仓库的普通 Go module 使用者,而不再是一个 fork。

HN 评论精华

这条帖子拿到 662 分、129 条评论。作者 Brad Fitzpatrick(memcached、OpenID、Perkeep 的作者,Go 团队老兵,Tailscale 联合创始人之一)全程在场答疑,几条关键澄清都出自他手。讨论主要沿三条线展开:这算不算厂商锁定、和 iroh/magic-wormhole/netbird 等既有方案怎么比、以及一场从 Nix 意外岔到 Minecraft Java 版与基岩版之争的超长离题支线。