这是组网记,也是串流记的延伸。

前两篇讲的是 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
2
3
4
5
6
7
HEADSCALE_VERSION="0.29.5"   # 以 Releases 页面最新版为准
ARCH=$(dpkg --print-architecture)

curl -fL -o headscale.deb \
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${ARCH}.deb"

sudo apt install -y ./headscale.deb

.deb 包会自动创建运行用户、生成默认配置、注册 systemd 服务。装完先别急着启动,配置还没改。

1.2 最小必要配置

编辑 /etc/headscale/config.yaml。对串流场景来说,真正需要动手的地方只有几处:

1
2
server_url: https://scale.example.com
listen_addr: 127.0.0.1:8080

server_url 是客户端将来连接的地址,必须写成公网可访问的域名,并且前面通常挂一层反向代理(Caddy/Nginx)来终结 TLS。listen_addr 绑回环即可,反代来兜底。

然后配置 ACL 的存放模式。Headscale 支持 file 和 database 两种策略模式,串流场景直接用 file 最省心——改文件、重启、生效,没有数据库那一层黑盒:

1
2
3
policy:
mode: file
path: /etc/headscale/acl.hujson

1.3 启动与第一个用户

1
2
sudo systemctl enable --now headscale
sudo systemctl status headscale

创建一个用户(Headscale 里叫“namespace”):

1
headscale users create kevison

然后在任意一台设备上用这个 preauthkey 登录,验证控制面通了:

1
headscale preauthkeys create --user kevison --reusable --expiration 24h

到这里,Headscale 本身就算跑起来了。但此时所有流量如果要中继,仍然会默认走 Tailscale 的官方 DERP——下一节把这件事终结掉。


二、DERP

DERP 是 Tailscale 的兜底中继。直连打不通的时候,流量走 DERP 绕一下。自建 DERP 的意义不在于快,而在于至少不会因为官方节点在国外而绕地球一圈。

2.1 编译 derper

derper 是 Tailscale 源码树里的一个命令,用 Go 编译:

1
2
3
4
sudo apt update
sudo apt install -y golang-go

go install tailscale.com/cmd/derper@latest

编译产物落在 ~/go/bin/derper,复制到一个顺手的位置:

1
2
sudo mkdir -p /etc/derp
sudo cp ~/go/bin/derper /etc/derp/

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
2
3
4
5
6
7
8
sudo /etc/derp/derper \
-a :8989 \
-http-port -1 \
-stun-port 3478 \
-hostname derp.example.com \
-verify-client-url https://scale.example.com/verify \
-certmode manual \
-certdir /etc/derp/

-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
2
3
4
5
6
derp:
server:
enabled: false # 不用 Headscale 内嵌的 DERP
urls:
- https://derp.example.com/derp.json
paths: []

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
tailscale set --relay-server-port=41642

41642 是 UDP 端口。设为 0 会随机选端口,设为空字符串则关闭。

3.2 声明静态端点

1
tailscale set --relay-server-static-endpoints=11.4.5.14:41642,[1919:810::]:41642

把公网 IPv4 和 IPv6 地址作为候选端点通告给其他节点。IPv6 必须带方括号,多个端点用逗号分隔。如果云服务器的公网 IP 变了,这一行必须重新执行,否则候选端点失效。

3.3 打标签

1
tailscale set --advertise-tags=tag:relay

tag:relay 是 Headscale ACL 里授权中继能力的锚点。

3.4 ACL

编辑 /etc/headscale/acl.hujson。核心是 grants 段里的两个条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"grants": [
{
"src": ["*"],
"dst": ["tag:relay"],
"ip": ["icmp:*"]
},
{
"src": ["*"],
"dst": ["tag:relay"],
"app": {
"tailscale.com/cap/relay": []
}
}
]

第一个 grant 看起来有点奇怪:ICMP 和中继有什么关系? 关系大了。Headscale 存在一个已知的 ICMP 可见性 bug,如果不显式授予 ICMP 权限,源设备可能 ping 不通中继节点,而 Peer Relay 的升级过程依赖节点可达性探测。少了这一条,客户端会“以为中继不可用”,然后默默回落到 DERP,你甚至在 tailscale status 里看不出任何异常。

第二个 grant 才是真正授予 tailscale.com/cap/relay 能力的条目。

改完文件后:

1
2
headscale policy check -f /etc/headscale/acl.hujson
sudo systemctl restart headscale

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
tailscale debug prefs | grep -A3 RelayServer

期望看到:

1
2
3
4
5
"RelayServerPort": 41642,
"RelayServerStaticEndpoints": [
"11.4.5.14:41642",
"[1919:810::]:41642"
]

4.2 端口监听

1
ss -ulnp | grep -e 41641 -e 41642

tailscaled 应同时监听 0.0.0.0:41642 和 [::]:41642。

4.3 客户端侧:看 tailscale ping

在客户端上执行:

1
tailscale ping <目标节点>

如果输出里出现 via DERP 以外的中继路径,或者 tailscale status 显示流量经过 tag:relay 节点中转,Peer Relay 才算真正生效。客户端版本也必须 1.86+,旧版本会直接忽略 relay 能力,静默回落 DERP。


后记

这一篇的“踩坑”密度比串流记(二)低一些 但也没好到哪里去

所以验证那一步不要跳过。这样串流用起来不烧心

Headscale + DERP + Peer Relay,三神装到位之后,你终于可以随时随地舒适地访问你的电脑了。

(完)