有时 VPC 里只有一台机器适合安装 Tailscale,但其他 VM 也需要访问 Tailscale 后面的远端内网段。这个场景可以把这台机器当成一个 proxy 节点:VPC 内机器把目标网段路由到 proxy,proxy 再通过 tailscale0 转发出去。

示例环境:

1
2
3
4
5
A 节点:      172.16.10.8
proxy 节点: 172.16.10.3 + tailscale0
远端网段: 192.168.81.0/24

A -> proxy -> tailscale0 -> 远端网段

VPC 内 VM 通过 Proxy 访问 Tailscale 网段拓扑

推荐做法是 NAT 网关模式:远端只看到 proxy 的 Tailscale IP,看不到 A 节点真实 IP。这样配置最少,也不需要改远端节点路由。

先看数据包怎么走

A 节点访问 192.168.81.248 时,原始包大致是:

1
2
src=172.16.10.8
dst=192.168.81.248

A 把包交给 proxy 后,proxy 要做两件事:

  1. 允许包从 eth0 转发到 tailscale0
  2. 把源地址改成 proxy 的 Tailscale 地址。

第一件事靠 FORWARD 规则。第二件事靠 MASQUERADE,也就是 NAT。

如果只有 FORWARD,没有 NAT,远端看到的来源还是:

1
172.16.10.8

但远端 Tailscale 网络通常不知道怎么回到 172.16.10.8,表现就是请求可能出去了,但回包回不来。

加了 NAT 后,远端看到的来源会变成:

1
proxy 的 Tailscale IP

这样远端只需要回给 proxy,Tailscale 网络天然知道怎么回。

一句话记住:

1
2
FORWARD 负责「能不能转发」
MASQUERADE 负责「远端能不能回包」

推荐模式:NAT 网关

NAT 模式适合这些场景:

  • VPC 内多台 VM 通过一个 proxy 访问 Tailscale 网段。
  • 不希望把整个 VPC 暴露进 Tailscale。
  • 不想改远端节点路由。
  • 远端只需要知道 proxy,不需要知道每台 VPC VM。

它的代价也很明确:远端看到的都是 proxy 的 Tailscale IP,不是 A 节点的真实 IP。

proxy 节点配置

下面假设:

1
2
3
4
VPC 网段:          172.16.10.0/24
proxy VPC 网卡: eth0
proxy Tailscale: tailscale0
远端网段: 192.168.81.0/24

启动 Tailscale 并接收远端路由

proxy 节点先启动 Tailscale,并接收远端发布的 subnet route:

1
2
3
4
tailscale up \
--login-server=https://haha.com \
--accept-routes=true \
--accept-dns=false

这里我要访问的192.168.81.0/24这个网段,首先确认远端网段确实在 Tailscale 路由里:

1
2
3
4
5
root@proxy:~# ip route show table 52 | grep 192.168.81
192.168.81.0/24 dev tailscale0

root@proxy:~# ip route get 192.168.81.248
192.168.81.248 dev tailscale0 table 52 src 100.125.6.212 uid 0

期望目标走 tailscale0。如果这里都没走 tailscale0,后面加 iptables 也没用。

开启 Linux 转发

1
2
3
4
5
6
7
cat >/etc/sysctl.d/99-proxy-forward.conf <<'EOF'
net.ipv4.ip_forward=1
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
EOF

sysctl --system

这里有两个关键配置:

配置 含义
net.ipv4.ip_forward=1 允许 proxy 帮其他机器转发 IP 包
rp_filter=0 避免多网卡、多路由表场景下,因为回程路径不完全一致而误丢包

rp_filter 在普通单网卡服务器上通常不容易碰到,但在 VPC 网卡、tailscale0、策略路由同时存在时,关掉它更稳。

配置转发和 NAT

1
2
3
4
5
6
7
iptables -A FORWARD -i eth0 -o tailscale0 -j ACCEPT

iptables -A FORWARD -i tailscale0 -o eth0 \
-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

iptables -t nat -A POSTROUTING \
-s 172.16.10.0/24 -o tailscale0 -j MASQUERADE

三条规则分别是:

规则 作用
eth0 -> tailscale0 ACCEPT 允许 VPC 到 Tailscale 的转发流量
tailscale0 -> eth0 RELATED,ESTABLISHED 只允许已建立连接的回包回来
POSTROUTING MASQUERADE 把 VPC VM 的源地址动态改成 proxy 的 Tailscale 地址

第二条只放行已建立连接的回包,不开放远端主动访问 VPC。

MASQUERADE 是什么

MASQUERADE 是 iptables NAT 表里的一种动态 SNAT。

普通 SNAT 要明确写死出口源地址:

1
2
3
iptables -t nat -A POSTROUTING \
-s 172.16.10.0/24 -o tailscale0 \
-j SNAT --to-source 100.x.x.x

这里的 100.x.x.x 是 proxy 的 Tailscale IP。

MASQUERADE 不需要写死这个 IP:

1
2
3
iptables -t nat -A POSTROUTING \
-s 172.16.10.0/24 -o tailscale0 \
-j MASQUERADE

它会在包从 tailscale0 发出去时,自动使用 tailscale0 当前 IP 作为源地址。

也就是说,它把:

1
2
src=172.16.10.8
dst=192.168.81.248

动态改成:

1
2
src=proxy 的 tailscale0 当前 IP
dst=192.168.81.248

