最近在折腾各种 AI agent,家里的工作站、公司的开发机、云上的几台机器散落各处。不管是随时连上去看实时日志、调试环境,还是让机器上的 agent 之间直接打通 RPC 通信,Tailscale 早就成了我的刚需:不需要自己去配复杂的 WireGuard 拓扑或者维护一堆端口映射,自动打洞、身份明确的 mesh 网络用起来确实省心。
但在国内用 Tailscale,大家都有一个共同的痛点:官方的 DERP 中继节点基本都在境外(香港、东京、新加坡等)。只要两端设备的 NAT 稍微复杂一点(比如一端是公司严苛的对称型 NAT,另一端是移动 5G),打洞直连失败就只能退化到中继模式。这时候走官方 DERP,延迟瞬间飙到上百毫秒,日常传点大文件或者交互式敲命令时,卡顿感相当明显。
手头正好有一台闲置的阿里云 ECS,我很自然地动了心思:自己搭一个国内的 DERP 节点,给中继链路提速。
本以为这就是个把容器拉起来、签个证书半小时就能收工的常规操作,没想到后面一脚踩进了一个层层套娃的泥潭。
前提:DERP 绝不能被白嫖
自建 DERP 本身并不复杂,官方仓库就开源了 derper 工具。但我心里很清楚,国内云主机的公网带宽可都是真金白银,防蹭用是第一原则。
DERP 转发的是端到端加密的数据包,服务器本身虽然解不开内容,但只要中继端口完全暴露在公网,全网扫描的爬虫和白嫖党分分钟能把你的出口带宽吃满,月底账单会非常感人。
怎么鉴权?
- 靠 IP 白名单?移动设备的公网 IP 天天变,总不能随身带个脚本天天去云控制台改安全组;
- 官方最正统的方案是加上启动参数:
derper --verify-clients。
这个参数的实现逻辑很优雅:derper 通过本机的 Unix Domain Socket 直接与宿主机上运行的 tailscaled 交互。当有客户端发起中继握手时,derper 让 tailscaled 去控制面校验对方的公钥身份——只有真正属于自己 Tailnet 的设备,才允许使用中继。
这个方案安全、省心,不需要自己手工分发任何静态密码或 token。但它带来了一个前提条件:这台阿里云 VM 本身,也必须安装并运行一个 tailscaled 守护进程。
我把容器镜像拉下来、配好域名、用 ACME 自动签下证书,把防火墙端口放行。一切顺风顺水,我甚至觉得马上就能连上去测速了。
然后,机器的网络瞬间“暴毙”。
意料之外的 CIDR 冲突
tailscaled 按默认配置装好,一执行 tailscale up,VM 的网络瞬间瘫痪:
终端里随便敲个 curl 或者跑 dnf makecache,全部卡死在域名解析上,报 DNS 超时。
我停掉 tailscaled 服务,网络立刻秒恢复;一拉起服务,系统立刻又变成“半残”。
稍微看一眼路由表和策略规则,根因就浮出水面了——CIDR 撞车了。
Tailscale 默认给所有设备分配的内网虚拟 IP,落在 RFC 6598 规定的运营商级 CGNAT 保留网段:100.64.0.0/10(范围是 100.64.0.0 到 100.127.255.255)。
巧就巧在,阿里云 VPC 内部的基础设施也是用这个保留段! 比如:
- 阿里云的核心内网 DNS 是
100.100.2.136和100.100.2.138; - 云服务器的元数据服务(Metadata)是
100.100.100.200。
在 Linux 默认模式下,tailscaled 启动时会在内核创建一张虚拟网卡(tailscale0),并下发策略路由(ip rule 里的 table 52),把所有目标地址为 100.64.0.0/10 的流量通通往 tailscale0 里拽。
这下直接形成了路由黑洞:ECS 内部发出的所有 DNS 解析请求,全部被截胡进了 Tailscale 虚拟网卡,由于 Tailnet 里根本没有这台 DNS,数据包直接丢入虚空。DNS 断了,证书自动续期、包管理器更新、云监控全线罢工。
我本可以死磕到底
看清了是路由冲突,当时我脑子里冒出的第一个念头,就是典型的工程师本能:“兵来将挡,我还能被几条路由规则难住?”
以 Linux 网络栈的灵活性,要想把这套冲突按下去,多的是折腾的手段:
- 先改 DNS:把
/etc/resolv.conf里的内网 DNS 换成公共 DNS(如223.5.5.5)。外网是能解析了,但阿里云很多内网镜像源和服务域名,通过公网 DNS 解析出来的还是内网 CGNAT 地址,治标不治本; - 手搓明细 Host Route:既然
100.64.0.0/10被拦截,那我就在系统路由表里加静态明细路由,把100.100.2.136/32的优先级提到tailscale0之前,强行绑定到物理网卡eth0; - 折腾 Netfilter 与策略路由:加了明细路由后,发现某些特定协议的包依然被 Tailscale 的 iptables 规则拦截,我又去翻
tailscale set --netfilter-mode=nodivert,甚至考虑用 iptables 给阿里云内部流量打fwmark,再搞一套独立的多路由表配合策略路由分流。
讲实话,真要顺着这条路硬刚,搞 Network Namespace 隔离也好,把 iptables/nftables 的规则链翻个底朝天也罢,凭着以往积累的网络排障经验,我绝对能把它强行调通。
但改到第三层补丁、正在写自动化维护路由脚本的那一刻,我突然停住了。
看着屏幕上一地鸡毛的路由规则,我心里升起一股强烈的荒谬感:这不就是教科书级的 XY Problem 吗?
- 原始需求(X):安全、可控地为我的国内设备提供中继加速;
- 我脑补的手段(Y):为了给 DERP 验身份,必须把这台云主机作为一个常规节点加入 Tailnet;
- 深陷的泥潭:为了解决手段 Y 带来的副作用(阿里云 CIDR 冲突),我一头扎进系统路由表和 iptables 里,花了整整大半天去修补那些本不该存在的漏洞。
我手里真正忙活得满头大汗的事情,早就和最开始的目标毫无关系了。
这套靠人工搓出来的路由例外表极其脆弱。万一哪天阿里云内部基础设施新增了一个 IP 节点?万一哪天系统升级重置了网卡配置?只要有一处对不上,机器就会莫名其妙断网。
我们很多时候太容易被自己的技术能力绑架了——因为自己有能力死磕解决复杂的问题,就误以为这个问题是必须被解决的,结果在错误的道路上一路狂奔,亲手造出了一个日后连自己都不敢碰的复杂度怪兽。
意料之外的收获:Peer Relay
在反思这套方案时,其实还有个看似省事的退路:放弃官方的 --verify-clients,去用社区魔改的带 token 鉴权的 derper 分支。没有本地校验,机器自然不用跑 tailscaled,冲突立消。
但我很快打消了这个念头。除了不想长期肉身维护第三方分支以外,还有一个更关键的原因:在翻看 Tailscale 最近的连接演进时,我注意到了 Peer Relay。
在 Tailscale 的数据链路中,连接优先级的顺序其实是:
Direct(打洞直连) → Peer Relay(节点中继) → DERP(服务器中继)
很多人只知道 DERP,但实际上 Tailscale 早就支持了 Peer Relay:它允许我们在 Tailnet 里把某台网络条件好的设备(比如这台公网云主机)声明为一个 UDP 中继节点。
相比于 DERP,Peer Relay 的优势实在太大了:
- 性能远胜 DERP:DERP 是基于 TCP/TLS(WebSockets)封装的中继,哪怕延迟降下来了,握手和吞吐开销依然很大;而 Peer Relay 是纯 WireGuard UDP 中继,延迟更低、吞吐更接近原生物理网速;
- 原生的安全机制:它直接依托于 Tailnet 的 ACL Capability Grant(
tailscale.com/cap/relay)授权,只有被授权的设备才能走它中继,根本不需要对外暴露容易被扫描的公共端口。
无论从延迟体验还是架构干净程度来看,Peer Relay 才是国内中继真正的主力解法。
但要用 Peer Relay,这台云主机就必须作为一个合法的节点加入 Tailnet。换句话说,tailscaled 必须跑起来,绕是绕不过去的。
转折:用第一性原理回归底层事实
既然必须跑 tailscaled,但我又不想继续在路由表里死磕,那破局点在哪?
既然意识到了自己掉进了 XY Problem 的陷阱,那就必须把所有预设全给拆了。我逼自己停下来,用所谓**“第一性原理”**把这层层外壳剥掉,退回到最底层的网络事实去推导:
这台云主机上的操作系统,真的需要“进入”Tailscale 网络吗?
我们一条条看底层事实:
- 我需要在这台机器上通过 Tailscale 的虚拟 IP 去 SSH 它吗?不需要,它本来就有公网 IP 和密钥,正常的运维走公网足够;
- 这台主机上的应用需要主动去访问我家里的 NAS 或者内网机器吗?完全不需要;
- 它是作为 Subnet Router(子网路由)或者 Exit Node(出口节点)存在吗?也不是。
抽丝剥茧之后,本质一下子浮出水面:
- 中继的本质是什么? 是一个暴露在公网上的后台进程,负责接收两端发来的加密数据包,并原封不动地转发出去;
- 鉴权的本质是什么? 是这个进程能拿到本地的身份令牌,证明自己是可信的,并帮控制面做一下校验。
我从头到尾需要的,只是让 tailscaled 作为一个后台进程存在,负责连上控制面获取身份、帮其他设备转发 UDP 报文(Peer Relay),并在本地暴露一个 socket 供 derper 校验身份。
这台机器本身的 Linux 操作系统、它的业务进程、它的 DNS 和系统路由表,压根就不需要知道 Tailscale 的存在!
之前的冲突之所以发生,纯粹是因为我们在认知上把它当成了一台普通的“办公终端”,理所当然地觉得“装了 Tailscale 就必须建网卡、改路由、接管流量”。
一旦用第一性原理把“操作系统必须进网”这个伪前提剥离掉,真正的解法就自己跳出来了:只要让 tailscaled 纯在用户态运行,完全不碰内核网络栈就行了。
一行配置破局:Userspace Networking
Tailscale 官方其实早就为无 root 权限或容器环境提供了现成的原生参数:--tun=userspace-networking。
在 Linux 上,配置起来极其简单。如果是用 systemd 管理 tailscaled,编辑 /etc/default/tailscaled(Debian/Ubuntu)或对应的配置文件,加上启动参数:
FLAGS="--tun=userspace-networking"
或者手动启动时直接带上:
tailscaled --tun=userspace-networking
重启服务的一瞬间,困扰了我大半天的所有问题全部迎刃而解:
- 不建虚拟网卡:内核里完全没有
tailscale0; - 不碰路由表与 iptables:宿主机的系统路由原封不动,没有多余的策略路由拦截;
- 阿里云内部网络畅通无阻:系统 DNS(
100.100.2.136)、Metadata、内网软件源继续走原生物理通道,CIDR 冲突直接物理蒸发; - 核心功能毫发无损:
tailscaled依然在用户态保持控制面长连接,Peer Relay 的 UDP 转发正常工作,本机的/var/run/tailscale/tailscaled.sock也依然稳稳为derper提供鉴权。
大半天写下的那些花里胡哨的路由补丁,在一行启动参数面前全成了多余的笑话。
最终落地与架构
理顺了网络边界之后,整台机器的职责变得异常清爽。
既然 Userspace 模式下的 tailscaled 既稳定又不扰民,我索性把 Peer Relay 和自建 DERP 全部配齐,打造“双保险”:
- 日常主力(Peer Relay):设备之间打不通直连时,优先通过云主机的 Peer Relay 进行高速 WireGuard UDP 中继;
- 兜底方案(DERP):给个别尚不支持 Peer Relay 的客户端(比如手机上的某些第三方工具如 Surge)做保底,或者在极端苛刻的网络下走 TCP/TLS 兜底。
在 Tailscale 控制台的 ACL 策略中,为中继节点添加能力授权:
{
"tagOwners": {
"tag:relay": ["autogroup:admin"]
},
"grants": [
{
"src": ["autogroup:member"],
"dst": ["tag:relay"],
"app": {
"tailscale.com/cap/relay": [{}]
}
}
]
}
在云主机端为节点打标启动:
tailscale up --advertise-tags=tag:relay --accept-routes=false
同时把带 --verify-clients 的 derper 拉起来:
derper \
-hostname derp.yourdomain.com \
-certmode manual \
-certdir /etc/derper/certs \
-verify-clients
整台机器的分工极度清晰:
云服务器 (ECS)
├── tailscaled (Userspace 模式) → 维护 Tailnet 身份 + 提供 Peer Relay (UDP)
│ → 暴露本地 socket 供客户端鉴权
└── derper (Container) → 承接传统 DERP (TCP/TLS) + STUN (UDP)
在客户端执行 tailscale ping <peer>,终端里清晰地显示两端在穿透失败后无缝走上了本地云服务器的 Peer Relay,延迟直接从境外官方 DERP 的 150ms 压到了 20ms 以内,日常操作顺滑如内网。
写在最后
这次排障折腾下来,最大的感悟倒不是学会了 --tun=userspace-networking 这个参数,而是让我重新审视了工程师面对问题时的“思维惯性”。
在遇到 CIDR 冲突、路由拦截的那一刻,凭着以往积累的网络底层经验,我完全有能力写出一套极为精妙的 iptables 规则和多路由表分流策略把它搞定。我们往往会下意识地享受这种“通过硬刚复杂机制把问题强行摆平”的成就感。
但我差点就陷了进去——为了炫技式地解决一个由“错误前提”引发的次生问题,去亲手造出一个日后连自己都懒得维护的复杂度怪兽。
死磕技术细节固然是工程师的基本功,但更重要、也往往更难的一步,是在投入大量精力之前先踩一脚刹车:我们手里正在死磕的这个难题,真的是必须被解决的吗?
当你发现这台机器打从一开始就不需要进入内网的三层网络栈时,那个看起来极其棘手的 CIDR 冲突,其实连一行路由规则都不需要写。
