Tailscale 非对称路由导致 LAN 设备无法访问同网段 Tailscale 节点

温馨提醒
旁路由 + Tailscale + 同网段节点,LAN 设备 ping 得通远程节点却 ping 不通同局域网的 Tailscale 节点?rp_filter 在作怪,SNAT 一行解决。

前置问题

本文是 Tailscale 子网路由劫持本地流量排查与修复 的后续。解决了路由劫持之后,又发现了一个新问题。

发生了什么

Windows 关闭 Tailscale 后(纯 LAN 设备),ping 其他 Tailscale 节点:

1
2
3
4
100.80.0.1 (iStoreOS)    ✅ 通
100.80.0.3 (chengdu)     ✅ 通
100.80.0.4 (jing)        ✅ 通
100.80.0.2 (dbroot)      ❌ 不通!卡住不动

tracert 显示: 第一跳到 192.168.1.3(iStoreOS),第二跳就超时——说明请求到了,回复丢了。

为什么偏偏是 dbroot?

因为 dbroot 和 Windows 在同一个物理局域网里。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
远程节点 (chengdu/jing):
  请求路径:Windows → iStoreOS → tailscale隧道 → 远程节点
  回复路径:远程节点 → tailscale隧道 → iStoreOS → Windows
  路径完全对称 ✅

本地节点 (dbroot):
  请求路径:Windows → iStoreOS → tailscale0 → 100.80.0.2 (dbroot)  ✅ 到达
  回复路径:dbroot 收到后查路由表:
           "192.168.1.10?这不就在我隔壁吗?走 LAN 直连!"
           dbroot → LAN直连 → 192.168.1.10
  路径不对称 ❌

回复从 tailscale0 进来,却从 LAN 口出去——Linux 内核的 rp_filter(反向路径过滤) 认为这不正常,直接丢包。

为什么开启 Tailscale 就没问题

开启 Tailscale 后,Windows 的源 IP 从 192.168.1.10 变成了 100.80.0.6

1
2
源 IP 100.80.0.6 → dbroot 收到 → 查路由表 → "100.80.0.6 走 tailscale0"
→ 回复从 tailscale0 出去 → 入=出=tailscale0 → 对称 ✅

源 IP 不同,回复路径不同,就这么简单。

为什么 ZeroTier 没这问题

ZeroTier 是二层虚拟交换机,所有设备共享一个虚拟网段。同网段通信直接走虚拟交换机,不经过网关,没有非对称路径。Tailscale 是三层的,每个节点独立路由,旁路由转发时就埋下了非对称的坑。

怎么修

方案一:在 iStoreOS 上做 SNAT(已实施)

思路: 让 LAN 发往 Tailscale 的流量在 iStoreOS 上转换源地址,对外伪装成 100.80.0.1。这样回复路径永远对称,rp_filter 不触发。

1
2
3
4
5
修复前:
  源 IP: 192.168.1.10 → dbroot 收到 → 回复走 LAN → 非对称 ❌

修复后:
  源 IP: 100.80.0.1 (iStoreOS SNAT) → dbroot 收到 → 回复走 tailscale0 → 对称 ✅

实施:

在 iStoreOS 上创建 nftables 规则文件 /usr/share/nftables.d/chain-post/srcnat/30-tailscale-masq.nft

1
ip saddr 192.168.1.0/24 oifname "tailscale0" masquerade comment "Tailscale MASQ for LAN"

重启防火墙生效:

1
fw4 restart

验证:

1
2
nft list chain inet fw4 srcnat | grep -i tailscale
# 应该看到那条 masquerade 规则

方案二:在 dbroot 上关闭 rp_filter

思路: 直接告诉内核不要检查 tailscale0 接口的反向路径。

1
2
3
4
5
6
# 临时生效(重启失效)
echo 0 > /proc/sys/net/ipv4/conf/tailscale0/rp_filter

# 或者全关
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.tailscale0.rp_filter=0

持久化:

1
2
3
# 写入 /etc/sysctl.d/99-tailscale.conf
echo "net.ipv4.conf.tailscale0.rp_filter = 0" >> /etc/sysctl.d/99-tailscale.conf
sysctl -p /etc/sysctl.d/99-tailscale.conf

两个方案对比

方案一:iStoreOS SNAT方案二:关 rp_filter
配置位置只在 iStoreOS 上一处每台同局域网的 Tailscale 节点都要配
新增节点自动生效新节点需要手动配
安全性不降低内核安全策略关闭了反向路径检查,降低了安全性
源 IP 可见性远程节点看到的是 100.80.0.1(iStoreOS)远程节点能看到真实源 IP
适用场景不需要区分 LAN 内具体设备需要区分来源(比如日志审计)
维护成本低,一次配完中,每台都要配

为什么选了方案一:

这个网络里 LAN 设备访问 Tailscale 节点时,不需要区分"请求来自哪台设备"。就算需要区分,LAN 设备也可以直接通过 Tailscale 客户端用 100.80.0.x 身份访问,那时的源 IP 就是真实的。方案一一次配置解决所有问题,更省心。

为什么不两全其美

两个方案本质上是冲突的——做了 SNAT 就不需要关 rp_filter,关了 rp_filter 就不需要 SNAT。如果两个都做,反而多了一层 NAT 的性能开销,且没有额外收益。

最终效果

修完之后,整个网络的访问逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
LAN 设备(无 Tailscale)
  → 192.168.1.3 网关 → NAT → 100.80.0.1
  → tailscale → 远程节点
  ✅ 全通

Windows(有 Tailscale,接口跃点 1000)
  → 192.168.1.x 走物理网卡(跃点 276)
  → 100.80.0.x 走 Tailscale(跃点 1000)
  ✅ 两不抢

远程 Tailscale 节点
  → 子网路由 192.168.1.0/24 → iStoreOS → LAN
  ✅ 不变

知识点

  • rp_filter: Linux 内核的安全机制,检查包的入接口和路由表决定的出接口是否一致,不一致就丢
  • 非对称路由: 请求和回复走不同路径,在网络拓扑复杂(旁路由 + VPN + 同网段)时容易触发
  • SNAT/MASQUERADE: 不仅能解决"外网访问内网"的问题,也能解决"非对称路由"的问题——本质上都是让回复路径和请求路径一致