剪藏#剪藏#非典型开发者

几步搞定headscale + tailscale局域网异地组网互通

2026 年 8 月 29 日1 分钟
分享Twitter / XTelegram微博
几步搞定headscale + tailscale局域网异地组网互通 封面

本文将介绍两种常见的异地组网场景配置方法。假设家庭局域网为 192.168.1.0/24

Headscale + Tailscale 异地组网实践

场景一:单向访问局域网(出门在外访问家里设备)

通过在家中配置一台转发设备,让外网设备能够访问家里的局域网。

1. 转发设备(子网路由器,家里那台)

开启内核转发

JAVASCRIPT
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

验证sysctl net.ipv4.ip_forward 返回 1。

启动 Tailscale

JAVASCRIPT
tailscale up --login-server=https://your-headscale --accept-routes --advertise-routes=192.168.1.0/24

两个参数可以共存,作用互不干扰:

如果这台只做单向出口(只提供不需要访问别的局域网),可以不加 --accept-routes;加上也无害。

防火墙:如果用了 ufw,把 /etc/default/ufwDEFAULT_FORWARD_POLICY 改成 ACCEPT

2. Headscale 服务端

批准路由

JAVASCRIPT
headscale nodes approve-routes -i <节点ID> -r 192.168.1.0/24

注意事项

  • -r全量覆盖,不是追加。多条要一次写全:-r 192.168.1.0/24,10.0.0.0/24

  • • 不带 -r 执行 = 清空所有已批准路由

  • -r "" 显式清空

  • -r 的网段必须和节点广播的字面完全一致,不匹配的条目不报错但不生效

确认结果

JAVASCRIPT
headscale nodes list-routes

ApprovedServing (Primary) 两列都出现该网段才算成功。只有 Approved 说明节点没在广播。

3. 其他局域网设备(访问端)

开启接受路由

JAVASCRIPT
tailscale set --accept-routes
  • • Linux 默认关闭,必须显式开

  • • macOS / Windows / iOS / Android 默认开启,对应 GUI 选项为 Use Tailscale subnets(中文界面「使用 Tailscale 子网」)

  • tailscale set 只改这一项,不会覆盖其他已有设置

确认收到路由

JAVASCRIPT
# 客户端视角:哪个 peer 提供了哪些网段
tailscale status --json | jq -'.Peer[] | select(.PrimaryRoutes) | "\(.HostName): \(.PrimaryRoutes | join(","))"'

# 系统视角:路由是否装进内核
ip route | grep tailscale        # macOS: netstat -rn | grep 192.168

tailscale status 普通输出不显示子网网段,只列节点和 100.x 地址,看不到是正常的。

排查对照

现象 原因 Approved 有、 Serving 空 节点没在广播,或网段字面不一致 服务端 Serving 有、客户端 PrimaryRoutes 空 客户端没重连,或 ACL 拦截 客户端 PrimaryRoutes 有、 ip route 无 --accept-routes 没开,或本地网段撞车 路由都正常但 ping 不通 转发未开、防火墙 FORWARD 链、返程路由

网段撞车:客户端所在局域网若与远程网段相同(如两边都是 192.168.1.0/24),本地直连路由优先,远程永远访问不到。只能改掉其中一边的网段,改完重新广播并批准。

返程路由:局域网内其他设备回包给 100.64.0.0/10 会走默认网关而丢失。转发设备的 --snat-subnet-routes=true(默认值)会做 SNAT 规避此问题,代价是内网侧看不到真实来源 IP。

场景二:双向局域网互通(site-to-site)

目标:两个局域网内的设备都不装 tailscale,也能互相访问。做法是把 tailscale 装在两边的软路由(网关)上,互为对方的子网路由器。

示例:局域网 1 为 192.168.10.0/24,局域网 2 为 192.168.20.0/24

下面两条命令中的 --snat-subnet-routes--stateful-filtering 仅 Linux 可用,详见本节末尾的参数说明。

软路由 A(局域网 1)

JAVASCRIPT
tailscale up --login-server=https://your-headscale \
  --advertise-routes=192.168.10.0/24 \
  --accept-routes \
  --snat-subnet-routes=false \
  --stateful-filtering=false

