Clash Proxy Tools

介绍了网络代理工具 Clash(以及 Shadowrocket 等同类型产品),包括它们的基本概念和工作机制,并且进行了较深入的剖析。


Clash Proxy Tools

前置知识

在阅读本文之前,你需要对计算机网络有基本了解,特别是网络层与 IP 协议;以及 VPN 的基本概念,例如 IPsec。可以先阅读我的相关笔记:

  1. 计算机网络概论
  2. 计算机网络的网络层
  3. IPsec 和 VPN 简介

此外,本文约定如下术语:

  • 流量(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 等。它们的具体协议、配置格式和实现细节不同,但基本工作模式相近:接收流量、解析连接语义、执行规则决策、重新出站。

    因此,本文中若无特别说明Clashmihomo 等名称都作为泛称使用,指这一类代理工具,尤其是其中真正执行代理逻辑的后端程序。

  • Clash TUN mode:clash 类型软件一般提供隧道模式(TUN mode,tunnel mode)和系统代理(system proxy)两种工作方式。后者比较简单,我们放在最后介绍。真正在功能上接近 VPN 的是隧道模式,因此本文中,除非特殊说明,否则介绍的都是隧道模式。

  • TUN 接口:Clash TUN 会创建虚拟网卡来在路由表中捕获流量(见下文),因此,这些虚拟网卡(一般来说,同一时间只用一个)也被称为 TUN 接口。

  • 代理节点:指作为代理的路由器/主机。如果代理要翻墙,那么代理节点都位于境外(包括香港)。

  • 翻墙:不解释了。

Clash 的基本组件

Clash 的配置可以粗略分为几类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
inbound:
流量入口。决定应用流量(在不同层次有不同的名字,例如应用层报文,或者传输层数据段,网络层数据包)如何进入 clash。
例如 system proxy、mixed-port、SOCKS / HTTP proxy、TUN。

rules:
分流规则。决定一条连接应该直连、拒绝,还是走代理。如果走代理的话,还可以决定哪个代理节点/代理组。常见规则例如: chatgpt.com 走 PROXY,中国大陆 IP 走 DIRECT,其余流量走 PROXY。

proxy-groups:
代理节点组。规则通常指向代理节点组,而不是直接指向某个节点。

proxies / proxy-providers:
代理节点或节点提供器。是真正的远程出站服务器。

dns:
DNS 处理机制。包括 DNS 接管、fake-ip、DoH / DoT 等。

tun:
TUN 配置。决定是否通过虚拟网卡在网络层接管系统流量。

mode:
运行模式。常见值是 rule、global、direct。决定一条连接应该直连、拒绝,还是走代理。rule 代表的是遵循 rules 指定的规则;direct 代表所有连接都不走代理,直接连接;而 global 模式代表所有连接都走代理。

什么是“订阅”?

clash 代理通常是要通过“购买订阅”才能使用的,也就是要付费。这里的订阅,通常是一个 URL。客户端导入订阅后,会下载一份 YAML 配置文件。这个配置文件里通常包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
proxies / proxy-providers:
远程代理节点

proxy-groups:
节点分组与选择策略

rules:
分流规则

dns:
DNS 配置

tun:
TUN 配置

所以,购买代理服务时,拿到的本质上是一份配置文件。里面最重要的自然是代理节点。代理节点由于位于境外,因此可以用于翻墙。

订阅中的规则匹配

rules 是自上而下匹配的。通常是首次匹配成功后,就使用该规则指定的出站策略。

例如:

1
2
3
4
5
rules:
- DOMAIN-SUFFIX,openai.com,PROXY
- DOMAIN-SUFFIX,apple.com,DIRECT
- GEOIP,CN,DIRECT
- MATCH,PROXY

含义是:

1
2
3
4
5
6
7
8
9
10
11
openai.com:
走 PROXY。

apple.com:
直连。

中国大陆 IP:
直连。

其余所有连接:
走 PROXY。

常见规则类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DOMAIN:
精确匹配完整域名。

DOMAIN-SUFFIX:
匹配域名后缀。

DOMAIN-KEYWORD:
匹配域名关键词。

IP-CIDR:
匹配 IP 网段。

GEOIP:
按 IP 所属地区匹配。

PROCESS-NAME:
按进程名匹配。

MATCH:
兜底规则,匹配所有剩余连接。

规则顺序很重要。例如:

1
2
3
4
rules:
- DOMAIN-SUFFIX,example.com,PROXY
- GEOIP,CN,DIRECT
- MATCH,PROXY

和:

1
2
3
4
rules:
- GEOIP,CN,DIRECT
- MATCH,PROXY
- DOMAIN-SUFFIX,example.com,PROXY

效果不同。第二种写法里,最后一条通常不会生效,因为 MATCH 已经提前匹配了所有剩余连接。

出站策略:DIRECT、Proxy、REJECT

DIRECTProxyREJECT 是规则匹配后的出站策略。

1
2
3
4
5
6
7
8
DIRECT:
直连。clash 直接连接原始目标。

Proxy / Proxy Group:
走代理。clash 连接远程代理节点,并通过代理协议传递原始目标信息。

REJECT:
拒绝连接。

例如:

1
2
rules:
- DOMAIN-SUFFIX,example.com,DIRECT

表示 example.com 直连。

1
2
rules:
- DOMAIN-SUFFIX,example.com,PROXY

表示 example.com 交给 PROXY 策略组处理。

订阅文件示例

下面是一个订阅文件的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
mixed-port: 7890
allow-lan: false
bind-address: 127.0.0.1
mode: rule
log-level: info
external-controller: 127.0.0.1:9090

dns:
enable: true
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
default-nameserver:
- 223.5.5.5
- 119.29.29.29
nameserver:
- https://dns.example.com/dns-query
fallback:
- https://fallback-dns.example.com/dns-query
fallback-filter:
geoip: true
ipcidr:
- 240.0.0.0/4
- 0.0.0.0/32

proxies:
- name: Japan-01
type: ss
server: proxy-jp.example.com
port: 443
cipher: aes-128-gcm
password: your-password-here
udp: true

- name: HongKong-01
type: vless
server: proxy-hk.example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
udp: true
tls: true
servername: example.com
flow: xtls-rprx-vision
client-fingerprint: chrome

proxy-groups:
- name: PROXY
type: select
proxies:
- AUTO
- FALLBACK
- Japan-01
- HongKong-01
- DIRECT

- name: AUTO
type: url-test
proxies:
- Japan-01
- HongKong-01
url: http://cp.cloudflare.com/generate_204
interval: 300
timeout: 3000

- name: FALLBACK
type: fallback
proxies:
- Japan-01
- HongKong-01
url: http://cp.cloudflare.com/generate_204
interval: 300
timeout: 3000

rules:
- DOMAIN-SUFFIX,openai.com,PROXY
- DOMAIN-SUFFIX,chatgpt.com,PROXY
- DOMAIN-SUFFIX,google.com,PROXY

- DOMAIN-SUFFIX,apple.com,DIRECT
- DOMAIN-SUFFIX,icloud.com,DIRECT

- GEOIP,CN,DIRECT
- MATCH,PROXY

简单讲解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mixed-port:
本地代理入口端口。应用可以通过 127.0.0.1:7890 把流量交给 clash。

dns:
DNS 配置。这里启用了 fake-ip,clash 会用 198.18.0.0/16 这样的虚拟地址维护 domain → fake-ip 映射。

proxies:
具体代理节点。这里示例写了一个 Shadowsocks 节点和一个 VLESS 节点。

proxy-groups:
这里的代理节点组中的 PROXY 是手动选择组,AUTO 是自动测速组,FALLBACK 是故障转移组。

rules:
分流规则,自上而下匹配。
openai.com / chatgpt.com / google.com 走 PROXY;
apple.com / icloud.com 直连;
中国大陆 IP 直连;
其余流量默认走 PROXY。

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Application

TCP / UDP

IP
↓ routing
utunX

clash reads packets in user space

recover flow metadata:
dst IP, dst port, protocol, domain if available

rule matching

outbound decision:
DIRECT / Proxy / REJECT

new outbound connection

physical NIC

这里的 DIRECTProxyREJECT` 是规则匹配后的出站策略:

  1. DIRECT: 重新建立一条到原始目标的新连接。
  2. Proxy: 重新建立一条到远程代理节点的新连接。
  3. 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
2
3
4
5
6
7
8
9
10
11
12
# 具体端口取决于 Clash 配置。
Web Proxy (HTTP):
Server = 127.0.0.1
Port = 7890

Secure Web Proxy (HTTPS):
Server = 127.0.0.1
Port = 7890

SOCKS Proxy:
Server = 127.0.0.1
Port = 7890

此外,若订阅文件中使用 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
2
3
export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
export ALL_PROXY=socks5://127.0.0.1:7890

的效果是类似的。

浏览器都支持系统代理,但也有很多工具不支持,例如 pipnpm 等等,它们可能需要单独设置环境变量,或者干脆使用 TUN 模式。

System Proxy 和 TUN Mode 的区别可以概括为:

  1. System Proxy:流量入口在应用层
    • 应用主动读取系统代理设置,并把代理请求发送给 clash 的本地端口。
    • clash 从 HTTP proxy / SOCKS5 协议中直接获得目标地址、端口等连接语义
    • 随后 clash 在用户态规则匹配,并重新出站。
  2. TUN Mode:流量入口在网络层
    • 系统路由把 IP 包导入 TUN 虚拟网卡
    • clash 在用户态读取这些 IP 包,并从中恢复目标地址、端口等连接语义
    • 随后 clash 在用户态规则匹配,并重新出站。

因此,二者的核心区别是入口不同。这也导致 TUN Mode 能覆盖几乎的应用范围比 System Proxy 更广:TUN Mode 能覆盖所有应用,只要它们的 IP 包被路由匹配;而 System Proxy 只能覆盖浏览器等常见应用。

实战:Clash TUN mode 的作用

本节给出一组 macOS 上的实验命令,用于观察本机网络配置、默认网关、路由表,以及开启 Clash / Mihomo TUN 模式前后的变化。

实验目标是理解:

  1. 不开 TUN:应用流量通常直接从默认物理网卡 en0 出站
  2. 开启 TUN:部分或全部流量先进入 utunX,Mihomo 在用户态接管后重新出站

1. 不开启 TUN 模式,本地只有物理网卡,查看路由

我们先不开启 TUN 模式,查看本机的网络接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
nd6 options=201<PERFORMNUD,DAD>
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
ether bc:d0:74:18:63:86
inet6 fe80::181d:259e:78aa:4346%en0 prefixlen 64 secured scopeid 0xe
inet 192.168.2.202 netmask 0xffffff00 broadcast 192.168.2.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
<snip>

可以看到,本机有 lo0 这个环回地址(在我的网络层协议笔记中提到)和 en0 这个物理网卡。在 macos 系统中,enX 代表的是连接 WIFI 网络的网卡。这说明我的电脑通过 WIFI 连接到了局域网 192.168.0.0/16(通过子网掩码 192.168.2.255 得出),并且 en0这个网卡 这个局域网中的 IPv4 地址为 192.168.2.202.

接着查看本机的默认路由:

1
2
3
4
5
6
7
8
9
$ route -n get default                                                                                      
route to: default
destination: default
mask: default
gateway: 192.168.2.1
interface: en0
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 1500 0

这表示当前系统的默认路由为:

  • 目标: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
2
3
4
5
6
7
8
9
$ route -n get 8.8.8.8
route to: 8.8.8.8
destination: default
mask: default
gateway: 192.168.2.1
interface: en0
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 1500 0

可以看到,路由和默认路由一样,这说明 对 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
2
3
4
5
$ sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443'
listening on en0, link-type EN10MB (Ethernet), snapshot length 524288 bytes
17:33:29.811013 IP 192.168.2.202.64872 > 8.8.8.8.443: Flags [SEW], seq 1386591602, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1713004934 ecr 0,sackOK,eol], length 0
17:33:30.812852 IP 192.168.2.202.64872 > 8.8.8.8.443: Flags [S], seq 1386591602, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1713005935 ecr 0,sackOK,eol], length 0
<snip>

这说明不开 TUN 时,访问 8.8.8.8:443 的 IP 包直接从物理网卡 en0 发出。并且 curl 迟迟无法返回结果,这是因为我身处中国大陆,谷歌对中国大陆是不提供服务的。

2. 开启 TUN 模式,本地多出了虚拟网卡,查看路由

接下来,我打开 clash 的 TUN 模式,再次查看本机的网络接口:

1
2
$ ifconfig -l 
lo0 en0 utun1 utun2 utun3 utun4 utun5 utun6

发现多出了虚拟网卡 utun1, utun2, ...。它们都是 Mihomo 创建的。

然后查看路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ route -n get default
route to: default
destination: default
mask: default
gateway: 192.168.2.1
interface: en0
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 1500 0

$ route -n get 8.8.8.8
route to: 8.8.8.8
destination: 8.0.0.0
mask: 248.0.0.0
gateway: 198.18.0.1
interface: utun6
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 9000 0

开启 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
2
3
4
5
6
7
8
9
10
11
12
$ netstat -rn -f inet
default 192.168.2.1 UGScg en0
1 198.18.0.1 UGSc utun6
2/7 198.18.0.1 UGSc utun6
4/6 198.18.0.1 UGSc utun6
8/5 198.18.0.1 UGSc utun6
16/4 198.18.0.1 UGSc utun6
32/3 198.18.0.1 UGSc utun6
64/2 198.18.0.1 UGSc utun6
128.0/1 198.18.0.1 UGSc utun6
192.168.2 link#14 UCS en0
<snip>

由于 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
2
3
4
5
6
7
8
9
$ sudo tcpdump -i utun6 -nn -c 10 'host 8.8.8.8 and tcp port 443'
listening on utun6, link-type NULL (BSD loopback), snapshot length 524288 bytes
17:52:02.489537 IP 198.18.0.1.49612 > 8.8.8.8.443: Flags [SEW], seq 4170619816, win 65535, options [mss 8960,nop,wscale 6,nop,nop,TS val 326139574 ecr 0,sackOK,eol], length 0
17:52:02.489843 IP 8.8.8.8.443 > 198.18.0.1.49612: Flags [S.E], seq 4211469811, ack 4170619817, win 65535, options [mss 8960,nop,wscale 6,nop,nop,TS val 1928652959 ecr 326139574,sackOK,eol], length 0
17:52:02.489884 IP 198.18.0.1.49612 > 8.8.8.8.443: Flags [.], ack 1, win 4335, options [nop,nop,TS val 326139574 ecr 1928652959], length 0
17:52:02.489987 IP 8.8.8.8.443 > 198.18.0.1.49612: Flags [.], ack 1, win 4335, options [nop,nop,TS val 1928652959 ecr 326139574], length 0
17:52:02.491955 IP 198.18.0.1.49612 > 8.8.8.8.443: Flags [F.], seq 1, ack 1, win 4335, options [nop,nop,TS val 326139576 ecr 1928652959], length 0
17:52:02.492090 IP 8.8.8.8.443 > 198.18.0.1.49612: Flags [.], ack 2, win 4335, options [nop,nop,TS val 1928652961 ecr 326139576], length 0
17:52:07.455143 IP 8.8.8.8.443 > 198.18.0.1.49612: Flags [F.], seq 1, ack 2, win 4335, options [nop,nop,TS val 1928657924 ecr 326139576], length 0

这说明访问 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
2
$ sudo tcpdump -i en0 -nn -c 10 'host 8.8.8.8 and tcp port 443'
listening on en0, link-type EN10MB (Ethernet), snapshot length 524288 bytes

这条命令没有输出,说明 TUN 重建的是一个到远程代理节点的连接,而不是到8.8.8.8:443 这个原始目标的连接。而原始目标会被加密放在这条新的连接中,并会被代理节点恢复,这一点就不展开了。

现在我们证明这条到远程代理节点的连接是通过物理网卡 en0 发送的:我们开启 TUN 模式,同时抓取 utun6en0,并且对 8.8.8.8:443 发起连接,我们观察到

1
2
3
4
5
6
7
utun6:
2026-05-05 23:18:50.294092
198.18.0.1.57732 > 8.8.8.8.443: Flags [S]

en0:
2026-05-05 23:18:50.296934
inet 192.168.2.202.57733 > 175.41.22.19.443: Flags [S]

在 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


  1. 但经常会用加密或混淆技术以躲过攻击和检查↩︎