同一台 Mac 同时运行 Tailscale 和 Clash 后,启智平台打不开了。恢复启智访问后,另一个问题又出现了:Tailscale 设备能 ping 通,浏览器却打不开它的服务首页。

两次故障发生在不同环节。启智请求最初卡在 DNS;Tailscale 服务则是直接访问返回 HTTP 200,经过本地 Clash 代理返回 502。最后的配置是:Tailscale 保留设备互联,关闭 DNS 和接受子网路由;Clash 继续按规则分流,同时在 TUN 和系统代理两处绕过 Tailscale 目标。

下面先复盘证据,再给出可以直接复用的排查 SOP。设备地址统一替换为示例 100.101.102.103,代理端口以本次环境的 7897 为例。

1. 先明确要保留的网络行为

这次需求很具体:通过 Tailscale IP 访问一台设备上的服务,其余日常访问继续由 Clash 按现有规则处理。既不需要用设备名访问,也不需要借助远端设备访问它背后的局域网。

功能 负责什么 本次选择
Tailscale 设备互联 通过虚拟 IP 连接已加入 tailnet 的设备 保留
Tailscale DNS 接受管理后台配置的 DNS,包括设备名及其他域名的解析设置 关闭
接受 Tailscale 子网路由 通过其他节点访问其发布的网段,如远端局域网 关闭
Tailscale 出口节点 借助指定节点转发普通互联网流量 不启用
Clash TUN 在系统路由层接入流量,再交给规则引擎处理 保留,并排除 Tailscale 地址段
Clash 系统代理 让遵循系统代理设置的应用连接本地代理端口 保留,并绕过目标 IP

直接访问设备自己的 Tailscale IP,不依赖 DNS,也不需要接受子网路由。关闭这两项不会断开设备互联;如果以后要使用设备名或远端局域网,再按需求重新启用。Tailscale 客户端设置文档

这是一套流量分工配置,不是访问控制策略。其他获授权的 Tailscale 设备仍可能可达;若要限制只能访问某个 IP 或端口,应另行配置 tailnet 访问策略。

2. 第一次故障发生在 DNS 阶段

2.1 先确认连接停在哪里

访问启智平台时,最初的请求直接报错:

1
curl: (6) Could not resolve host: qz.sii.edu.cn

这说明请求还没有进入目标站点的 TCP 和 TLS 阶段。此时应先看解析路径,不能直接判断服务器宕机或证书失效。

macOS 的检查命令是:

1
2
3
scutil --dns
scutil --proxy
dig qz.sii.edu.cn A +time=3 +tries=1

当时系统解析器中存在 Tailscale 的 100.100.100.100,域名查询返回 NXDOMAIN,即解析器回答该域名不存在。

2.2 指定公共 DNS 也可能仍被 Clash 接管

再分别查询两个公共 DNS:

1
2
dig @223.5.5.5 qz.sii.edu.cn A +time=3 +tries=1
dig @1.1.1.1 qz.sii.edu.cn A +time=3 +tries=1

两次却都立即返回 198.18.0.7。结合 Clash 已启用 fake-ip 和 dns-hijack 的配置,可以判断这些查询进入了 Clash 的 DNS 处理路径,返回的是虚拟地址。

指定 @公共DNS,不足以证明查询真的抵达了那个公共 DNS。 在 TUN 接管 DNS 的环境下,需要同时核对配置和响应特征。这里也不能把 198.18.0.7 当成启智服务器的真实地址,更不能写进 hosts 长期使用。

随后用加密 DNS 查询做对照,Google 和 Cloudflare 的解析服务也返回了 NXDOMAIN。这修正了最初的判断:问题不能简单归结为“Tailscale 把一个公开可解析的域名解析坏了”;更准确的结论是,当时的普通解析路径无法得到可用地址,而既有启智代理路径能够完成访问。

2.3 用显式代理验证整条登录链

1
2
3
4
5
curl --proxy 'http://127.0.0.1:7897' --noproxy '' \
--connect-timeout 5 --max-time 20 \
-sS -L -o /dev/null \
-w 'HTTP %{http_code}; TLS %{ssl_verify_result}; redirects %{num_redirects}\n' \
'https://qz.sii.edu.cn/'

这条路径最终返回 HTTP 200,TLS 验证通过。启智入口先重定向到身份认证服务,所以只看到入口的 302 还不够;必须继续检查登录域名和最终页面。

本机已有启智域名到专用代理组的分流规则。开启系统代理后,遵循系统设置的浏览器可以把目标域名交给 Clash 处理。之后关闭 Tailscale DNS,让本次不需要的 DNS 接管退出,也简化了两套工具的职责。

3. 第二次故障暴露了系统代理路径

3.1 隧道连通和服务可访问是两次验收

第一轮配置完成后,目标设备的 Tailscale ping 成功,系统路由也指向 Tailscale 接口。但浏览器仍然打不开服务首页。