软路由 B(局域网 2)

JAVASCRIPT
tailscale up --login-server=https://your-headscale \
  --advertise-routes=192.168.20.0/24 \
  --accept-routes \
  --snat-subnet-routes=false \
  --stateful-filtering=false

headscale 分别批准

JAVASCRIPT
headscale nodes approve-routes -i <A的ID> -r 192.168.10.0/24
headscale nodes approve-routes -i <B的ID> -r 192.168.20.0/24

为什么内网设备不用装 tailscale

192.168.10.10 访问 192.168.20.20 时,包发给默认网关(软路由 A),A 的路由表里有 192.168.20.0/24 dev tailscale0,直接转发。回程同理。因为 tailscale 就跑在网关本身,不需要在客户端或路由器上额外加静态路由——这是装在软路由上最大的好处。

两个额外参数

⚠️ 这两个都是 Linux 专属参数。在 macOS / Windows 上跑 tailscale up -h 看不到它们,属正常现象(同样看不到的还有 --netfilter-mode)。确认时务必在软路由本机上执行 help,不要在 mac 上查。

两者在 tailscale uptailscale set 上都支持,官方 site-to-site 文档用的就是 tailscale up 写法。

--snat-subnet-routes=false

  • • 默认开启 SNAT,会把源地址改写成对端路由器的内网 IP。单向访问时它能掩盖返程路由问题,但双向互通场景下会让日志、ACL、基于源 IP 的访问控制全部失真。关掉后保留真实源地址。

  • • 关掉后必须解决返程路由:内网设备回包给对端网段时要能找到路。在软路由做网关的场景下天然满足(网关自己就有路由),但如果 tailscale 装在非网关的机器上,则需要在路由器上加静态路由。

  • 已知问题:同一节点既做子网路由器又做 exit node 时,设置此项可能导致上游丢包。官方建议把两个角色拆到不同节点。软路由若同时开了 --advertise-exit-node 要注意。

  • • 官方还建议配合 iptables 做 MSS clamping(--clamp-mss-to-pmtu),避免大包在隧道里分片失败。

--stateful-filtering=false

  • • 有状态过滤会丢弃「不属于已有连接」的入站包,导致只能单向发起连接。

  • • 1.66 引入时默认开启,造成大量 site-to-site 断连;当前版本已改回默认关闭,所以多数情况可以省略这个参数。

  • • 显式写上仍然稳妥(防止某些发行版打包时改了默认值),但如果你的版本报 flag 不存在,说明版本过老,直接去掉即可。

  • • 反过来说:关掉它意味着子网路由器会接受任意入站包,配合 SNAT 关闭时存在被诱导发起大量 wg 握手的 DoS 风险。内网环境可接受,暴露在不可信网络时需自行补防火墙规则。

补充说明

HA 场景注意:如果两台软路由广播的是同一个网段(主备冗余),不要给它们都加 --accept-routes——备机会接受主机广播的路由,导致本该直连的流量绕道主机。本文档的两网段互通场景不受影响。

其余要求:两台软路由都要开内核转发(同第 1 节);headscale 若配了自定义 ACL,需放行两网段互访,默认全通则不用管。

验证顺序:先从软路由 A 上 ping 192.168.20.20——通了说明隧道和路由没问题,再从 192.168.10.10 ping 一次;后者不通而前者通,问题就在转发或防火墙 FORWARD 链。

原文地址: https://mp.weixin.qq.com/s?__biz=MzkzNzk0MDgxNQ==&mid=2247483917&idx=1&sn=c403c1afdc3fb0bbb8cfbee174799b65&chksm=c39d67b6bbabdcb2f0c6074d8bdc9ff9ebd5dc6df5d199a9e47814499e092dc5fb21d1eee13c&mpshare=1&scene=1&srcid=0828W0zDNCpZYDU005YIIye6&sharer_shareinfo=272b0d71abb5a4523ef93a6d5b5a232e&sharer_shareinfo_first=272b0d71abb5a4523ef93a6d5b5a232e#rd

相关文章