0%

VPS 既是 Headscale 节点又是 Tailscale 节点:实现双网互通

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 进程,分别通过 tailscale0tailscale-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 实例的状态
tailscale status # 官方侧 (tailscale0)
sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock status # headscale 侧 (tailscale-hs0)

# 查看 IP 地址
tailscale ip # VPS 在官方网络的 IP
sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock ip # 100.64.0.2

# 查看 Headscale 节点列表
sudo headscale nodes list

# 查看 headscale 配置的 IP 段
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
# FORWARD 双向放行
iptables -I FORWARD -i tailscale0 -o tailscale-hs0 -j ACCEPT
iptables -I FORWARD -i tailscale-hs0 -o tailscale0 -j ACCEPT

# SNAT:Headscale → Tailscale 官方(流量从 tailscale-hs0 进入、从 tailscale0 出去时改写源 IP)
iptables -t nat -A POSTROUTING -o tailscale0 -j SNAT --to-source <TS_VPS_IP>

# SNAT:Tailscale 官方 → Headscale(从 tailscale0 进入、从 tailscale-hs0 出去时改写源 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

# 创建 systemd service
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
# 用 Headscale 侧 tailscale 实例广播子网路由
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
# 查看待批准路由(`<N>` 为 VPS 在 headscale 中的节点 ID)
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 规则
iptables -S FORWARD | grep tailscale
iptables -t nat -L POSTROUTING -n -v

# 查看 Headscale 路由批准状态
sudo headscale nodes list-routes --identifier <N>

# Tailscale ping 测试(Tailscale 自身的 ping 工具能绕过对端 ICMP 限制)
tailscale ping <TS_Node_IP> # VPS → Tailscale 官方节点
sudo tailscale --socket=/run/tailscale-headscale/tailscaled.sock ping 100.64.0.1 # VPS → Headscale 节点

在两个网络的其他节点上互相 ping 或访问服务,确认双向可达。

注意事项

  1. IP 段不冲突的前提:Headscale 节点全部在 100.64.0.0/24 内,与 Tailscale 官方分配的 IP 没有实际重叠。如果将来 Headscale 节点数量增加,需要调整路由广播的粒度。

  2. SNAT 导致源 IP 丢失:对端看到的连接源 IP 始终是 VPS 的地址,而非原始发起节点的 IP。如果应用层需要识别真实来源,需要用其他方式传递。

  3. 防火墙干扰:Tailscale 节点之间 ICMP ping 不通很可能是对端防火墙拦截,不影响 TCP/UDP 服务访问。tailscale ping 命令能更准确地验证连通性。

  4. 重启恢复:iptables 规则通过 systemd service 在启动时自动加载,路由广播信息存储在 tailscale 本地状态中,重启后会自动恢复。

总结

在不修改 Headscale 地址池的前提下,通过 iptables SNAT 双向桥接 + 精确子网路由广播,实现了 Headscale 私有网络与 Tailscale 官方网络之间的互通。核心依赖 VPS 作为两个网络的唯一交汇点,配置完成后两侧节点可以像在同一网络内一样互相访问。