这里用 MASQUERADE 的原因是 Tailscale 地址由 Tailscale 分配,重连、迁移、重新登录后都有可能变化。用 MASQUERADE 不需要关心当前 Tailscale IP 是多少。

两者区别可以这样记:

方式 特点 适合场景
SNAT 源地址写死 出口 IP 固定
MASQUERADE 自动取出口网卡当前 IP VPN、Tailscale、拨号网络这类出口地址可能变化的场景

A 节点或 VPC 路由配置

A 节点不需要安装 Tailscale,只需要让去往远端 Tailscale 网段的流量先到 proxy。

最直接的方式是在 A 节点上加静态路由:

1
ip route add 192.168.81.0/24 via 172.16.10.3 dev eth0

验证单机路由:

1
2
3
4
ip route get 192.168.81.248
192.168.81.248 via 172.16.10.3 dev eth0

ping 192.168.81.248

如果 A 上有多块网卡,dev eth0 要替换成能到达 proxy 的那块网卡。

如果 VPC 内有多台 VM 都要访问这个远端网段,更推荐把这条路由放到 VPC 路由器或云平台路由表里:

1
2
目标网段: 192.168.81.0/24
下一跳: 172.16.10.3,也就是 proxy 节点

这样整个 VPC 内的 VM 都可以通过 proxy 访问 192.168.81.0/24,不需要在每台 VM 上单独执行 ip route add

需要注意的是,这只是把“入口路由”集中到了 VPC 路由器上。proxy 节点上的 ip_forwardFORWARDMASQUERADE 仍然需要配置,否则包到了 proxy 以后还是转不出去,或者回包回不来。

如果云平台要求下一跳 VM 具备转发能力,还要在云平台侧开启对应的转发设置,或关闭类似 source/destination check 的限制。不同平台叫法不一样,核心都是允许 proxy 转发非本机源目的的流量。

排查思路

排查时按数据包路径一段一段确认,不要一上来就猜 NAT 或 Tailscale。

入口路由是否交给 proxy

1
ip route get 192.168.81.248

如果是在 A 节点上直接加的静态路由,期望看到 via 172.16.10.3

如果是在 VPC 路由器或云路由表里集中配置,A 节点上可能只看到默认网关,这是正常的。这时要检查 VPC 路由表里是否存在:

1
192.168.81.0/24 -> proxy 节点 172.16.10.3

也可以直接在 proxy 上抓 eth0

1
tcpdump -ni eth0 host 172.16.10.8

如果 A 发起访问时 proxy 的 eth0 能看到包,说明入口路由已经把流量交给 proxy。

proxy 是否走 Tailscale

1
2
ip route get 192.168.81.248
ip route show table 52 | grep 192.168.81

如果 proxy 本机去远端网段都不走 tailscale0,先查 Tailscale route,而不是查 A。

iptables 规则是否命中

1
2
iptables -L FORWARD -n -v
iptables -t nat -L POSTROUTING -n -v

看对应规则的 packet / byte 计数是否增长。

抓包看路径

在 proxy 上抓两侧接口:

1
2
tcpdump -ni eth0 host 172.16.10.8
tcpdump -ni tailscale0 host 192.168.81.248

常见判断:

现象 优先检查
eth0 有包,tailscale0 没包 ip_forwardFORWARD、proxy 路由
tailscale0 有出包,但 A 收不到回包 MASQUERADE、回包 conntrack、远端防火墙
proxy 自己也访问不了远端 Tailscale route、远端 subnet route 是否发布和审批

不加 NAT 可以吗

可以,但那就变成纯路由模式。

纯路由模式下,远端会看到 A 的真实来源:

1
172.16.10.8

所以必须让 Tailscale 网络知道怎么回到 VPC 网段。也就是在 proxy 上发布 VPC 网段:

1
2
3
4
5
tailscale up \
--login-server=https://haha.com \
--accept-routes=true \
--accept-dns=false \
--advertise-routes=172.16.10.0/24

然后在 Tailscale 管理后台审批这个路由。

两种模式对比:

模式 优点 代价
NAT 模式 配置简单,远端只看到 proxy,推荐日常使用 远端看不到 A 的真实 IP
纯路由模式 远端能看到 A 的真实 IP 需要发布 VPC 网段,并在后台审批路由

大多数场景下,我会先用 NAT 模式。只有远端必须区分具体 VPC VM 时,才考虑纯路由模式。

不要把公网网段随便丢给 proxy

比如这条路由:

1
ip route add 139.198.183.0/24 via 172.16.10.3

只有在 proxy 的 Tailscale 路由里也存在这个网段时才有意义:

1
ip route show table 52 | grep 139.198.183

如果这个网段不在 Tailscale 路由里,A 把包交给 proxy 也没用。公网 HTTP / HTTPS 访问更适合用应用层代理:

1
2
export HTTP_PROXY=http://proxy:port
export HTTPS_PROXY=http://proxy:port

路由和代理解决的是不同层的问题,不要混在一起。

总结

这个方案的核心很简单:

  • 其他 VM 或 VPC 路由器:把 Tailscale 远端网段 route 到 proxy。
  • proxy:负责 ip_forwardFORWARD 放行、MASQUERADE 改源地址。
  • FORWARD:决定包能不能穿过 proxy。
  • MASQUERADE:动态 SNAT,决定远端回包能不能回到 proxy,再回到 A。

最终结论:

1
2
让其他 VM 通过 proxy 访问 Tailscale 网段时,光能转发还不够。
如果不发布 VPC 回程路由,就必须做 NAT。