在 Linux VM 里启动 Tailscale 后,当前 SSH 连接立刻断开。这个问题看起来像 sshd 异常,但很多时候真正原因是:Tailscale 接收了子网路由,并插入了优先级更高的策略路由,导致 SSH 回包被导向 tailscale0

现象

执行:

1
tailscale up --accept-routes=true

当前 SSH 连接马上断开。

重新登录后直接看原始输出:

1
2
3
4
5
6
7
ip rule
5270: from all lookup 52
32766: from all lookup main

ip route show table 52
192.168.39.0/24 dev tailscale0
10.16.11.0/24 dev tailscale0

重点看两处:

  • 5270: from all lookup 52table 52 排在 main 前面。
  • 192.168.39.0/24 dev tailscale0:SSH 客户端所在网段可能被 Tailscale 接管。

这说明 Tailscale 的 table 52 比系统默认的 main 路由表更早被查询。如果 SSH 客户端所在网段也被 Tailscale 接管,SSH 回程就可能被抢走。

先确认 SSH 客户端 IP

不要先猜网段,先看当前 SSH 连接来自哪里:

1
2
ss -tnpa | grep sshd
ESTAB 0 0 172.16.10.3:22 192.168.39.3:61769 users:(("sshd",pid=1234,fd=4))

这里服务端本地地址是 172.16.10.3:22,SSH 客户端地址是:

1
192.168.39.3

后面的排查都要围绕这个客户端 IP 做,不要凭印象写一个管理网段。

查看 SSH 回程走哪里

拿 SSH 客户端 IP 查路由。如果回程被 Tailscale 抢走,通常会看到类似输出:

1
2
3
ip route get 192.168.39.3
192.168.39.3 dev tailscale0 table 52 src 100.64.0.10
cache

重点看 dev tailscale0table 52,这说明 SSH 回包会从 Tailscale 出去。

正常情况下,回程应该走 VM 原来的网卡,例如:

1
2
3
4
ip route get 192.168.39.3
192.168.39.3 via 172.16.10.1 dev eth0
src 172.16.10.3
cache

这里的 eth0、网关和网段都要按真实环境替换。

查看 Tailscale 策略路由

继续看策略路由和 Tailscale 路由表,建议把命令和输出一起贴出来:

1
2
3
4
5
6
7
ip rule
5270: from all lookup 52
32766: from all lookup main

ip route show table 52
192.168.39.0/24 dev tailscale0
10.16.11.0/24 dev tailscale0

重点看两件事:

  1. lookup 52 是否排在 lookup main 前面。
  2. table 52 里是否有 SSH 客户端所在网段,比如 192.168.39.0/24 dev tailscale0

Linux 策略路由里,pref 数值越小,优先级越高。这里 5270 小于 32766,所以会先查 Tailscale 的 table 52,再查 main 表。

根因

SSH 请求从 VM 原网卡进来:

1
client -> eth0 -> sshd

但启动 Tailscale 并接收子网路由后,回包查路由时先命中 table 52

1
sshd -> tailscale0 -> client

请求从 eth0 进,回包却从 tailscale0 出,路径不一致,当前 SSH 连接就会断开。

所以这个问题的核心不是 sshd 挂了,而是 SSH 管理流量的回程路由被改了。

临时修复

给 SSH 客户端网段加一条更高优先级规则,强制走 main 表:

1
ip rule add to 192.168.39.0/24 pref 90 lookup main

这里的 90 小于 Tailscale 的 5270,所以会先命中这条规则。

验证时也直接看命令输出:

1
2
3
4
ip route get 192.168.39.3
192.168.39.3 via 172.16.10.1 dev eth0
src 172.16.10.3
cache

重点看 dev eth0。如果仍然是 dev tailscale0,说明网段、客户端 IP 或规则优先级需要继续检查。

DNS 也要检查

有时 SSH 恢复后,DNS 仍然异常。可以先拿当前 DNS 服务器地址:

1
2
DNS=$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf)
echo "$DNS"

看 DNS 回程是否也被导到 tailscale0

1
2
3
ip route get "$DNS"
10.16.11.2 dev tailscale0 table 52 src 100.64.0.10
cache

重点看 dev tailscale0。如果 DNS 不在管理网段内,并且也走了 tailscale0,可以单独保护 DNS IP:

1
ip rule add to "$DNS"/32 pref 91 lookup main

再验证:

1
2
3
4
ip route get "$DNS"
10.16.11.2 via 172.16.10.1 dev eth0
src 172.16.10.3
cache

持久化配置

可以把规则写进 /etc/rc.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat >/etc/rc.local <<'EOF'
#!/bin/sh
set -e

# 保护 SSH / 管理网段,避免被 Tailscale table 52 抢走
ip rule del to 192.168.39.0/24 pref 90 lookup main 2>/dev/null || true
ip rule add to 192.168.39.0/24 pref 90 lookup main

# 如 DNS 不在管理网段,可单独保护 DNS IP
# ip rule del to <DNS_IP>/32 pref 91 lookup main 2>/dev/null || true
# ip rule add to <DNS_IP>/32 pref 91 lookup main

exit 0
EOF

chmod +x /etc/rc.local

再创建 rc-local.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat >/etc/systemd/system/rc-local.service <<'EOF'
[Unit]
Description=/etc/rc.local Compatibility
After=network-online.target tailscaled.service
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/etc/rc.local
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable rc-local
systemctl start rc-local

检查规则是否生效:

1
2
ip rule
ip route get 192.168.39.3

防止把自己锁外面

测试 Tailscale 前,可以先放一个自动恢复:

1
nohup sh -c 'sleep 90; tailscale down' >/tmp/ts-rescue.log 2>&1 &

如果 90 秒内 SSH 没断,或者已经确认规则没问题,可以忽略它;如果 SSH 被断开,Tailscale 会在 90 秒后自动关闭,方便重新登录。

也可以开两个 SSH 窗口:一个执行 Tailscale 操作,一个专门观察路由和连接状态。

排查清单

完整顺序可以固定成下面这几步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 先确认 SSH 客户端 IP
ss -tnpa | grep sshd

# 2. 看 SSH 回程走原网卡还是 tailscale0
ip route get <client_ip>

# 3. 看策略路由优先级
ip rule

# 4. 看 Tailscale 接管了哪些网段
ip route show table 52

# 5. 保护 SSH / DNS 管理流量
ip rule add to <client_subnet> pref 90 lookup main

一句话总结:启动 Tailscale 后 SSH 断连,先查 ip route get <ssh_client_ip>。只要回程落到 tailscale0,就优先怀疑 Tailscale 的 table 52 抢了管理网段路由。