最有区分度的测试,是对同一个 URL 分别走直连和显式代理:

1
2
3
4
5
6
7
8
9
10
11
12
TARGET_IP='100.101.102.103'
TARGET_URL="http://${TARGET_IP}/"
CLASH_PROXY='http://127.0.0.1:7897'

# 不使用 curl 的显式代理,交给系统路由。
curl --noproxy '*' --connect-timeout 5 --max-time 10 \
-sS -o /dev/null -w 'Direct: HTTP %{http_code}\n' "$TARGET_URL"

# 明确强制进入 Clash,避免 NO_PROXY 环境变量干扰对照。
curl --proxy "$CLASH_PROXY" --noproxy '' \
--connect-timeout 5 --max-time 10 \
-sS -o /dev/null -w 'Proxy: HTTP %{http_code}\n' "$TARGET_URL"

现场结果如下:

测试 结果 能确认的事实
tailscale ping 成功,约几十毫秒 对端 Tailscale 连接可建立
route -n get 目标IP 指向 Tailscale 接口 系统直接连接目标时有对应路由
HTTP 直连 200 目标 Web 服务可经当前直连路径访问
HTTP 显式经过 Clash 502,约 5 秒后返回 经本地代理的请求路径失败

这组对照把问题收敛到了经过本地代理的访问路径。它没有证明目标服务宕机,也不足以单凭现象断定 Mihomo 内部究竟是哪项接口选择或拨号行为导致失败。

3.2 TUN 排除和系统代理绕过作用在不同入口

两种应用访问路径可以画成:

1
2
3
4
5
6
7
8
9
应用直接连接目标 IP
→ 系统路由
→ Tailscale 隧道
→ 目标 Web 服务

浏览器遵循系统代理,且目标未被绕过
→ 127.0.0.1:7897
→ Clash 规则与出站连接
→ 目标 Web 服务

Clash 的 TUN 排除网段只影响自动路由的接管范围。如果浏览器已经主动连接本地代理端口,TUN 排除不会撤销这个应用层选择。Mihomo TUN 配置说明

当时 Clash 中已经存在目标 IP 的 DIRECT 规则,但代理访问仍然返回 502。因此,DIRECT 只能说明规则选择了直连出站,不能单独作为“浏览器访问已经成功”的证据。

修复是在系统代理绕过列表中明确加入目标 IP,让浏览器直接连接目标,再由系统的 Tailscale 路由接手。

4. 最终使用的最小配置

4.1 关闭 Tailscale DNS 和接受子网路由

macOS 独立版客户端可以使用应用内的 CLI:

1
2
3
/Applications/Tailscale.app/Contents/MacOS/Tailscale set \
--accept-dns=false \
--accept-routes=false

若安装方式不同,先确认本机 CLI 路径。set 只修改显式提供的设置;这里没有重新登录或重置整个客户端配置。

同时在 Tailscale 菜单中确认出口节点为 None。本次原本就没有启用出口节点,无需额外更改。

4.2 保留 Clash TUN 的 Tailscale 网段排除

在 Clash Verge 的持久扩展配置中,合并以下字段:

1
2
3
4
tun:
route-exclude-address:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48

合并时保留已有的其他排除项,不要用这个片段覆盖整个 TUN 配置。100.64.0.0/10 是共享地址空间,具体环境若还使用该地址段,需要一并核对路由。

本次 IPv6 TUN 配置已经存在,予以保留;没有通过关闭 IPv6 来掩盖访问路径问题。也不需要为了这次修复再启用全局模式或修改已有策略组。

4.3 把目标 IP 加入系统代理绕过列表

本次 Clash Verge Rev v2.5.8 的操作路径为:

  1. 打开 Settings → System Proxy 旁的齿轮。
  2. 关闭 Always use Default Bypass,进入自定义列表。
  3. 保留原有默认绕过项,新增 100.101.102.103。
  4. 点击 NEW 加入列表,再点击 SAVE 保存。
  5. 用 scutil --proxy 确认 ExceptionsList 中确实出现目标 IP。

这个列表填写单个 IP,适用于该 IP 的不同服务端口;不要把 http://、路径或端口一起填进去。不同版本的界面名称可能有所不同。

把设置保存到 Clash Verge,而不只是临时修改 macOS 网络设置,可以避免客户端下次重新应用系统代理时丢失绕过项。

4.4 保留目标 IP 的直连规则作为补充

在持久自定义规则的前部加入:

1
2
rules:
- IP-CIDR,100.101.102.103/32,DIRECT,no-resolve

这里展示的是要添加的规则,不是让整个 rules 列表只剩这一项。保留其他规则并重新激活配置,确认内核规则列表中已出现该项。Mihomo 按从上到下的顺序匹配规则。规则说明

