开启 Tailscale 后,Clash 出口为什么变成中国?一次 IPv6 绕过代理的排查与修复
开启 Tailscale 后,Clash 明明选中了海外节点,查 IP 网站却显示中国。第一反应是:两个软件都创建虚拟网卡,是不是互相冲突了?
这次排查发现,海外代理节点仍然正常,IPv4 经过 Clash,公网 IPv6 却绕过了 Clash,直接从本地宽带出站。支持 IPv6 的网站可能显示国内地址,仅支持 IPv4 的网站则仍然显示海外地址。
修复方法是为 Clash TUN 补上 IPv6 地址,让 Mihomo 自动建立 IPv6 接管路由,同时排除 Tailscale 的内网网段。修复后,两者可以继续同时使用。
1. 现象与运行环境
本次环境是 macOS,使用 Clash Verge Rev,内核为 Mihomo v1.19.32,Tailscale 版本为 1.102.4。
当时的设置如下:
| 项目 | 状态 |
|---|---|
| Clash TUN 模式 | 开启 |
| Clash 系统代理 | 关闭 |
| Clash 分流模式 | Rule |
Clash 顶层 ipv6 |
true |
Clash TUN auto-route |
true |
| Tailscale | 已连接 |
| Tailscale 出口节点 | 未启用 |
| 本地网络 | 同时支持 IPv4 和 IPv6 |
用户观察到的问题发生在开启 Tailscale 后,但排查时两者已经同时运行。为了避免打断连接,没有关闭 Tailscale 做前后对照。因此,下文能够确认的是两者同时运行时存在的 IPv6 路由缺口,不能据此认定 Tailscale 存在兼容性缺陷。
2. 为什么“Google 能打开”还不够
最初检查时,Google 和国内网站都能访问,Tailscale 内网设备也能 ping 通。路由表中,Tailscale 的 100.64.0.0/10 网段仍然指向自己的隧道。
这些结果说明网络没有整体断开,却无法证明所有公网流量都经过了 Clash。
双栈网络下,网站可以同时有两种 DNS 记录:
- A 记录:IPv4 地址。
- AAAA 记录:IPv6 地址。
浏览器和其他应用会根据解析结果、连接速度及自身策略选择连接方式。如果 IPv4 和 IPv6 的路由不同,同一台设备就可能同时存在两个公网出口。
这次实际路径是:
1 | 应用访问 IPv4 网站 |
因此,排查代理出口时,至少要分别验证 IPv4、IPv6 和显式代理。
3. 排查:让同一个网站回答三次
3.1 分别测试 IPv4 和 IPv6
Cloudflare 的诊断接口会返回它看到的来源 IP 和地区,可以用同一个接口比较:
1 | # 不使用环境变量中的代理,依靠系统路由访问 |
重点查看返回内容中的 ip= 和 loc=。
这里的 --noproxy '*' 只是不使用 curl 的显式代理设置,不会绕过系统级 TUN。如果 TUN 路由完整,这些请求仍然会进入 Clash。
当时的结果如下,具体 IP 已省略:
| 测试方式 | 服务端看到的来源 | 地区 |
|---|---|---|
| 强制 IPv4 | 海外代理节点 IPv4 | US |
| 强制 IPv6 | 本地宽带公网 IPv6 | CN |
另外使用 https://api64.ipify.org 交叉检查,结果一致。这样可以排除仅仅是某个网站显示异常的情况。
3.2 显式指定 Clash 代理
本机 Clash 的混合代理端口是 7897,再让请求明确经过这个端口:
1 | curl --proxy 'http://127.0.0.1:7897' --noproxy '' \ |
这次返回的是海外节点 IP,地区为 US。
显式 HTTP 代理会把目标连接交给 Clash 处理,请求不再依赖应用直接连接目标网站时的那条 IPv6 路由。这个结果表明代理端口和节点本身可以正常工作,问题应该继续往 TUN 接管范围和系统路由上查。
如果你的代理端口不同,需要替换 7897。
3.3 查看公网 IPv6 实际走哪张网卡
在 macOS 上查看 IPv6 路由表:
1 | netstat -rn -f inet6 |
也可以直接查询一个目标地址的路由:
1 | route -n get -inet6 2606:4700::6810:7b60 |
修复前,该公网 IPv6 地址对应的结果是:
1 | interface: en0 |
en0 是这台机器的 Wi-Fi 网卡。与此同时,Clash TUN 为 utun1024,Tailscale 为 utun8。公网 IPv6 目标没有进入 Clash TUN,而是直接交给了 Wi-Fi。
网卡编号会因机器和启动顺序而变化,排查时应以本机实际结果为准。
4. 原因:允许 IPv6,不等于 TUN 已经接管 IPv6
配置中已经存在:
1 | ipv6: true |
TUN 也开启了自动路由:
1 | tun: |
但是,运行中的 TUN 只有 IPv4 地址 198.18.0.1/30,没有 IPv6 地址,系统路由表里也没有 Clash 接管公网 IPv6 的对应路由。
顶层启用 IPv6 和 TUN 接管 IPv6 是两个需要分别确认的状态。 在本次使用的版本和配置下,补上 TUN 的 inet6-address 后,Mihomo 才建立了预期的 IPv6 接管路由。
Tailscale 的 DNS 设置也提供了一条相关线索。通过:
1 | scutil --dns |
可以看到 Tailscale 的 DNS 服务器 100.100.100.100,而向它查询公网网站 AAAA 记录时,能够得到真实的公网 IPv6 地址。应用拿到这些地址后,如果选择 IPv6,系统就会按现有 IPv6 路由直接出站。
因此,一种与现象相符的解释是:DNS 路径变化让应用拿到了可用的真实 IPv6 地址,从而暴露了原本不完整的 TUN 接管配置。 但本次没有验证 Tailscale 关闭时的解析结果和路由,不能把这条解释当作已经完成对照验证的因果结论。
5. 修复:补上 IPv6 地址,排除 Tailscale 内网
在保留原有 TUN 设置的基础上,合并以下配置:
1 | ipv6: true |
这是一段需要合并到现有配置中的示例,不是完整的 Clash 配置。如果已有 tun 或 route-exclude-address,应在原处补充,保留已有字段和排除项,避免在同一层重复定义键。
5.1 inet6-address 的作用
fdfe:dcba:9876::1/126 是给 TUN 虚拟网卡使用的本地 IPv6 地址。它不是代理服务器地址,也不是最终公网出口,不需要向运营商申请。
在本次环境中,添加它并保持 auto-route: true 后,Mihomo 自动配置了公网 IPv6 路由,无需手动执行 route add,也没有额外指定 route-address。
Mihomo 官方文档说明,inet6-address 需要与顶层 ipv6: true 一起使用,并且程序启动时会检查其他网卡是否具备 IPv6。本机 Wi-Fi 已有公网 IPv6,因此不需要额外设置跳过检查的环境变量。
5.2 为什么排除 Tailscale 网段
Tailscale 使用以下地址范围:
| 地址范围 | 用途 |
|---|---|
100.64.0.0/10 |
Tailscale IPv4 地址,位于共享地址空间内 |
fd7a:115c:a1e0::/48 |
Tailscale IPv6 地址 |
把它们加入 TUN 路由排除列表,是为了让这些目标继续交给 Tailscale 隧道。
需要注意,100.64.0.0/10 并非 Tailscale 独占地址空间。如果本地网络或其他 VPN 也使用这个范围,还要结合实际路由检查。这里的排除项用于解决本机已确认的 Tailscale 共存需求。
如果 Tailscale 还提供了子网路由,例如访问另一地点的 192.168.x.0/24,则还需要单独检查该子网的路由。本次没有对这类场景做验证。
6. 写到哪里,才能在重启后保留
Clash Verge Rev 的配置分为应用维护的基础配置、订阅及增强配置,以及最终生成的运行配置。只修改生成文件,下一次订阅更新或重新生成时可能被覆盖。
本机配置目录是:
1 | ~/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/ |
本次修改了三个位置:
| 文件 | 处理方式 |
|---|---|
config.yaml |
在原有 TUN 设置中补充 IPv6 地址和排除项 |
profiles/Merge.yaml |
在全局增强配置中保留相同设置,用于后续配置生成 |
clash-verge.yaml |
更新当时的生成配置,用于校验和立即应用 |
全局增强配置中原有的 profile.store-selected 也予以保留。使用图形界面时,可以在全局扩展配置中合并相应 YAML;不同版本界面名称和配置合并规则可能不同。
修改前,对相关文件进行了备份。随后用 Mihomo 的配置检查功能确认生成配置能够通过校验,再通过本地控制接口应用设置。
最后重启 Clash Verge,确认它重新生成的配置仍包含 inet6-address 和两个排除网段。重启后日志还显示订阅更新成功,再次检查路由和出口也正常。这比只看到一次配置加载成功更能说明持久化设置已经生效。
7. 验证:既要查出口,也要查 Tailscale
7.1 公网 IPv6 路由进入 Clash
再次执行:
1 | route -n get -inet6 2606:4700::6810:7b60 |
修复后的关键结果变为:
1 | gateway: fdfe:dcba:9876::1 |
这说明该公网 IPv6 目标已由 Clash TUN 接管。
7.2 三种访问方式的出口一致
对同一个诊断接口重复测试,结果如下:
| 访问方式 | 修复前 | 修复后 |
|---|---|---|
| 自动选择地址族 | 可能使用国内 IPv6 | 海外代理出口,US |
| 强制 IPv4 | 海外代理出口,US |
海外代理出口,US |
| 强制 IPv6 | 国内宽带出口,CN |
海外代理出口,US |
强制 IPv6 后,服务端仍然可能看到代理节点的 IPv4 地址,这并不矛盾。
curl -6 控制的是应用访问目标时使用的地址族;流量进入 Clash 后,代理协议及节点会负责后续连接。代理节点出站使用的地址族,不必与应用进入 TUN 时相同。这次结果证明,原本直出的 IPv6 流量已经经过代理,不能将其理解成代理节点获得了新的公网 IPv6 出口。
7.3 Tailscale 内网仍然可用
验证 Tailscale 时,替换成自己网络中的在线设备名:
1 | tailscale status |
还可以检查 DNS 服务地址的路由:
1 | route -n get 100.100.100.100 |
本次结果仍然指向 Tailscale 的 utun8,内网设备连通性和内网域名解析也正常。
到远端设备的连接仍然使用 DERP 中继,尚未建立直连。这与公网 IPv6 绕过代理是两个问题:本次修复恢复了预期的出口路径,没有解决 Tailscale 的 NAT 穿透或中继延迟。
8. 修复后的分流边界与回退方法
补齐 IPv6 接管后,流量路径变为:
1 | 公网 IPv4 / IPv6 |
进入 TUN 不等于所有网站都必须显示海外 IP。 Clash 仍处于 Rule 模式,匹配直连规则的国内网站继续使用本地出口。应选择明确走代理的网站验证,不能只凭某个国内查 IP 页面的结果判断接管是否失败。
如果修改后出现异常,可以恢复备份中的基础配置和全局增强配置,再重启 Clash Verge,使运行配置重新生成。已经生成的文件不应成为唯一的回退来源。
这次排查最有用的步骤,是对同一个目标分别测试 IPv4、IPv6 和显式代理,然后用路由表确认流量实际经过哪张网卡。它将“两个 VPN 软件似乎冲突”的模糊现象,缩小成了一个可以验证、修复并回归检查的 IPv6 路由问题。
参考资料
- Mihomo TUN 配置文档:inet6-address、auto-route 与路由排除
- Cloudflare 连接诊断接口
- 本文的测试结果来自上述 macOS 环境中的实际配置、路由查询与请求对比;接口返回的地区仅代表测试时所选服务对来源 IP 的识别结果。



