代理模式与DNS接管:Clash Verge实战入门
同一台 Mac,浏览器看 YouTube 顺畅得很,终端 git clone github.com:xxx/yyy.git 转半天超时。
节点没坏,规则也对,问题出在更早一步:这个包根本没被送到代理。
这篇文章跟着两个具体请求走一遍——浏览器看 YouTube、终端 git clone GitHub——看清谁把包交给了 Clash、谁没交。看完你能回答:
- 要不要开 TUN?
- fake-ip 到底是啥、为什么
ping会返回198.18.x.x? - Clash Verge 那个 “DNS 覆写 “ 开关到底覆写什么?我的订阅该不该开?
1. 浏览器为什么能走代理
打开 Clash Verge,点亮 “ 系统代理 “ 开关。它做的事其实很小——把系统里 “ 默认 HTTP 代理 “ 这个变量改成了 127.0.0.1:7897。
仅此而已。系统不会强迫任何应用去用这个代理,它只是 “ 挂了张公告 “。
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#fff', 'lineColor': '#60A5FA'}}}%%
flowchart LR
Browser["浏览器<br/>(守规矩)"] -->|"读公告"| Proxy["Clash<br/>127.0.0.1:7897"]
Proxy --> Remote1["远端"]
Terminal["终端<br/>(不读公告)"] --> NIC["物理网卡"]
NIC --> Remote2["远端 (漏了)"]
classDef ok fill:#10B981,stroke:#059669,color:#fff
classDef bad fill:#EF4444,stroke:#B91C1C,color:#fff
class Browser,Proxy,Remote1 ok
class Terminal,NIC,Remote2 bad浏览器是守规矩的。它启动时读这张公告,看到代理地址,就把每个 HTTPS 请求改成下面这种格式发给 Clash:
1 | CONNECT youtube.com:443 HTTP/1.1 |
注意 youtube.com 这五个字——浏览器把域名原文直接交给了 Clash。Clash 一眼看到 “youtube.com”,立刻就知道:
- 该走哪个节点(按域名匹配规则)
- 域名对应的 IP 是什么(让节点那头去查,本地完全不解析)
所以浏览器场景下:
- DNS 不用代理工具操心(域名已经送到代理了)
- 分流不靠 IP 靠域名(精准)
- 不会泄漏(你的 DNS 服务器看不到
youtube.com)
结论:纯浏览器用户开系统代理就够了,DNS 怎么配都没人在乎。
2. 终端为什么不走代理
切到终端,敲 git clone https://github.com/xxx/yyy.git。
git 不读那张公告。它会直接调用系统的 socket 函数,先把 github.com 翻译成 IP(找系统 DNS,可能是 8.8.8.8 或 223.5.5.5),拿到 IP 后直接往物理网卡上送包。
整个过程 Clash 从头到尾不知情。
为什么 git 不读?因为读不读 “ 公告 “ 这件事,由每个应用自己决定。浏览器读,是因为 Chromium/Firefox 写代码时主动支持。git、Docker、Navicat、绝大多数游戏、Java 程序的 socket、Go 程序的默认 HTTP client——大部分都不读。
更糟的是:
- 系统 DNS 查到的
github.comIP 可能被污染了,或者解析到 GitHub 在中国某个慢节点 - 物理网卡那头是你家路由器 → 运营商 → 国际出口,每一跳都没经过代理
- 你的运营商完全能看见 “ 这个 IP 去查了 github.com”
终端超时只是表象。真正的问题是:这些请求从未被 Clash 看到过。
3. TUN 模式:装一张假网卡
要把终端的流量也抓住,得换个抓法——不靠应用配合,靠操作系统的路由。
Clash 在系统里装一张虚拟网卡(macOS 下叫 utun0、utun1 这种名字),然后改路由表,告诉系统:” 以后所有发往外网的 IP 包,先送到这张虚拟网卡。”
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#fff', 'lineColor': '#60A5FA'}}}%%
flowchart LR
subgraph 没开TUN
B1["浏览器"] -->|"公告"| C1["Clash"]
T1["终端"] --> N1["物理网卡 (漏)"]
C1 --> N1
end
subgraph 开了TUN
B2["浏览器"] -->|"公告"| C2["Clash"]
T2["终端"] --> U["utun0<br/>虚拟网卡"]
U --> C2
C2 --> N2["物理网卡"]
end
classDef bad fill:#EF4444,stroke:#B91C1C,color:#fff
classDef ok fill:#10B981,stroke:#059669,color:#fff
class N1 bad
class U,C2 ok应用不需要任何配合。git 照旧把包送给系统、系统按路由表送给 utun0、utun0 把包交给 Clash。Clash 看完决定送哪个节点。
代价是 Clash 拿到的是原始 IP 包——只有 “ 谁要去连 140.82.121.4”,没有域名信息。
3.1 为什么只有 IP 没有域名
你敲 git clone https://github.com/... 之后发生了什么:
- git 在内存里先做一件事:调用系统 resolver,把
github.com翻译成140.82.121.4(经典getaddrinfo()调用) - 翻译完,git 调用
connect(140.82.121.4, 443)开 TCP socket。这一刻"github.com"这个字符串只活在 git 进程的局部变量里,从未离开 git - 内核打 TCP SYN 包,包头只装 IP(源 IP + 目的 IP + 端口号)。IP 协议本身就不携带域名字段——这是 TCP/IP 几十年没变的层级约定
- 内核按路由表把包送给 utun0
- Clash 从 utun0 收包,只能看到 IP——域名在第 1 步就被消化掉了
类比:你寄快递时填了 “ 收件人姓名 + 详细地址 “。快递员路上只看地址(IP),姓名(域名)是给最终收件人核对用的,路上没人关心。TUN 就是路上拦快递的——看到地址,看不到姓名。
这就是为什么 Clash 必须自己接管 DNS——它要在第 1 步 “ 翻译 “ 时就介入,亲手把 github.com → 140.82.121.4 这层映射记下来。后面收包看到 140.82.121.4,反查就知道这是 github,可以按域名规则分流。
4. TUN 开了之后,DNS 必须有人管
包到了 Clash 这只看得到 140.82.121.4。但你的代理规则多半是按域名写的:
1 | - DOMAIN-SUFFIX,github.com,Proxy |
140.82.121.4 怎么对上 github.com?答案是:Clash 必须把自己也变成一个 DNS 服务器,亲自拦截系统的每一次 DNS 查询,自己回应、自己记账。这样它就掌握了 “ 谁问过什么域名 “ 的全部历史,连接来的时候反查一下就能还原。
这件事在 Mihomo 内核里叫 dns.enable: true(就是订阅 yaml dns: 块里那个 enable 字段),几乎所有机场订阅都默认开着。TUN 模式 = 必须有人接管 DNS,要么订阅 YAML 自带一份 dns: 块、要么你自己提供一份。
4.1 dns.enable 和 “DNS 覆写 “ 是两回事
很多人一看到都有 “enable”,以为是同一个开关。其实在两个完全不同的层次:
| 开关 | 在哪 | 控制什么 |
|---|---|---|
dns.enable: true | 订阅 yaml 的 dns: 块里 | Mihomo 内核 “ 要不要启动 DNS 接管模块 “ |
“DNS 覆写 “(enable_dns_settings) | Clash Verge GUI 设置页 | Verge “ 用哪份 yaml 喂给 Mihomo” |
实际三种组合:
1 | DNS 覆写 = 关 + 订阅 dns.enable = true → 用订阅的 dns 配置接管 ✓ (默认且推荐) |
dns 块怎么写、什么时候该开覆写、怎么看自己订阅的 dns 块写得好不好——下面几章一一回答。
5. 两种答法:redir-host 和 Fake-ip
Clash 接管 DNS 后,对每个域名查询有两种回应方式。一个慢但老实,一个快但耍滑头。看完你就知道为什么 95% 的机场都选了后者。
5.1 用餐厅取餐打比方
你在餐厅点了 “ 麻婆豆腐 “。
老实人(redir-host):服务员真跑去厨房问主厨 “ 这道菜哪个灶台做 “,主厨说 “3 号灶台 “,服务员回来告诉你 “ 去 3 号灶台取 “。你过去等。
问题来了——3 号灶台同时还做着鱼香肉丝、宫保鸡丁。菜端出来五盘,你不知道哪盘是自己的。
这正是 redir-host 在 CDN 时代的尴尬:现代 CDN 一个 IP 服务几千个域名,142.250.80.46 到底是 youtube 还是 google?反查时根本分不清。
耍滑头(fake-ip):服务员根本不去厨房,当场给你贴一个 “ 叫号牌 17 号 “。等灶台做好菜,广播 “17 号取餐 “。
每个客人一个独立号牌,绝不混淆。服务员也省了跑厨房——你拿到号牌的瞬间 DNS 应答就完成了,零延迟。
5.2 回到代理工具
你的浏览器查 youtube.com 时,fake-ip 模式下 Clash 是这么干的:
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#10B981', 'actorTextColor': '#fff'}}}%%
sequenceDiagram
autonumber
participant App as "浏览器 (点菜的)"
participant Clash as "Clash (发号牌的服务员)"
participant Remote as "代理节点 (真厨房)"
App->>Clash: "查 youtube.com (点麻婆豆腐)"
Clash->>Clash: "从池里发一个: 198.18.0.5 (贴叫号牌 17)"
Clash->>Clash: "记下 198.18.0.5 ↔ youtube.com"
Clash-->>App: "198.18.0.5 (拿到号牌, 零延迟)"
App->>Clash: "连 198.18.0.5:443 (按号去取菜)"
Clash->>Clash: "查表: 198.18.0.5 是 youtube.com"
Clash->>Remote: "让节点那头去查真 IP 并连接"三个细节:
198.18.0.5这种号牌从198.18.0.0/15这段 IP 池里发。IANA 把这段 IP 划给 “ 基准测试用 “—— 正常互联网根本不会出现这种地址,发再多号也不会撞到真实服务器- 你的应用拿着
198.18.0.5去连接的瞬间,Clash 一眼就知道这是youtube.com(号牌对应表) - 真正的 DNS 查询延迟到代理节点那边去做——你这边一点都不等
5.3 两种模式对比
| 维度 | redir-host(老实人) | fake-ip(耍滑头) |
|---|---|---|
| DNS 应答速度 | 慢(要去厨房问) | 零延迟(当场发号牌) |
| 给应用的 IP | 真实 IP | 假 IP(198.18.x.x) |
| CDN 反查精度 | 差(一个灶台多道菜) | 完美(一人一号绝不撞) |
| 泄漏风险 | 较高 | 极低(号牌不离店) |
| 默认 | 老应用 / IP 敏感场景 | 95% 机场订阅默认这个 |
实际上 95% 的机场订阅、所有主流配置示例,都用 fake-ip。所以你日后看到的 dns 块大概率长这样:
1 | dns: |
5.4 一个让人困惑的现象
ping youtube.com 返回 198.18.0.5,你以为代理坏了。
其实那就是 fake-ip 给的叫号牌。ping 用的是 ICMP 协议,它不走代理(拿着号牌没去 “ 取餐 “),所以表现是 “ 不通 “。这是 fake-ip 的正常表现,不是故障。
要测代理是不是真的工作,用 curl -I https://youtube.com——curl 走 TCP,会真去 “ 取餐 “,Clash 会按号牌还原成 youtube.com 走代理。
fake-ip 也有副作用——比如局域网域名 printer.local 也会被发假 IP,瞬间废掉。怎么用 fake-ip-filter 救场、Navicat 直连域名怎么还原,这些细节在 下一篇 详讲。
6. Clash Verge “DNS 覆写 “ 到底覆写了什么
这是新手问得最多、机场文档说得最含糊、官方设置页起名最误导的开关。
先看一眼源码里它的真名:在 Clash Verge Rev 仓库的 verge.rs 里,这个开关对应字段叫 enable_dns_settings。注释只有一行:
this controls whether
dns_config.yamlis applied
翻译成人话:这个开关只控制一件事——要不要用 Verge 本地的那份 dns_config.yaml,去整块替换订阅 YAML 里的 dns: 块。
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#fff', 'lineColor': '#60A5FA'}}}%%
flowchart LR
Sub["订阅 YAML<br/>(含 dns 块)"] --> Merge["各种 merge / script"]
Merge --> Q{"DNS 覆写 = 开?"}
Q -->|"是"| Replace["读 dns_config.yaml<br/>整块替换 dns 块"]
Q -->|"否"| Keep["啥也不做<br/>订阅的 dns 块保留"]
Replace --> Final["最终配置"]
Keep --> Final
Final --> Mihomo["Mihomo 内核"]
classDef key fill:#EF4444,stroke:#B91C1C,color:#fff
classDef ok fill:#10B981,stroke:#059669,color:#fff
class Q key
class Keep ok注意 “ 整块替换 “ 这四个字。不是 merge、不是覆盖个别字段,是直接 config.insert("dns", new_dns),订阅那份 dns: 块完全消失。
6.1 三种情况,三种配法
| 你的状况 | DNS 覆写开关 | 谁在跑 DNS |
|---|---|---|
机场订阅 YAML 写好了 dns: 块(多数情况) | 关(默认) | 订阅自带的 dns 配置 |
订阅没写 dns: 块(少数粗糙机场) | 开 | Verge UI 里你填的 DNS |
| 完全不信任机场的 DNS、想自己控制全套 | 开,或用脚本注入 | 你自己填的 |
99% 的人属于第一种。所以默认就是关的,是对的。
6.2 怎么看最终生效的 DNS(开了覆写也适用)
“ 开了 DNS 覆写后,最后到底跑的是哪份 DNS 配置 “——这个问题靠 GUI 看不出来,必须翻文件。Verge 涉及两个 dns 相关文件,千万别搞混:
| 文件 | 路径(macOS) | 角色 |
|---|---|---|
dns_config.yaml | ~/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/dns_config.yaml | 输入——你在 UI “ 设置 DNS” 编辑的那份,仅在 DNS 覆写开启时被读 |
clash-verge.yaml | ~/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml | 输出——所有 merge/script/ 覆写处理完后的最终结果,真正送给 Mihomo 内核运行的就是这份 |
Mihomo 的 external-controller HTTP API(默认 127.0.0.1:9090)不暴露 dns 字段——GET /configs 返回的是端口 /TUN/ 模式等基础项,没有 dns。所以唯一可靠的查看方式是读 clash-verge.yaml。
6.3 三份真实订阅对照(机场 Dns 配置打分)
🥇 精心型:完整 Dns 块
1 | dns: |
亮点:
- 用了机场自建的加密 DNS(DoH,你的 DNS 查询不经过运营商,看不到你查啥)
fake-ip-filter把所有易翻车场景都列了:NTP 校时、WebRTC、流媒体 IP 校验、游戏验证- 所有字段齐全
结论:千万别开 DNS 覆写。开了你 100% 比这个差。
🥈 中等型:半成品
1 | dns: |
诊断:
- ✅
nameserver给了 DoT(加密 DNS,虽不如 DoH 抗封锁) - ✅
fake-ip-filter列了主流游戏服务 - ❌ 致命漏洞:没设
enhanced-mode: fake-ip,默认会退化到redir-host——意味着写的fake-ip-filter根本不会被读(filter 是 fake-ip 模式专属) - ❌ 没有
fallback/fallback-filter防污染机制
结论:不建议开 DNS 覆写(开了会把 nameserver 也盖掉),建议用脚本补一行 enhanced-mode: fake-ip 让 filter 生效,再补 fallback 防污染。脚本怎么写见 下一篇 ch.5。
🥉 敷衍型:4 行交差
1 | dns: |
诊断:
- 只有 4 行
- 没
nameserver、没fake-ip-filter、没enhanced-mode、没fallback - 唯一的
nameserver-policy只是为了解析他们自家节点域名(自利型)——对用户上网体验毫无帮助 - 跑这份等于 “ 把 DNS 接管开关打开了,但没干任何活儿 “
结论:建议开 DNS 覆写,让 Verge 接管。或更优——用全局扩展脚本注入完整 dns 块(下一篇 ch.5 给模板)。
DoH / DoT / DoQ 什么意思
| 协议 | 端口 | 性能 | 抗封锁 | 一句话评价 |
|---|---|---|---|---|
| DoH(HTTPS) | 443 | 中 | 强(混进普通 HTTPS 流量) | 默认选这个,几乎所有工具都支持 |
| DoT(TLS) | 853 | 中 | 弱(专用端口易识别) | 适合可控内网(家里 AdGuard Home) |
| DoQ(QUIC) | 853 / 443 | 最高 | 中 | 趋势,丢包多的链路有优势,sing-box 和较新 Mihomo 支持 |
配置上就是 URL scheme 不同:
1 | nameserver: |
Mihomo 1.18+ 对 DoQ 支持比较稳,可以试试在 prefer-h3: true 配合下能省一两轮握手。
6.4 机场为什么劝你关
几乎每家机场的设置教程都重复同一句话:” 关闭 DNS 覆写、关闭 IPv6、打开统一延迟 “。
原因不是 “ 关掉更安全 “,是 “ 关掉才能让我们的配置生效 “。精心型机场(如对照里的第一份)花心思配了自家加密 DNS、针对性 fake-ip-filter——你一开覆写,这些定制全被 Verge 通用模板盖掉,出问题概率立刻上升。
所以这个开关的真名应该叫 “ 用 Verge 的 DNS 配置替换订阅的 DNS 配置 “。可惜 UI 上就写 “DNS 覆写 “ 三个字,谁看了都以为是 “ 接不接管 DNS”。
7. Verge 其他几个开关
DNS 覆写讲透了,剩下几个开关一笔带过:
| 开关 | 实际控制 | 建议 |
|---|---|---|
| IPv6 | Clash 内核是否处理 IPv6 流量 | 用不上 IPv6 就关。开着但节点不支持 IPv6 会出现 “IPv6 走直连泄漏 “ |
| 统一延迟 | 把 TCP 握手 + TLS 握手都算进节点测速 | 开。否则测速不真实 |
| 局域网连接 | 是否允许同网段其他设备用你的 Clash 当代理 | 不需要就关。开等于在局域网开了个公开代理端口 |
8. 顺手澄清一个高频误会:Global ≠ 全系统代理
很多人以为切到 Global 模式 就能 “ 全系统都走代理 “,终端不走、Navicat 走错节点的问题也就解决了。错。
| 策略 | 行为 |
|---|---|
| Rule(规则模式) | 智能分流:国内直连、国外代理、MATCH 兜底 |
| Global(全局模式) | 忽略规则,所有流量强制走代理 |
| Direct(直连模式) | 所有流量直连,不走代理 |
Global 的含义是 “抓到的全代理 “,不是 “ 全系统都被抓 “。抓的活儿是 TUN 干的,分流策略(Rule/Global/Direct)只决定 “ 抓到之后怎么处理 “。两件事,别合并理解:
| TUN | 模式 | 实际效果 |
|---|---|---|
| 关 | Rule | 仅浏览器走代理,分流生效 |
| 关 | Global | 仅浏览器强制走代理,终端依旧漏 |
| 开 | Rule | 全系统接管,国内直连国外代理(开发首选) |
| 开 | Global | 全系统接管,国内服务也走代理(排障用) |
关键区分
- ClashX「增强模式」= TUN 机制(决定能否捕获终端流量)
- Quantumult X「全部代理」= Global 策略(但是因 VPN 架构,天生运行于 TUN 模式)