这是组网记,也是串流记的延伸。
前两篇讲的是 Moonlight + Sunshine 怎么把画面从宿舍送到平板上。但我在外面怎么串流?
我想到了Tailscale。这个方案基于Wireguard,不仅安全,还可以复用。但是我们学校的网络实在特殊。教学区和宿舍区的内网是不互通的(至少不能互访,可能是网关不同,也可能是VLAN隔离),而公网打洞也不行,因为IPv4 NAT类型是NAT4,IPv6又因为学校配置问题暂时是滚木。
中间那段网络传输,如果全走 Tailscale 官方 DERP,延迟全看运气,吞吐全看人品。更别说 Tailscale 的控制面在境外,哪天不高兴了,整个 tailnet 直接失联,串流也跟着一起陪葬。刚好手头上有一台带宽够大的国内云。还不如折腾一下,ALL IN 国内云。
⚠️ 以下操作建立在有一台公网 Debian 13 服务器的基础上。没有公网服务器?那……可能得先买一台。
物料与前置
服务器:一台 Debian 13,公网 IPv4 + IPv6 都有,root 或 sudo 权限。
域名:至少一个,本文用 derp.example.com 指向服务器,scale.example.com 也解析到同一台机器(Headscale 和 DERP 共用域名也可以,后面会说明)。
一、Headscale
Headscale 是 Tailscale 控制面的自托管实现。没有它,后面的 DERP 和 Peer Relay 都无从谈起。
1.1 安装
Headscale 官方为 Debian 12+ 提供 .deb 包,Debian 13 完全在支持范围内。直接从 GitHub Releases 下载安装即可:
1 | |
.deb 包会自动创建运行用户、生成默认配置、注册 systemd 服务。装完先别急着启动,配置还没改。
1.2 最小必要配置
编辑 /etc/headscale/config.yaml。对串流场景来说,真正需要动手的地方只有几处:
1 | |
server_url 是客户端将来连接的地址,必须写成公网可访问的域名,并且前面通常挂一层反向代理(Caddy/Nginx)来终结 TLS。listen_addr 绑回环即可,反代来兜底。
然后配置 ACL 的存放模式。Headscale 支持 file 和 database 两种策略模式,串流场景直接用 file 最省心——改文件、重启、生效,没有数据库那一层黑盒:
1 | |
1.3 启动与第一个用户
1 | |
创建一个用户(Headscale 里叫“namespace”):
1 | |
然后在任意一台设备上用这个 preauthkey 登录,验证控制面通了:
1 | |
到这里,Headscale 本身就算跑起来了。但此时所有流量如果要中继,仍然会默认走 Tailscale 的官方 DERP——下一节把这件事终结掉。
二、DERP
DERP 是 Tailscale 的兜底中继。直连打不通的时候,流量走 DERP 绕一下。自建 DERP 的意义不在于快,而在于至少不会因为官方节点在国外而绕地球一圈。
2.1 编译 derper
derper 是 Tailscale 源码树里的一个命令,用 Go 编译:
1 | |
编译产物落在 ~/go/bin/derper,复制到一个顺手的位置:
1 | |
2.2 关键参数:-verify-clients 与 -verify-client-url
derper 不加验证就是一个开放中继,谁都能白嫖。有两种防白嫖方式:
方式一:本机 tailscaled 校验(-verify-clients) 。这要求 derper 所在服务器上已经安装了 tailscale 客户端并登录到你的 Headscale,derper 通过本机的 tailscaled.sock 询问“这个客户端是不是咱们 tailnet 的”。
方式二:Headscale /verify 端点(-verify-client-url) 。derper 直接把客户端信息发给 Headscale 的 /verify 接口,由 Headscale 判断是否放行。这种方式不要求 derper 服务器本机登录 tailscale,更适合“中继节点只做中继”的部署。
本文采用方式二,因为后面的 Peer Relay 节点本身就要加入 tailnet,方式一和方式二在逻辑上是兼容的,但 /verify 端点的边界更清晰。
2.3 启动 derper
1 | |
-http-port -1 关掉明文 HTTP,-stun-port 3478 提供 STUN 服务,-certmode manual 配合你自己的证书(反代终结 TLS 时也可以让 derper 跑在内部端口,由 Caddy 对外提供 443)。
写成 systemd 服务是常规操作,这里不展开。
2.4 告诉 Headscale “有这个 DERP”
编辑 /etc/headscale/config.yaml 中的 derp 段:
1 | |
enabled: false 意味着 Headscale 不会自动把内嵌 DERP 塞进 DERP map;urls 指向你自建 derper 的 /derp.json 描述文件。重启 Headscale 后,客户端 tailscale netcheck 里看到的 DERP 列表就应该包含 derp.example.com 了。
三、Peer Relay
DERP 是应用层 TCP 中继,能通,但延迟和吞吐都不理想。串流时还会有UDP in TCP,一旦丢包重传速率就很低。Tailscale 从 1.86 开始提供 Peer Relay:让 tailnet 内的一台节点作为 UDP 中继服务器,客户端在无法直连时优先走 Peer Relay,而不是绕到 DERP。
这正是国内云适合干的事——公网 IPv4 + IPv6 都有,UDP 41641 和 41642 一开,就是一个现成的中继节点。
3.1 在服务器上开启 relay server
首先确保这台机器已经作为节点加入了 tailnet(tailscale up 登录到你的 Headscale)。然后:
1 | |
41642 是 UDP 端口。设为 0 会随机选端口,设为空字符串则关闭。
3.2 声明静态端点
1 | |
把公网 IPv4 和 IPv6 地址作为候选端点通告给其他节点。IPv6 必须带方括号,多个端点用逗号分隔。如果云服务器的公网 IP 变了,这一行必须重新执行,否则候选端点失效。
3.3 打标签
1 | |
tag:relay 是 Headscale ACL 里授权中继能力的锚点。
3.4 ACL
编辑 /etc/headscale/acl.hujson。核心是 grants 段里的两个条目:
1 | |
第一个 grant 看起来有点奇怪:ICMP 和中继有什么关系? 关系大了。Headscale 存在一个已知的 ICMP 可见性 bug,如果不显式授予 ICMP 权限,源设备可能 ping 不通中继节点,而 Peer Relay 的升级过程依赖节点可达性探测。少了这一条,客户端会“以为中继不可用”,然后默默回落到 DERP,你甚至在 tailscale status 里看不出任何异常。
第二个 grant 才是真正授予 tailscale.com/cap/relay 能力的条目。
改完文件后:
1 | |
file 模式下 Headscale 从文件读取策略,重启生效。只有切到 database 模式才需要用 headscale policy set 写入数据库。
3.5 放行防火墙
放行以下端口:
| 端口 | 协议 | 用途 |
|---|---|---|
| 41641 | UDP | Tailscale WireGuard |
| 41642 | UDP | Peer Relay |
| 3478 | UDP | DERP STUN |
| 443 | TCP | Caddy → derper(DERP 兜底) |
核心新增只有 41642/UDP。41641 和 3478 是 Tailscale/DERP 本来就需要的。
如果服务器上跑了 mihomo 之类的 TProxy,记得对 41641 和 41642 做 return,否则 relay 的 UDP 流量会被劫持进代理,中继直接失败。
四、验证
4.1 本机 relay 参数
1 | |
期望看到:
1 | |
4.2 端口监听
1 | |
tailscaled 应同时监听 0.0.0.0:41642 和 [::]:41642。
4.3 客户端侧:看 tailscale ping
在客户端上执行:
1 | |
如果输出里出现 via DERP 以外的中继路径,或者 tailscale status 显示流量经过 tag:relay 节点中转,Peer Relay 才算真正生效。客户端版本也必须 1.86+,旧版本会直接忽略 relay 能力,静默回落 DERP。
后记
这一篇的“踩坑”密度比串流记(二)低一些 但也没好到哪里去
所以验证那一步不要跳过。这样串流用起来不烧心
Headscale + DERP + Peer Relay,三神装到位之后,你终于可以随时随地舒适地访问你的电脑了。
(完)