
VPS 既是 Headscale 节点又是 Tailscale 节点:实现双网互通
背景
有一台云 VPS,同时承担了两个角色:
- Headscale 服务器:自建的 Tailscale 协调服务器,管理一个私有网络。
- Tailscale 官方节点:同时加入了 Tailscale 官方 SaaS 网络。
两个网络各自运行,Headscale 网络内的节点和 Tailscale 官方网络内的节点无法互相访问。由于 VPS 是两个网络的唯一交汇点,利用它做路由转发即可打通。
网络拓扑
1 2 3 4 5 6 7 8
| Headscale 网络 (私网) VPS Tailscale 官方网络 ┌──────────────────┐ ┌─────────────────────────┐ ┌──────────────────┐ │ Desktop │ │ tailscale-hs0: 100.64.0.2│ │ Mac │ │ 100.64.0.1 │◄────►│ tailscale0: <TS_VPS_IP> │◄──►│ <TS_Node_B_IP> │ │ │ │ │ │ │ │ Laptop │ │ headscale server │ │ Server-B │ │ 100.64.0.3 │ │ iptables SNAT bridge │ │ <TS_Node_C_IP> │ └──────────────────┘ └─────────────────────────┘ └──────────────────┘
|
VPS 上运行两个独立的 tailscaled 进程,分别通过 tailscale0 和 tailscale-hs0 两张虚拟网卡连接两个网络。
核心问题:IP 地址段重叠
Headscale 默认的地址池是 100.64.0.0/10,Tailscale 官方同样使用 100.64.0.0/10。两个网络分配的具体 IP 如下:
- Headscale 节点:
100.64.0.1, 100.64.0.2, 100.64.0.3(占用 100.64.0.0/24 子网)
- Tailscale 官方节点:
100.72.x.x ~ 100.126.x.x(实际分散在 100.64.0.0/10 的其余空间中)
虽然网段重叠,但 Headscale 节点实际只用了 100.64.0.0/24,Tailscale 官方没有分配这个子网内的地址。因此可以通过精确路由广播 + SNAT 实现互通。
核心思路:SNAT 双向桥接
VPS 作为中间人,通过 iptables SNAT 让两个网络的流量看起来都是从 VPS 发出的。
- Headscale 节点 → Tailscale 官方节点:流量从
tailscale-hs0 进入 VPS,经内核路由从 tailscale0 出去,同时做 SNAT 将源 IP 改写为 VPS 在官方网络的 IP(记为 <TS_VPS_IP>)。
- Tailscale 官方节点 → Headscale 节点:流量从
tailscale0 进入,从 tailscale-hs0 出去,SNAT 将源 IP 改写为 100.64.0.2(VPS 在 Headscale 网络的 IP)。
配合子网路由广播,两个网络的节点都知道应该把去往对方的流量送到 VPS。
配置步骤
以下所有操作在 VPS 上执行。
1. 确认现状
1 2 3 4 5 6 7 8 9 10 11 12 13
| tailscale status sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock status
tailscale ip sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock ip
sudo headscale nodes list
grep -A5 "ip_prefixes" /etc/headscale/config.yaml
|
确认两件事:VPS 在两个网络中的 IP(后续 SNAT 要用),以及 VPS 在 Headscale 中的节点 ID(后续批准路由要用)。
两个 tailscaled 进程应分别由独立的 systemd service 管理,官方侧通常是默认的 tailscaled.service,Headscale 侧需要单独创建(本例中为 tailscaled-headscale.service)。
2. 开启 IP 转发
1 2 3 4 5 6 7 8 9
| sysctl -w net.ipv4.ip_forward=1 sysctl -w net.ipv6.conf.all.forwarding=1
cat > /etc/sysctl.d/99-tailscale.conf << EOF net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 EOF
|
3. 配置 iptables SNAT 和 FORWARD
1 2 3 4 5 6 7 8 9
| iptables -I FORWARD -i tailscale0 -o tailscale-hs0 -j ACCEPT iptables -I FORWARD -i tailscale-hs0 -o tailscale0 -j ACCEPT
iptables -t nat -A POSTROUTING -o tailscale0 -j SNAT --to-source <TS_VPS_IP>
iptables -t nat -A POSTROUTING -o tailscale-hs0 -j SNAT --to-source 100.64.0.2
|
<TS_VPS_IP> 替换为 VPS 在 Tailscale 官方网络的 IP(通过 tailscale ip 获取)。
4. 持久化 iptables 规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| cat > /etc/tailscale-bridge-iptables.sh << 'EOF' #!/bin/bash iptables -C FORWARD -i tailscale0 -o tailscale-hs0 -j ACCEPT 2>/dev/null || \ iptables -I FORWARD -i tailscale0 -o tailscale-hs0 -j ACCEPT iptables -C FORWARD -i tailscale-hs0 -o tailscale0 -j ACCEPT 2>/dev/null || \ iptables -I FORWARD -i tailscale-hs0 -o tailscale0 -j ACCEPT iptables -t nat -C POSTROUTING -o tailscale-hs0 -j SNAT --to-source 100.64.0.2 2>/dev/null || \ iptables -t nat -A POSTROUTING -o tailscale-hs0 -j SNAT --to-source 100.64.0.2 EOF chmod +x /etc/tailscale-bridge-iptables.sh
cat > /etc/systemd/system/tailscale-bridge-iptables.service << 'EOF' [Unit] Description=Restore iptables for tailscale-headscale bridge After=network-online.target tailscaled.service tailscaled-headscale.service Wants=network-online.target
[Service] Type=oneshot RemainAfterExit=yes ExecStart=/etc/tailscale-bridge-iptables.sh
[Install] WantedBy=multi-user.target EOF
systemctl daemon-reload systemctl enable tailscale-bridge-iptables.service
|
5. Headscale 侧:向 Headscale 网络广播 Tailscale 官方子网路由
在 Headscale 侧广播 Tailscale 官方节点所在的网段,排除 100.64.0.0/24(Headscale 自用段)。
1 2 3 4 5 6
| sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock up \ --advertise-routes=100.64.1.0/24,100.65.0.0/16,100.66.0.0/15,100.68.0.0/14,100.72.0.0/13,100.80.0.0/12,100.96.0.0/11 \ --accept-routes \ --advertise-exit-node \ --reset
|
批准路由(Headscale 管理面需要手动 approve):
1 2 3 4 5 6 7
| sudo headscale nodes list-routes --identifier <N>
sudo headscale nodes approve-routes \ --identifier <N> \ --routes "0.0.0.0/0,100.96.0.0/11,100.80.0.0/12,100.72.0.0/13,100.68.0.0/14,100.66.0.0/15,100.65.0.0/16,100.64.1.0/24,::/0"
|
执行后 headscale nodes list-routes 会看到所有路由状态变为 Serving。
6. Tailscale 官方侧:向官方网络广播 Headscale 子网
1 2 3
| sudo tailscale up \ --advertise-routes=100.64.0.0/24 \ --accept-routes
|
这一步完成后,登录 Tailscale Admin Console,找到 VPS 对应的那台机器,在 Edit route settings 中批准 100.64.0.0/24 路由。
7. 让 Headscale 客户端接受路由
在所有 Headscale 客户端节点上执行:
1
| tailscale up --accept-routes
|
验证
在 VPS 上确认规则生效:
1 2 3 4 5 6 7 8 9 10
| iptables -S FORWARD | grep tailscale iptables -t nat -L POSTROUTING -n -v
sudo headscale nodes list-routes --identifier <N>
tailscale ping <TS_Node_IP> sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock ping 100.64.0.1
|
在两个网络的其他节点上互相 ping 或访问服务,确认双向可达。
注意事项
IP 段不冲突的前提:Headscale 节点全部在 100.64.0.0/24 内,与 Tailscale 官方分配的 IP 没有实际重叠。如果将来 Headscale 节点数量增加,需要调整路由广播的粒度。
SNAT 导致源 IP 丢失:对端看到的连接源 IP 始终是 VPS 的地址,而非原始发起节点的 IP。如果应用层需要识别真实来源,需要用其他方式传递。
防火墙干扰:Tailscale 节点之间 ICMP ping 不通很可能是对端防火墙拦截,不影响 TCP/UDP 服务访问。tailscale ping 命令能更准确地验证连通性。
重启恢复:iptables 规则通过 systemd service 在启动时自动加载,路由广播信息存储在 tailscale 本地状态中,重启后会自动恢复。
总结
在不修改 Headscale 地址池的前提下,通过 iptables SNAT 双向桥接 + 精确子网路由广播,实现了 Headscale 私有网络与 Tailscale 官方网络之间的互通。核心依赖 VPS 作为两个网络的唯一交汇点,配置完成后两侧节点可以像在同一网络内一样互相访问。