Clash Proxy Tools
介绍了网络代理工具 Clash(以及 Shadowrocket 等同类型产品),包括它们的基本概念和工作机制,并且进行了较深入的剖析。
Clash Proxy Tools
前置知识
在阅读本文之前,你需要对计算机网络有基本了解,特别是网络层与 IP 协议;以及 VPN 的基本概念,例如 IPsec。可以先阅读我的相关笔记:
此外,本文约定如下术语:
流量(traffic):在本文中,流量泛指网络中被传输和处理的数据单元。根据讨论层次不同,它可以指 IP 包、TCP/UDP 段,或者应用层请求。若不特别说明,本文主要从 IP 包和 TCP/UDP 连接的角度讨论。
连接语义(flow semantics):指从一条网络流中可以恢复出的关键信息,例如目标 IP、目标端口、传输层协议、应用层协议,域名等等。
公网中的中间路由器:指本机公网出口与远程服务器公网入口之间(我们假设本机和远程服务器处于两个局域网内,并通过公网连接),负责转发外层 IP 包的互联网路由器。对于 VPN 隧道来说,这些路由器通常只能看到外层 IP 头,例如“本机公网出口 → VPN 网关”,而看不到被封装和加密的内层目标地址。
VPN 网关(VPN gateway):指 VPN 隧道的远端终点。客户端把封装后的隧道流量发往 VPN 网关;VPN 网关解封装后,再把内层 IP 包转发到目标网络或公网目标。
代理(proxy):代理是一种计算机网络中常用的抽象操作,指把“A 直接连接 B”改为“A 先连接 C,由 C 作为代理代替 A 连接 B”。代理可以出现在协议栈的不同层次,但日常所说的 HTTP proxy、SOCKS proxy、Clash / clash proxy 通常属于应用层代理。
Clash / mihomo:日常所说的 “Clash” 通常不是指某一个单独程序,而是泛指一类代理工具生态。这类工具通常由两部分组成:前端和后端。前端负责图形界面、配置管理、节点选择等交互;后端才是真正执行代理逻辑的核心。Clash 常用的后端是 mihomo。
类似 clash 的代理工具还包括 V2Ray / Xray、Shadowrocket 等。它们的具体协议、配置格式和实现细节不同,但基本工作模式相近:接收流量、解析连接语义、执行规则决策、重新出站。
因此,本文中若无特别说明,
Clash、mihomo等名称都作为泛称使用,指这一类代理工具,尤其是其中真正执行代理逻辑的后端程序。Clash TUN mode:clash 类型软件一般提供隧道模式(TUN mode,tunnel mode)和系统代理(system proxy)两种工作方式。后者比较简单,我们放在最后介绍。真正在功能上接近 VPN 的是隧道模式,因此本文中,除非特殊说明,否则介绍的都是隧道模式。
TUN 接口:Clash TUN 会创建虚拟网卡来在路由表中捕获流量(见下文),因此,这些虚拟网卡(一般来说,同一时间只用一个)也被称为 TUN 接口。
代理节点:指作为代理的路由器/主机。如果代理要翻墙,那么代理节点都位于境外(包括香港)。
翻墙:不解释了。
Clash 的基本组件
Clash 的配置可以粗略分为几类:
1 | inbound: |
什么是“订阅”?
clash 代理通常是要通过“购买订阅”才能使用的,也就是要付费。这里的订阅,通常是一个 URL。客户端导入订阅后,会下载一份 YAML 配置文件。这个配置文件里通常包含:
1 | proxies / proxy-providers: |
所以,购买代理服务时,拿到的本质上是一份配置文件。里面最重要的自然是代理节点。代理节点由于位于境外,因此可以用于翻墙。
订阅中的规则匹配
rules 是自上而下匹配的。通常是首次匹配成功后,就使用该规则指定的出站策略。
例如:
1 | rules: |
含义是:
1 | openai.com: |
常见规则类型:
1 | DOMAIN: |
规则顺序很重要。例如:
1 | rules: |
和:
1 | rules: |
效果不同。第二种写法里,最后一条通常不会生效,因为 MATCH 已经提前匹配了所有剩余连接。
出站策略:DIRECT、Proxy、REJECT
DIRECT、Proxy、REJECT 是规则匹配后的出站策略。
1 | DIRECT: |
例如:
1 | rules: |
表示 example.com 直连。
1 | rules: |
表示 example.com 交给 PROXY 策略组处理。
订阅文件示例
下面是一个订阅文件的示例:
1 | mixed-port: 7890 |
简单讲解:
1 | mixed-port: |
mixed-port 用于 System Proxy。在 TUN 模式中,clash 是通过路由表而不是 mixed-port 来拦截流量的。
Clash TUN 与 VPN 的区别
中文互联网经常把 clash TUN 和 VPN 软件混为一谈,甚至有人把 clash 也叫做 VPN,这是因为二者的用户体验很相似:都能让应用们“翻墙”。然而,clash 和 VPN 在技术上是不同的。
VPN:网络层隧道
严格来说,VPN 指网络层隧道技术,典型的例子是使用网络层安全协议 IPsec。虽然还存在 WireGuard 等隧道协议,但是它们不影响我们对 VPN 的理解,因此我们就以 IPsec 为例介绍 VPN。
IPsec 的核心机制是把原始 IP 数据包作为 payload 封装进去传输,本机的物理网卡和公网中的中间路由器看到的是“本机公网出口到 VPN 网关”的流量;原始目标地址位于被加密/封装的内层数据中,对中间路由器不可见。
Clash TUN:网络层入口 + 用户态代理
clash 的 TUN 模式会创建一个虚拟网卡,例如 macOS 上的 utunX,并通过修改路由表使部分或全部 IP 流量进入该虚拟网卡。随后,clash 在用户态从虚拟网卡读取这些 IP 包。
但 clash TUN 并不会像 VPN 那样,把原始 IP 包完整封装进另一个网络层隧道包中传输;它甚至没有定义新的网络层或传输层协议1。虚拟网卡在这里只是一个网络层入口:clash 通过它获得原始流量,并从中解析出其连接语义(例如从 IP / TCP / UDP 头部中恢复目标地址、目标端口、协议类型,域名等信息)
恢复连接语义后,clash 会在用户态执行规则匹配,并根据结果重新建立一条连接(或者不建立连接)。
简化流程:
1 | Application |
这里的 DIRECT、Proxy、REJECT` 是规则匹配后的出站策略:
- DIRECT: 重新建立一条到原始目标的新连接。
- Proxy: 重新建立一条到远程代理节点的新连接。
- REJECT: 拒绝连接。
物理网卡上看到的都是 clash 新建的出站连接,即图中的 new outbound connection,而不是原始 IP 包被修改后继续转发。
因此,clash TUN 依然是应用层代理,它只是入口在网络层。TUN 模式不同于 HTTPS_PROXY 环境变量或(后文讲到的)System Proxy 这类显式应用层代理,后两者都是在应用层将代理请求交给 clash;而 TUN 模式通过路由表在网络层接收 IP 包,再由 clash 在用户态从中恢复连接语义,执行规则匹配并建立新的出站连接。
System Proxy:另一种常见的代理模式
除了 TUN 模式,Clash/clash 还提供系统代理(System Proxy)模式。System Proxy 不会在网络层拦截数据包,而是修改操作系统的代理配置,让系统代理指向 clash 的本地监听端口。
例如在 macOS 的网络代理设置(System Settings -> WIFI -> Details -> Proxies)中,Clash 可以把这些代理项配置为:
1 | # 具体端口取决于 Clash 配置。 |
此外,若订阅文件中使用 mixed-port: 7890,则表示 clash 在 127.0.0.1:7890 上开启一个混合代理入口。这个端口可以同时接受 HTTP proxy 和 SOCKS5 proxy 请求。
System Proxy 的局限以及和 TUN 模式的区别
应用必须主动读取并遵守系统代理。遵守系统代理的应用会把请求交给 127.0.0.1:7890,随后由 clash 接收请求、执行规则匹配,并选择 DIRECT / Proxy / REJECT 等出站策略。
因此,系统代理是一种显式的应用层代理入口,它和我们设置环境变量:
1 | export HTTP_PROXY=http://127.0.0.1:7890 |
的效果是类似的。
浏览器都支持系统代理,但也有很多工具不支持,例如 pip,npm 等等,它们可能需要单独设置环境变量,或者干脆使用 TUN 模式。
System Proxy 和 TUN Mode 的区别可以概括为:
- System Proxy:流量入口在应用层
- 应用主动读取系统代理设置,并把代理请求发送给 clash 的本地端口。
- clash 从 HTTP proxy / SOCKS5 协议中直接获得目标地址、端口等连接语义。
- 随后 clash 在用户态规则匹配,并重新出站。
- TUN Mode:流量入口在网络层
- 系统路由把 IP 包导入 TUN 虚拟网卡。
- clash 在用户态读取这些 IP 包,并从中恢复目标地址、端口等连接语义。
- 随后 clash 在用户态规则匹配,并重新出站。
因此,二者的核心区别是入口不同。这也导致 TUN Mode 能覆盖几乎的应用范围比 System Proxy 更广:TUN Mode 能覆盖所有应用,只要它们的 IP 包被路由匹配;而 System Proxy 只能覆盖浏览器等常见应用。
实战:Clash TUN mode 的作用
本节给出一组 macOS 上的实验命令,用于观察本机网络配置、默认网关、路由表,以及开启 Clash / Mihomo TUN 模式前后的变化。
实验目标是理解:
- 不开 TUN:应用流量通常直接从默认物理网卡 en0 出站
- 开启 TUN:部分或全部流量先进入 utunX,Mihomo 在用户态接管后重新出站
1. 不开启 TUN 模式,本地只有物理网卡,查看路由
我们先不开启 TUN 模式,查看本机的网络接口:
1 | $ ifconfig |
可以看到,本机有 lo0 这个环回地址(在我的网络层协议笔记中提到)和 en0 这个物理网卡。在 macos 系统中,enX 代表的是连接 WIFI 网络的网卡。这说明我的电脑通过 WIFI 连接到了局域网 192.168.0.0/16(通过子网掩码 192.168.2.255 得出),并且 en0这个网卡 这个局域网中的 IPv4 地址为 192.168.2.202.
接着查看本机的默认路由:
1 | $ route -n get default |
这表示当前系统的默认路由为:
- 目标:default
- 下一跳:192.168.2.1
- 出接口:en0
默认路由就是当目标地址没有命中路由表中更具体的路由项时系统会用的路由,也就是用于“兜底”的路由。而这条默认路由的含义是:
- 会被匹配到默认路由的前缀是
default,在 IPv4 中,default 通常等价于0.0.0.0/0,也就是任意公网地址(因此可以用于“兜底”,因为任何公网地址如果没有被其它更具体的路由表项命中,就会命中这个0.0.0.0/0)。 - 默认路由的下一条路由器是
192.168.2.1,因此该路由器就是我们的默认网关, - 并且可以看到,凡是需要把包交给下一跳
192.168.2.1的路由,都会从本机接口en0发出。
下一步查看到达 8.8.8.8(Google的DNS server)的路由:
1 | $ route -n get 8.8.8.8 |
可以看到,路由和默认路由一样,这说明 对 8.8.8.8 的路由命中了默认路由(也可以在路由表中查看)。
然后,我们监视本机的 en0 发出的到达 8.8.8.8 的流量:
1 | sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
在另一个终端,我们访问 8.8.8.8:
1 | nc -vz 8.8.8.8 443 |
我们看到:
1 | $ sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
这说明不开 TUN 时,访问 8.8.8.8:443 的 IP 包直接从物理网卡 en0 发出。并且 curl 迟迟无法返回结果,这是因为我身处中国大陆,谷歌对中国大陆是不提供服务的。
2. 开启 TUN 模式,本地多出了虚拟网卡,查看路由
接下来,我打开 clash 的 TUN 模式,再次查看本机的网络接口:
1 | $ ifconfig -l |
发现多出了虚拟网卡 utun1, utun2, ...。它们都是 Mihomo 创建的。
然后查看路由:
1 | $ route -n get default |
开启 TUN 后,系统的 default route 仍然指向本地网关 192.168.2.1 和物理网卡 en0。但 Mihomo 额外安装了更具体的路由,例如 8.0.0.0/5 → 198.18.0.1 dev utun6。由于路由选择遵循最长前缀匹配,访问 8.8.8.8 时会优先命中这条更具体的 TUN 路由,而不是 default route。因此,该流量会先进入 utun6,再由 Mihomo 接管。
这里的
198.18.0.1不是一个真实的网关/路由器,而是 Mihomo TUN 虚拟出来的。它的作用是让系统把这类 IP 包交给utun6,再由 Mihomo 在用户态读取和处理。
我们可以通过查看完整路由表来验证 “Mihomo 额外安装了更具体的路由”,以及“访问 8.8.8.8 时会优先命中这条更具体的路由(指网关为 198.18.0.1 且网卡为 utun6)”:
1 | $ netstat -rn -f inet |
由于 8.8.8.8 ∈ 8.0.0.0/5,所以访问 8.8.8.8 时会命中:8/5 → 198.18.0.1 via utun6,走网关198.18.0.1和网卡 utun6。
3. 监视在访问 Google 时,TUN 接口以及物理接口的流量
接下来监视本机的 utun6 (TUN 接口) 发出的到达 8.8.8.8 的流量:
在一个终端:
1 | sudo tcpdump -i utun6 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
在另一个终端:
1 | nc -vz 8.8.8.8 443 |
我们得到:
1 | $ sudo tcpdump -i utun6 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
这说明访问 8.8.8.8:443 的连接已经进入 utun6(TUN 接口)。这里的 198.18.0.1 是 Mihomo TUN / fake-ip 相关的虚拟地址,而不是本机局域网地址。utun6 上看到的是原始目标连接视图:应用想访问 8.8.8.8:443。
根据前文对 clash TUN 模式的解释,我们知道,clash 在通过虚拟网卡捕获流量后,会读取其中的连接语义,并新建一条连接,重新走网络栈,并最终通过物理网卡发出。为了验证这点,我们继续抓 en0:
1 | sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
在另一个终端:
1 | nc -vz 8.8.8.8 443 |
我们得到:
1 | $ sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443' |
这条命令没有输出,说明 TUN 重建的是一个到远程代理节点的连接,而不是到8.8.8.8:443 这个原始目标的连接。而原始目标会被加密放在这条新的连接中,并会被代理节点恢复,这一点就不展开了。
现在我们证明这条到远程代理节点的连接是通过物理网卡 en0 发送的:我们开启 TUN 模式,同时抓取 utun6 和 en0,并且对 8.8.8.8:443 发起连接,我们观察到
1 | utun6: |
在 23:18:50.294,TUN 接口上出现了访问原始目标:198.18.0.1:57732 → 8.8.8.8:443,这里的 198.18.0.1 就是直掐你到的 Mimoho TUN 创建的虚拟地址;而在 23:18:50.296,物理网卡上新建了一条连接:192.168.2.202:57733 → 175.41.22.19:443。这两个事件只差约 2.8 ms,并且 en0 上的新连接随后完成了 TCP 握手并开始发送 payload(上面的文字记录中未显示)。
这个 175.41.22.19 是一个代理节点的地址,通过查询得知,它是个香港的地址,而我在 clash 中使用的是韩国节点。这意味着我用的 clash 实际上做了链式的策略节点调用,我的流量会先到达香港节点,再到达韩国节点,最终到达目的地址 8.8.8.8。
但经常会用加密或混淆技术以躲过攻击和检查↩︎