这条规则为进入 Clash 的请求提供分流意图;本次浏览器恢复访问的关键,仍是上一节的系统代理绕过。

5. 可复用的分层排查 SOP

步骤一 固定目标和失败表现

先记录 URL、协议、端口、浏览器报错,以及 Tailscale 和 Clash 的当前状态。IP 能打开而域名打不开,优先查 DNS;域名已解析但连接失败,再看路由、端口和代理。

先把下面的示例 IP、服务端口和本地代理端口替换为自己的值:

1
2
3
TARGET_IP='100.101.102.103'
TARGET_URL="http://${TARGET_IP}/"
CLASH_PROXY='http://127.0.0.1:7897'

不要先同时关闭两个软件。否则即使恢复访问,也难以知道究竟哪条路径发生了变化。

步骤二 读取 DNS 和代理设置

1
2
scutil --dns
scutil --proxy

检查三个位置:系统选择了哪些解析器,HTTP/HTTPS/SOCKS 是否启用,以及目标是否在 ExceptionsList 中。DNS 查询若被 Clash 劫持,应结合 fake-ip 配置解释结果。

对于域名故障,可以用 dig 和加密 DNS 做对照;测试内部域名时,使用自己有权访问且适合该域名的解析器。

步骤三 核对目标路由和 Tailscale 连接

1
2
3
4
route -n get "$TARGET_IP"

/Applications/Tailscale.app/Contents/MacOS/Tailscale ping \
--c 1 --timeout 5s "$TARGET_IP"

记录路由对应的接口,以及 Tailscale 报告的连接路径。接口名称可能是 utun8、utun9 或其他编号,应结合本机配置识别,不能把编号写死。

这一步通过后,仍需测试实际服务端口。ping 成功不能替代 HTTP、SSH 或应用自身的验收。

步骤四 对同一服务做直连和代理对照

复用第三节的两条 curl 命令,固定 URL 和超时,只改变是否进入代理。

观察结果 下一步检查
DNS 失败,显式代理成功 解析器选择、域名是否依赖特定代理或网络
Tailscale ping 成功,HTTP 直连失败 服务监听地址、端口、主机防火墙及访问策略
HTTP 直连成功,代理失败 系统代理绕过、Clash 规则和代理出站路径
HTTP 两种方式成功,浏览器失败 浏览器自身代理设置、扩展、缓存及页面子资源
入口成功,登录页或子域名失败 完整重定向链与后续域名的分流和解析

curl --noproxy '*' 只排除 curl 的显式代理,不会跳过系统级 TUN。反过来,命令行 curl 也不会自动完整复现浏览器对 macOS 系统代理的使用方式,因此浏览器验收必须单独做。

步骤五 每次修改只对应一个已定位的环节

先改 DNS 或单条绕过规则,再重复同一组测试。把修改写入客户端的持久配置,并核对当前运行状态;不要只编辑会被重新生成的运行时配置文件。

如果系统代理绕过后浏览器已经成功,手动强制 curl --proxy ... --noproxy '' 仍然失败,并不表示绕过设置失效。这条命令刻意绕过了正常分工,用于重现原先失败的代理路径。

步骤六 在浏览器里完成最终验收

本次旧错误标签页没有立即恢复,但新标签页成功显示了目标服务首页。验收应打开原始 URL 或新建标签页,确认实际页面内容,而不只看路由表和 HTTP 状态码。

启智平台还要确认身份认证页面正常渲染;如果只有 302 跳转,或登录页仍然空白,排查还没有结束。

6. 验收表和回退方法

本次最终完成了以下检查:

检查项 验收结果
Tailscale DNS 已关闭,系统不再使用其 DNS 配置
接受子网路由 已关闭,Tailscale 继续连接
出口节点 未启用
目标 IP 路由 指向 Tailscale 接口
目标服务 HTTP 200,浏览器显示服务首页
系统代理绕过项 目标 IP 同时存在于系统状态和客户端持久设置中
Clash 保持 Rule 模式,TUN 与系统代理开启
公网测试 IP 系统路由进入 Clash TUN,后续按规则决定出站
启智平台 TUN 与显式代理路径均返回最终 HTTP 200,TLS 验证通过,浏览器显示认证页面

回退时,恢复修改前记录的设置即可:删除新增的单 IP 绕过项和直连规则,恢复原来的自定义或默认绕过列表模式;DNS 和子网路由也只恢复各自的原值,不要一律设为开启。原本已存在的 TUN 排除项和 IPv6 设置应保留。

这次最值得保留的排查习惯,是把 DNS 解析、系统路由、代理路径和应用响应 分别验证。一次 ping、一个 DIRECT 标签或一条正确路由都只覆盖其中一部分;同一 URL 的直连与代理对照,加上浏览器里的实际页面,才能把这类共存问题定位到具体环节。

参考资料与延伸阅读