代理模式与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.8223.5.5.5),拿到 IP 后直接往物理网卡上送包。

整个过程 Clash 从头到尾不知情。

为什么 git 不读?因为读不读 “ 公告 “ 这件事,由每个应用自己决定。浏览器读,是因为 Chromium/Firefox 写代码时主动支持。git、Docker、Navicat、绝大多数游戏、Java 程序的 socket、Go 程序的默认 HTTP client——大部分都不读。

更糟的是:

  • 系统 DNS 查到的 github.com IP 可能被污染了,或者解析到 GitHub 在中国某个慢节点
  • 物理网卡那头是你家路由器 → 运营商 → 国际出口,每一跳都没经过代理
  • 你的运营商完全能看见 “ 这个 IP 去查了 github.com”

终端超时只是表象。真正的问题是:这些请求从未被 Clash 看到过。

3. TUN 模式:装一张假网卡

要把终端的流量也抓住,得换个抓法——不靠应用配合,靠操作系统的路由。

Clash 在系统里装一张虚拟网卡(macOS 下叫 utun0utun1 这种名字),然后改路由表,告诉系统:” 以后所有发往外网的 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 照旧把包送给系统、系统按路由表送给 utun0utun0 把包交给 Clash。Clash 看完决定送哪个节点。

代价是 Clash 拿到的是原始 IP 包——只有 “ 谁要去连 140.82.121.4”,没有域名信息

3.1 为什么只有 IP 没有域名

你敲 git clone https://github.com/... 之后发生了什么:

  1. git 在内存里先做一件事:调用系统 resolver,把 github.com 翻译成 140.82.121.4(经典 getaddrinfo() 调用)
  2. 翻译完,git 调用 connect(140.82.121.4, 443) 开 TCP socket。这一刻 "github.com" 这个字符串只活在 git 进程的局部变量里,从未离开 git
  3. 内核打 TCP SYN 包,包头只装 IP(源 IP + 目的 IP + 端口号)。IP 协议本身就不携带域名字段——这是 TCP/IP 几十年没变的层级约定
  4. 内核按路由表把包送给 utun0
  5. 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
2
- DOMAIN-SUFFIX,github.com,Proxy
- DOMAIN-SUFFIX,amazonaws.com.cn,DIRECT

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
2
3
DNS 覆写 = 关 + 订阅 dns.enable = true  → 用订阅的 dns 配置接管 ✓ (默认且推荐)
DNS 覆写 = 关 + 订阅没写 dns 块 → Mihomo 没 DNS 模块,TUN 残废 ⚠
DNS 覆写 = 开 + 任何订阅 → 用 Verge 的 dns_config.yaml 接管 ✓

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
2
3
4
5
6
dns:
enable: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/15
fake-ip-filter: [...]
nameserver: [...]

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.yaml is 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dns:
ipv6: false
enable: true
listen: 0.0.0.0:1053
use-hosts: false
default-nameserver: [119.28.28.28, 119.29.29.29, 223.5.5.5, 223.6.6.6]
nameserver:
- https://机场专属 DoH 服务器 1/dns-query/<token>
- https://机场专属 DoH 服务器 2/dns-query/<token>
- https://机场专属 DoH 服务器 3/dns-query/<token>
fake-ip-range: 198.18.0.1/15
fake-ip-filter:
- "*.lan" / "*.local" / "*.localhost" # 局域网
- "+.pool.ntp.org" / "ntp1-7.*.com" / "time1-7.*.com" # NTP 校时
- "stun.*.*" / "stun.*.*.*" # WebRTC
- "*.music.163.com" / "*.126.net" # 网易云 IP 校验
- "*.kuwo.cn" / "api.joox.com" / "*.y.qq.com" # QQ/酷我/JOOX
- "*.msftconnecttest.com" / "*.msftncsi.com" # Windows 网络探针
- "+.wargaming.net" / "*.srv.nintendo.net" # 游戏验证
# 共 60+ 条

亮点:

  • 用了机场自建的加密 DNS(DoH,你的 DNS 查询不经过运营商,看不到你查啥)
  • fake-ip-filter 把所有易翻车场景都列了:NTP 校时、WebRTC、流媒体 IP 校验、游戏验证
  • 所有字段齐全

结论:千万别开 DNS 覆写。开了你 100% 比这个差。

🥈 中等型:半成品

1
2
3
4
5
6
7
8
9
10
11
12
13
dns:
enable: true
ipv6: false
listen: 0.0.0.0:53
fake-ip-filter: # ⚠️ 写了 filter 但忘开 fake-ip
- "*.lan"
- "*.srv.nintendo.net" / "*.stun.playstation.net"
- "xbox.*.microsoft.com" / "*.xboxlive.com"
nameserver:
- 119.29.29.29
- 223.5.5.5
- tls://223.5.5.5:853 # DoT
- tls://120.53.53.53

诊断:

  • 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
2
3
4
5
dns:
enable: true
listen: '0.0.0.0:1053'
default-nameserver: [119.29.29.29, 223.5.5.5]
nameserver-policy: { +.机场专属域名.com: '124.x.x.x:1053' }

诊断:

  • 只有 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
2
3
4
nameserver:
- https://dns.alidns.com/dns-query # DoH
- tls://dns.alidns.com # DoT
- quic://dns.adguard.com # DoQ

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 覆写讲透了,剩下几个开关一笔带过:

开关实际控制建议
IPv6Clash 内核是否处理 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 模式)