Clash Verge 开了 TUN 虚拟网卡反而上不了网:Hysteria2 流量绕回 TUN 自己,Tailscale 又把 DNS 截走了
现象
环境:Mac mini,macOS 26.3.1;Clash Verge Rev 2.5.6,内核 mihomo v1.19.31,通过「服务模式」以 root 身份运行;订阅里有 7 个自建节点(5 个 Hysteria2、2 个 VLESS-REALITY),全局模式,当前选的是 美国-DMIT-Hysteria2。本机同时在跑 Tailscale。
- 只开「系统代理」时,浏览器、
curl -x http://127.0.0.1:7897全都正常。 - 在设置里打开「虚拟网卡模式(TUN)」后,开关能打开,也没有报错,但所有网站都打不开,连百度都打不开。
「连国内站都打不开」这一点很关键:如果只是 DNS 被污染,国内站应该还能用。现在国内外一起断,说明经过 TUN 的流量整体都没走通。
排查工具:直接连 mihomo 内核的 API
Clash Verge 的 GUI 只显示了开关状态,要排查得直接问内核。服务模式下,内核把控制接口开在一个 unix socket 上,当前用户可以直接访问(secret 在 Clash Verge 目录的 config.yaml 里):
S=/var/run/clash-verge-service/users/501/verge-mihomo.sock
A="Authorization: Bearer set-your-secret"
curl -s --unix-socket $S -H "$A" http://x/version
# {"meta":true,"version":"v1.19.31"}
# 实时日志(debug 级别),另开一个终端挂着
curl -sN --unix-socket $S -H "$A" "http://x/logs?level=debug"
# 不用点 GUI,直接开关 TUN
curl -s --unix-socket $S -H "$A" -X PATCH http://x/configs -d '{"tun":{"enable":true}}'
后面所有实验都用这三条命令完成:开 TUN、看日志、用 curl --noproxy '*' 测连通性(绕过系统代理,确保流量真的走 TUN),测完再关。
第一步:TUN 本身起来了吗
$ ifconfig utun5 | grep inet
inet 198.18.0.1 --> 198.18.0.1 netmask 0xfffffffc
$ netstat -rn -f inet | grep utun5 | head -3
1 198.18.0.1 UGSc utun5
2/7 198.18.0.1 UGSc utun5
4/6 198.18.0.1 UGSc utun5
网卡已经创建,auto-route 也用 1/8、2/7、4/6…… 这组拆分路由把默认路由盖住了。内核日志同样正常:
[TUN] default interface changed by monitor, => en1
[TUN] Tun adapter listening at: utun5([198.18.0.1/30],[]), mtu: 9000, auto route: true, auto redir: false, ip stack: gVisor
但连通性测试全挂:
https://www.google.com 000 8.002101s ip=31.13.92.37
https://www.baidu.com 000 8.005689s ip=103.235.47.188
所以 TUN 本身没问题,问题出在「进了 TUN 之后」。
第二步:日志里的可疑连接——代理在代理自己
接着看 TUN 打开后的连接日志:
[UDP] 198.18.0.1:55638 --> 179.253.x.x:443 using GLOBAL
[UDP] 198.18.0.1:56470 --> 163.7.x.x:8488 using GLOBAL
[UDP] 198.18.0.1:54918 --> 36.50.x.x:8488 using GLOBAL
[TCP] 198.18.0.1:57612 --> 103.235.47.188:443 using GLOBAL
前三条的目标地址就是 Hysteria2 节点自己(:443 是 DMIT 的 Hysteria2,:8488 是另外两台)。本机会往这些地址发 UDP 包的,只有 mihomo 自己。源地址是 198.18.0.1,说明这些包是从 TUN 网卡进来的。
也就是说:mihomo 想连 Hysteria2 节点 → 这个 UDP 包没有绑定到物理网卡 en1 → 按路由表进了 utun5 → mihomo 又从 TUN 收到它,按 GLOBAL 规则再交给 Hysteria2 → 再次进入 TUN……形成回环,所有走 Hysteria2 的连接都不可能建立。
第三步:确认是 Hysteria2(UDP)独有的问题
TUN 开着的情况下,对不同协议的节点分别做延迟测试:
$ curl -s --unix-socket $S -H "$A" "http://x/proxies/<节点名>/delay?url=http://www.gstatic.com/generate_204&timeout=5000"
美国-DMIT-VLESS {"delay":156}
日本-Hysteria2 {"message":"Timeout"}
TUN 关着时,这两个节点分别是 156ms 和 66ms,都正常。再对比日志:VLESS 出站时每次都会打印这一行,Hysteria2 一次都没有:
[TUN] Auto detect interface for 179.253.x.x --> en1
auto-detect-interface: true 的作用,是让内核自己的出站连接绑定到物理网卡,防止它们绕回 TUN。从现象看,这个机制在 VLESS(TCP)出站上生效了,在 Hysteria2(QUIC/UDP)出站上没有生效。GitHub 上有同类报告(MetaCubeX/mihomo#1222「启用 Tun 模式后,无法连接 Hysteria 和 Hysteria2 节点」),没有官方结论。我没有读到内核源码层面去确认,这里只写实测观察到的行为。
把全局节点切成 VLESS 再测:
TUN+VLESS https://www.baidu.com 200 2.03s ip=103.235.46.96
TUN+VLESS https://www.google.com 000 5.49s ip=31.13.92.37
TUN+VLESS https://www.youtube.com 000 6.03s ip=174.132.167.252
百度通了,说明回环问题确实只和 Hysteria2 有关。但 Google 和 YouTube 还是不通,而且解析出来的 IP 不对:31.13.92.37 是 Facebook 的地址,是典型的 DNS 污染结果。所以还有第二个问题。
第四步:DNS 为什么没被 Clash 接管
TUN 配置里写了 dns-hijack: [any:53],按理说所有 53 端口的查询都应该被 mihomo 截下来。看一下系统当前实际在用的 DNS:
$ scutil --dns | grep "nameserver\[0\]" | sort | uniq -c
3 nameserver[0] : 100.100.100.100
2 nameserver[0] : 240e:306:2085:bd00:42b0:76ff:fec3:f948
$ netstat -rn -f inet | grep -E "100.64|100.100.100.100/32"
100.64/10 link#23 UCS utun4
100.100.100.100/32 link#23 UCS utun4
Tailscale 的 MagicDNS(100.100.100.100)接管了系统 DNS,而且这个地址有一条指向 Tailscale 自己网卡 utun4 的专用路由。它比 Clash 的拆分路由更具体,所以 DNS 查询直接进了 Tailscale,根本不会经过 utun5,dns-hijack 自然拦不到。整个排查过程中,内核日志里的 [DNS] hijack 只出现过一次,还是我手动 dig @198.18.0.2 触发的。
Tailscale 再把查询转发给上游(路由器 / 运营商 DNS),拿回来的就是被污染的 IP。mihomo 在 TUN 里只能看到「连 31.13.92.37:443」,不知道真实域名,于是让代理节点去连一个 Facebook 的 IP,TLS 握手的 SNI 却是 google.com,结果当然失败。
另外,Clash Verge 在开关 TUN 时会修改系统 DNS(日志里的 set system dns successfully),但 Tailscale 的 resolver 在 scutil 里优先级更高,这一步也不起作用。当时的运行时配置里根本没有 dns 段(「DNS 覆写」没开,订阅里也没写),也没有 sniffer 段。
站内之前有一篇 Tailscale 接管 DNS 后把翻墙机的解析清空了,那次出事的是服务器端,这次是客户端。两次都是同一类问题:装了 Tailscale 之后,DNS 的实际走向和你以为的不一样。
修法
两个问题分别修,而且两处修改都放在 Clash Verge 的订阅 Merge 覆写里(订阅右键 →「编辑覆写」,或直接编辑 profiles/ 下该订阅对应的 merge 文件)。这样不改订阅本身,订阅更新时也不会被覆盖。
1. 把节点 IP 排除出 TUN 路由
tun:
route-exclude-address:
- 179.253.x.x/32 # 每个节点的服务器 IP 各写一行
- 163.7.x.x/32
- 36.50.x.x/32
开启后,路由表里的拆分路由会绕开这些地址(179.253.232/26、179.253.232.65/32…… 唯独缺了节点那一个 IP),发往节点的包走默认路由出 en1,不会再进 TUN。这个修法不依赖内核是否正确绑定了网卡,是在路由层面切断回环。
代价是节点换 IP 时要同步修改这里。节点如果是用域名写的,要填解析后的 IP。
2. 打开 sniffer,用 SNI 还原域名
sniffer:
enable: true
parse-pure-ip: true # 目标是纯 IP 的连接也去嗅探
force-dns-mapping: true
override-destination: true # 用嗅探到的域名替换目标 IP
sniff:
HTTP:
ports: [80, 8080-8880]
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
DNS 查询绕过了 Clash 也没关系:浏览器拿着污染 IP 发起 TLS 连接时,ClientHello 里的 SNI 仍然是真实域名。sniffer 把它读出来,替换掉目标地址,再交给代理节点在远端解析。
我也测了「sniffer + 打开 DNS 覆写(fake-ip)」的组合,结果一样全通。但在 Tailscale 接管 DNS 的前提下,fake-ip 实际上收不到系统查询,真正起作用的是 sniffer,所以最终只保留了上面两段。
另一种思路:让 Tailscale 不接管 DNS
tailscale set --accept-dns=false 能让系统 DNS 回到路由器,查询也会重新经过 TUN 被 hijack。代价是失去 MagicDNS 的主机名解析。我这台机器上的 SSH 配置都写的是 IP,理论上可以关掉,但 sniffer 方案不动 Tailscale,影响面更小,所以没有采用。
验证
用 Clash Verge 重启后重新生成的运行时配置(clash-verge.yaml 里已经合进了这两段),打开 TUN,全局节点保持 Hysteria2,所有请求都加 --noproxy '*':
https://www.google.com 200 0.65s
https://www.youtube.com 200 1.66s
https://github.com 200 1.48s
https://api.anthropic.com 403 1.10s # 根路径本来就返回 403,说明连通
https://www.baidu.com 200 1.70s
exit ip country US AS906 DMIT Cloud Services
同时确认 Tailscale 没有受影响:ssh winmini(局域网 Windows)正常返回主机名,100.100.100.100:53 可达。原因是 Tailscale 的 100.64/10 路由比 TUN 的拆分路由更具体,天然就不会进 TUN。
排查清单:Clash TUN 打开后上不了网
- 国内站能不能开? 国内站也不行,说明流量没走通(回环、出站问题);只有国外站不行,优先怀疑 DNS。
- 看内核日志里有没有「代理连自己」:
198.18.0.1 --> <节点IP>:<节点端口>出现在 TUN 连接日志里,就是回环。 - TUN 开着时按协议分别测节点延迟:只有 UDP 类协议(Hysteria2 / TUIC)超时,就用
route-exclude-address排除节点 IP。 scutil --dns看系统实际用的 DNS:是 100.100.100.100(Tailscale)或其他 VPN 的地址,说明 DNS 不经过 TUN,需要开 sniffer。- 看运行时配置有没有
dns和sniffer段:Clash Verge 的运行时文件是~/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml,缺了就说明 GUI 上的开关没把它们合进去。