Arch Linux 中 Clash Verge TUN 模式导致热点客户端无法上网:原因与修复
适用环境
- Arch Linux,用 NetworkManager 开 Wi-Fi 热点(
ipv4.method shared); - Clash Verge Rev,内核为 Mihomo(
verge-mihomo),已开启 TUN 模式与auto-route; - 热点客户端能连上、能拿到 IP,但打不开任何网站;
- 关掉 TUN 后客户端立刻恢复。
客户端能连上但打不开网站这一条不成立时,先看「现象对照」确认不是别的问题。
先说结论
热点客户端打不开网页,但主机自己上网正常,绝大多数情况是这一件事:
客户端拿到的 DNS 答案是
198.18.x.x这类假地址,只有 Mihomo 能解释它;而客户端的流量被配置成不进 Mihomo,于是这些假地址无人认领,连接直接超时。
所以不是 NAT 或防火墙坏了,是”域名解析的结果”和”流量实际走的路”对不上。
先做两件事确定自己属于哪种情况,都在主机上运行。
第一,确认热点 DNS 返回的确实是假地址。192.168.155.1 是占位网关,用 ip -br addr 里热点接口上的地址替换:
sudo pacman -S bind # 只为拿到 dig,已装可跳过
dig @192.168.155.1 not-exist-abc123.example.com A +short
有输出(且落在 198.18.0.0/15)就说明是 Fake-IP 问题。真实解析器对这个不存在的域名只会返回空。
第二,看 dnsmasq 的上游服务器是谁:
journalctl -b -u NetworkManager --no-pager | grep -E 'dnsmasq.*nameserver'
| 输出中的上游服务器 | 你的情况 | 做法 |
|---|---|---|
路由器地址,例如 192.168.50.1 |
常见情况,Mihomo 直接接管了 dnsmasq 的查询 | 「修复一」 |
100.100.100.100、127.0.0.1、127.0.0.53 等本机地址 |
dnsmasq 的上游是 Tailscale、AdGuard Home、systemd-resolved 这类本机解析器,换成哪个 UID 都会被 TUN 接管 | 「修复二」,并保留「修复一」 |
多数人执行「修复一」就够了。「修复二」解决的是:”两条排除规则都写了,dig 还是返回 198.18.x.x“。
三个概念,够用就行
用户只需要理解下面三件事,其余细节可以跳过。
一、TUN 是一个”假的网卡”
开启 TUN 后,Mihomo 会创建一张名为 Mihomo 的虚拟网卡。它同时做两件事:
- 给系统加一条路由规则:符合条件的流量交给这张假网卡;
- 在假网卡上接住这些流量,按代理规则转发出去。
所以”流量进了 TUN”的意思是:这段流量经过 Mihomo 处理,而不是从物理网卡直接出去。
二、Fake-IP 是一个”代收点编号”
Mihomo 默认用 Fake-IP 方式回答 DNS 查询。你问 example.com 的地址,它不回真实公网 IP,而回一个保留地址,例如 198.18.0.119。
这个地址不是真实服务器地址,路由器也不认识它。它的作用只是”编号”:当流量连到 198.18.0.119 时,Mihomo 从 TUN 中接住,查表得知这是 example.com,再按规则转发。
因此有一条硬性对应关系:
Fake-IP 只有经 Mihomo 才能用。流量绕过 Mihomo,Fake-IP 就是一个死地址。
三、两条排除规则各管一半
tun: 配置里两项排除,作用对象完全不同:
| 配置 | 排除的对象 | 相当于 |
|---|---|---|
exclude-interface: wlp2s0 |
从热点网卡进来的转发流量 | 客户端的网页、游戏、下载 |
exclude-uid: 65534 |
以该 UID 运行的本机进程发出的流量 | 热点用的 dnsmasq 去问上游 DNS |
只写第一条时,客户端的数据不走 TUN,但 dnsmasq 的 DNS 查询仍可能进 TUN,于是客户端从 dnsmasq 那里拿到 Fake-IP 假地址。这就是经典的”能 ping 通 IP,打不开网页”。
现象对照
符合以下全部条件,就是本文的问题:
- 主机自己上网正常;
- 热点客户端能连上 Wi-Fi,并通过 DHCP 拿到
192.168.155.x地址; - 客户端
ping 1.1.1.1成功,但浏览器打不开任何网站; - 关掉 Clash Verge 的 TUN 模式,客户端立刻恢复正常。
第 3 条是区分点:
- 能 ping IP,不能打开域名 → 是本文的 DNS 与路径不一致问题;
- IP 也 ping 不通 → 是转发或 NAT 的问题,看「排查步骤」第 3 步。
为什么会出现
正确状态与故障状态的区别只在一行 DNS:
正常:
热点客户端
-> 192.168.155.1:53(dnsmasq)
-> 路由器 192.168.50.1
-> 真实公网 IP
-> 客户端用这个真实 IP 经 enp3s0 出去 ✓ 对得上
故障:
热点客户端
-> 192.168.155.1:53(dnsmasq)
-> Mihomo TUN
-> Fake-IP 198.18.1.16
-> 客户端拿假地址去连,但流量被 exclude-interface 排除,不经 Mihomo
-> 假地址无人处理 ✗ 超时
一句话:解析走了一条路,数据走另一条路。两条路必须在同一个地方汇合,否则 Fake-IP 无人认领。
排查步骤
下表中的值全是占位示例,不是本机真实值。用第 1 步的命令查出自己的值再替换,不要照抄:
| 项目 | 占位示例 |
|---|---|
| 热点连接名称 | Hotspot |
| 热点无线接口 | wlp2s0 |
| 上网接口 | enp3s0 |
| 热点网段 | 192.168.155.0/24 |
| 热点网关 | 192.168.155.1 |
| 路由器地址 | 192.168.50.1 |
热点网段与网关的默认值由 NetworkManager 决定,直接看 ip -br addr show <热点接口> 的输出即可,不需要记任何固定数字。
1. 确认接口名
nmcli device status
ip -br addr
记下热点用的无线接口(ip -br addr 里地址形如 <热点网关>/24 的那张)和实际上网接口(地址形如 192.168.x.x 或 10.x.x.x 的那张)。
2. 检查客户端流量从哪张网卡出去
把 192.168.155.101 换成一个真实在线客户端的地址(ip neigh show dev wlp2s0 可以看到):
ip route get 1.1.1.1 from 192.168.155.101 iif wlp2s0
正确结果类似:
1.1.1.1 from 192.168.155.101 via 192.168.50.1 dev enp3s0
关键是 dev enp3s0。如果显示 dev Mihomo,说明排除接口没生效,或者改的不是当前生效的配置。
3. 检查 NetworkManager 的 NAT 与转发规则
sudo nft list ruleset | grep -A20 nm-shared-wlp2s0
正常时能看到这一表里的两条规则:masquerade(做地址转换)和 filter_forward(允许转发)。有这两条,且第 2 步路由正确,就说明转发和 NAT 没问题,问题在 DNS。
顺带确认转发开关。NetworkManager 共享模式开的是按接口的转发,全局开关可以仍是 0:
sysctl net.ipv4.conf.wlp2s0.forwarding net.ipv4.conf.enp3s0.forwarding
# 两张都要是 1
sysctl net.ipv4.ip_forward
# 0 也正常,不要因为这里为 0 就判定转发被关闭
4. 确认 dnsmasq 的 UID
ps -eo user,uid,pid,args | grep '[d]nsmasq'
输出第二列就是 UID,Arch 上通常是 nobody / 65534。必须用实际输出,不要照抄。
5. 直接问热点 DNS,看它回什么
sudo pacman -S bind # 只为拿到 dig,已装可跳过
dig @192.168.155.1 example.com A +short
dig @192.168.155.1 not-exist-abc123.example.com A +short
两种结果的含义:
- 返回
198.18.x.x→ 就是本文的问题; - 第二条命令(不存在的域名)也返回地址 → 100% 是 Fake-IP,真实解析器对不存在的域名会回空。
6. 判断 dnsmasq 的上游是否被别的程序接管
这一步是排查「两条排除规则都写了,dig 仍然返回 198.18.x.x」时必须做的。
journalctl -b -u NetworkManager --no-pager | grep -E 'dnsmasq.*nameserver'
cat /etc/resolv.conf
如果日志显示:
dnsmasq[12345]: using nameserver 100.100.100.100#53
说明 dnsmasq 上游是 Tailscale 的 MagicDNS,不是路由器。这时 exclude-uid 不起作用,原因见下一节。
为什么有时写了 exclude-uid 也没用
exclude-uid 不是 Mihomo 内部判断,而是写进系统 ip rule 的一条规则。它排在第 9000 位,前面还有别的规则先做决定:
$ ip rule
5270: from all lookup 52 ← Tailscale 自己的路由表
9000: from all uidrange 65534-65534 goto 9010 ← exclude-uid,在这里
9003: not from all dport 53 lookup main suppress_prefixlength 0
9004: not from all iif lo lookup 2022 ← 进 TUN
dnsmasq 的查询发往 100.100.100.100(Tailscale 的解析器)时会先命中 5270:
$ ip route show table 52
100.100.100.100 dev tailscale0
表 52 里有这条路由,包就直接交给 Tailscale 处理,规则 9000 根本没有机会执行。所以 exclude-uid 拦不住它 —— 不是因为 UID 写错了,而是这个包本来就不是去路由器的。
接下来是真正的故障点:tailscaled 收到查询后,自己再去问上游 192.168.50.1:53。这个包由 tailscaled 发出,UID 是 0,与 65534 无关,于是走到 9004 被送进 TUN。
| 阶段 | 发包进程 | UID | 命中规则 | 结果 |
|---|---|---|---|---|
dnsmasq → 100.100.100.100 |
dnsmasq | 65534 | 5270 表 52 | 交给 tailscaled,不进 TUN |
tailscaled → 192.168.50.1:53 |
tailscaled | 0 | 9004 表 2022 | 进 TUN,被劫持成 Fake-IP |
验证方法:打开 Clash Verge Rev 的「日志」页(或 GET /logs?level=debug),同时执行一次 dig @192.168.155.1 example.com,日志里会出现:
[DNS] hijack udp:192.168.50.1:53 from 198.18.0.1:42824
192.168.50.1:53 是被劫持的目标,198.18.0.1 是 TUN 自己的地址。来源是 TUN 地址而不是 192.168.155.x 热点网段,说明这个 DNS 请求由主机本地进程发出,不是客户端直接发的。
结论:只要 dnsmasq 的上游是本机解析器(Tailscale、AdGuard Home、systemd-resolved 都算),exclude-uid 就无效,因为最后真正发出查询的是那个解析器进程。正确做法是让热点 dnsmasq 直接问路由器。
修复一:Merge 配置加两条排除规则
适用于「dnsmasq 上游是路由器」的情况。这是最小改动。
1. 在 Clash Verge Rev 里编辑当前订阅的 Merge 配置
打开 Clash Verge Rev → 配置 → 当前正在使用的订阅 → 编辑 Merge / Merge 配置 / 合并配置(不同版本叫法不同)。
不要直接改下面这个文件,它由程序生成,重启内核或切换配置后会被覆盖:
~/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml
在已有的 tun: 块里追加两项。不要新建第二个 tun: 块。
tun:
exclude-interface:
- wlp2s0
exclude-uid:
- 65534
替换规则:
wlp2s0→ 第 1 步查到的热点接口;65534→ 第 4 步查到的 dnsmasq UID 实际数字。
原有的 enable、auto-route、dns-hijack、stack 等保持不变,这里只是补两项。
2. 重启内核,让客户端重连
保存后在 Clash Verge Rev 里重启 Mihomo 内核,然后让客户端断开 Wi-Fi 再连。重连的目的是清掉客户端自己缓存的旧 DNS 结果。
检查生成的运行配置是否包含这两项:
rg -n -C 3 'exclude-interface|exclude-uid' \
~/.local/share/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml
Flatpak 等非标准安装的路径不同,以应用界面显示的生效配置为准。
修复二:让热点 dnsmasq 直接问路由器
适用于「dnsmasq 上游被 Tailscale / AdGuard Home / systemd-resolved 接管」的情况。修复一的排除规则要保留,两件事一起做才完整:
- Merge 里的
exclude-interface/exclude-uid负责让流量不进 TUN; - 下面这一项负责让DNS 答案是真实的公网 IP。
NetworkManager 启动热点 dnsmasq 时已经带了 --conf-dir=/etc/NetworkManager/dnsmasq-shared.d,往这个目录放一个文件即可,不需要改 NetworkManager 本身:
sudo mkdir -p /etc/NetworkManager/dnsmasq-shared.d
sudo tee /etc/NetworkManager/dnsmasq-shared.d/hotspot-upstream.conf >/dev/null <<'EOF'
# 热点专用 dnsmasq:忽略 /etc/resolv.conf,直接问路由器
no-resolv
server=192.168.50.1
EOF
把 192.168.50.1 换成你的路由器地址(ip route | grep default 里 via 后面那个)。
再关掉热点的 IPv6 共享。主机没有可分配的公网 IPv6 前缀时,客户端会优先用 IPv6 并拿到一条坏路由:
nmcli connection modify Hotspot ipv6.method disabled
nmcli connection down Hotspot
nmcli connection up Hotspot
新增的 hotspot-upstream.conf 只被热点专用的那个 dnsmasq 读取,不影响主机自己用 Tailscale MagicDNS,也不影响 Mihomo 的 Fake-IP 工作方式;nmcli connection modify 只改 Hotspot 这一个连接。
改完确认 dnsmasq 换了上游:
journalctl -b -u NetworkManager --no-pager | grep 'using nameserver' | tail -5
# 应只剩 192.168.50.1,不再出现 100.100.100.100
若这里仍显示 100.100.100.100,用 ps -eo args | grep '[d]nsmasq' 确认命令行里确实带 --conf-dir=/etc/NetworkManager/dnsmasq-shared.d。另外注意该目录下所有文件都会被当成配置读取,只有以 . 开头、以 ~ 结尾、或以 # 开头结尾的文件会被跳过;所以这个目录里不要放备份文件或其他内容。
no-resolv 的含义是”不要读 /etc/resolv.conf“,所以热点客户端从此只认 server= 指定的这一台上游。副作用:热点客户端无法解析 Tailscale 的 *.ts.net 名字。要访问 tailnet 内的机器,直接用它的 100.x.y.z 地址即可,这类请求不经过 DNS。
验证
按顺序做,能区分是”转发坏了”还是”DNS 坏了”:
# 1) 客户端上:IP 通不通
ping -c 3 1.1.1.1
# 2) 主机上:热点 DNS 是否还回假地址
dig @192.168.155.1 example.com A +short
# 必须是真实公网 IP,不能在 198.18.0.0/15 内
# 3) 主机上:不存在的域名应该没有答案
dig @192.168.155.1 not-exist-abc123.example.com A +short
# 应为空。有地址说明还是 Fake-IP
# 4) 客户端上:访问 https://example.com
# 5) 主机上:确认流量仍走物理网卡
ip route get 1.1.1.1 from 192.168.155.101 iif wlp2s0
# 应为 dev enp3s0
第 1 步通、第 2 步仍返回 198.18.x.x,按顺序排查三处:
- dnsmasq 的配置没生效 —— 重跑修复二里的
using nameserver检查; - 配置生效了但仍返回假地址 —— 说明 dnsmasq 发出的查询被 TUN 劫持,
exclude-uid没生效,重跑排查步骤第 4 步核对 UID; server=后面写的地址不对 —— 用ip route | grep default确认网关。
常见异常
| 现象 | 原因 | 处理 |
|---|---|---|
路由结果显示 dev Mihomo |
接口名写错,或 Merge 未生效 | 重查 ip -br addr,确认当前订阅,重启内核 |
dig 仍返回 198.18.x.x |
dnsmasq UID 与本机不符,或上游被别的解析器接管 | 重跑第 4、6 步;按修复二写 dnsmasq 配置 |
| 能 ping IP,域名全废 | DNS 答案仍是 Fake-IP | 同上 |
| IP 也 ping 不通 | 不是本文问题 | 查 nft 规则、上网接口、sysctl net.ipv4.conf.wlp2s0.forwarding |
| 关掉 TUN 才正常,开着就全废 | 默认排除配置未合并 | 确认 Merge 配置里的 tun: 块确实写入了 |
| 客户端偶发打不开部分网站 | 热点开了 IPv6 但上游没有可共享前缀 | 按修复二关闭热点 IPv6 |
| 客户端上装了自己的代理 App | 两层代理叠加(客户端 VPN + 主机 TUN),现象互相矛盾 | 排查时先在客户端退出代理 App |
最后一条值得单独说明:手机上开着 Clash 类 App 时,它会建立自己的 VPN 和默认路由。此时客户端”能 ping 通 1.1.1.1”可能只证明主机转发正常,而网页打不开是客户端自己的 Fake-IP 造成的。排查前先把客户端上的代理关掉,否则会在错误的方向上折腾很久。
exclude-uid 的影响范围
exclude-uid: 65534 不只匹配 DNS 端口,它让所有以该 UID 运行的本机进程都绕过 TUN。先确认这个 UID 上还有什么:
ps -eo user=,uid=,comm= | awk '$2 == 65534'
通常只有 dnsmasq 这类低权限服务。若有其他需要走代理的进程也在用这个 UID,需要重新评估。对 NetworkManager 的热点 dnsmasq 而言,这个范围很小,是安全的。
想让热点设备也走代理
本文的方案是让热点设备绕过代理,直连路由器。如果目标是让热点设备的流量也经过 Clash,处理方式相反:
删掉 Merge 里的 exclude-interface 和 exclude-uid,让客户端流量进 TUN。此时 Fake-IP 与数据路径重新一致,不需要改 DNS。
代价与注意事项:
- 热点设备的全部流量都会消耗代理订阅流量;
- Mihomo 需要能正确识别热点流量的来源,必要时配合
dns-hijack与路由标记; - 客户端自行设置的代理或 VPN 会与主机 TUN 冲突,应先排除。
不要只删 exclude-interface 而保留 exclude-uid,也不要在没改路由的情况下单独改 DNS,两者都会重新造出”解析与路径不一致”。
改完按同一套命令确认客户端流量确实进了 TUN:
ip route get 1.1.1.1 from 192.168.155.101 iif wlp2s0
# 此时应为 dev Mihomo,而不是 dev enp3s0
dig @192.168.155.1 example.com A +short
# 此时返回 198.18.x.x 是正常的,客户端流量会经 TUN 解释这个地址
为什么 Windows 上可能没这个问题
Windows 的 Internet Connection Sharing、DNS 代理和 TUN 驱动与 Arch 上的 NetworkManager + dnsmasq + Mihomo 不是同一套实现。Windows 的热点 DNS 服务可能根本不经过 TUN,或 TUN 驱动对共享流量的处理不同。
结论:不要照搬其他系统的结论,按本文排查步骤实际看数据走哪张网卡。
附:一次完整排查记录
下面是主机同时使用 Tailscale(接管 /etc/resolv.conf)时的输出,可作为对照。网段、网关、客户端地址、PID 均已替换为占位值,方法结论不受影响。
1. 转发路径正常,客户端流量走物理网卡
$ ip route get 1.1.1.1 from 192.168.155.101 iif wlp2s0
1.1.1.1 from 192.168.155.101 via 192.168.50.1 dev enp3s0
2. 客户端拿到的是 Fake-IP,即使域名根本不存在
$ dig @192.168.155.1 www.example.com A +short
198.18.1.16
$ dig @192.168.155.1 not-exist-abc123.example.com A +short
198.18.1.26
不存在的域名也有答案,说明回答者是 Mihomo,不是路由器。
3. 但客户端流量并不进 TUN,于是假地址无人处理
$ ip route get 198.18.1.26 from 192.168.155.101 iif wlp2s0
198.18.1.26 from 192.168.155.101 via 192.168.50.1 dev enp3s0
4. dnsmasq 的 UID 是 65534,排除规则也写了
$ ps -eo user,uid,pid,args | grep '[d]nsmasq'
nobody 65534 12345 /usr/bin/dnsmasq --conf-file=/dev/null --no-hosts ... --conf-dir=/etc/NetworkManager/dnsmasq-shared.d
5. 真正的原因:dnsmasq 的上游是本机 Tailscale
$ journalctl -b -u NetworkManager | grep -E 'dnsmasq.*nameserver'
dnsmasq[12345]: using nameserver 100.100.100.100#53
dnsmasq[12345]: using nameserver fd7a:115c:a1e0::53#53
/etc/resolv.conf 被 tailscaled 改写,NetworkManager 交给 dnsmasq 的上游就是 Tailscale,不是 192.168.50.1。
6. 路由规则里 Tailscale 的表排在 UID 排除之前
$ ip rule
5270: from all lookup 52
9000: from all uidrange 65534-65534 goto 9010
9003: not from all dport 53 lookup main suppress_prefixlength 0
9004: not from all iif lo lookup 2022
7. Mihomo 日志确认劫持来自 TUN 自身地址
[DNS] hijack udp:192.168.50.1:53 from 198.18.0.1:42824
来源是 198.18.0.1(TUN 地址),不是 192.168.155.101(客户端),证明这个查询是主机本地进程发出的,且没被 UID 规则放行。
结论与处理:按「修复二」给热点 dnsmasq 指定 server=192.168.50.1,脱离 Tailscale;「修复一」的排除规则保留。修复后第 2 步的第二个命令应返回空。
8. 单独验证 dnsmasq 配置本身可用
在落地前,可以先起一个临时 dnsmasq 实例确认配置文件格式正确:
T=$(mktemp -d)
printf 'no-resolv\nserver=192.168.50.1\n' > $T/hotspot-upstream.conf
dnsmasq --conf-file=/dev/null --no-hosts --listen-address=127.0.0.2 --port=5399 \
--conf-dir=$T --log-facility=-
启动日志会打出:
dnsmasq[54321]: using nameserver 192.168.50.1#53
说明 no-resolv 生效,上游已切换。此时向它查询仍会得到 198.18.x.x,原因是这个临时实例以当前登录用户(UID 1000)运行,不在 exclude-uid 的保护范围内,它的查询照样被 TUN 劫持。这恰好说明 exclude-uid 不能删。
清理:kill 掉该进程后 rm -rf $T。


