打开 Clash 之后,你的电脑里发生了什么
关键词:Clash、代理、系统代理、TUN、fake-ip、路由表、socket、DNS、网络Clash 一开,GitHub 秒开,npm 飞快——但你的电脑里到底发生了什么?本文从网卡、数据包、路由表、socket、DNS 五个地基概念出发,配合精密机械风格等距插图,讲透系统代理、TUN 模式、fake-ip、分流与隧道的完整链路。

打开 Clash 之后,你的电脑里发生了什么
学编程的同学,迟早要过一个坎:访问 GitHub。
要么是 clone 一个仓库半天不动,要么是 npm install 卡死在某个包上。这时候总有学长告诉你:装个 Clash,订阅个代理,就好了。
你照着做了。小猫咪图标一点,系统代理一开,GitHub 秒开,npm 飞快。问题解决。
大多数人到这里就停下了。但如果你往后想认真做技术,我劝你多问一句:刚才那一下,我的电脑里到底发生了什么?
这个问题值得问,因为它是那种“天天在用、从没想过为什么”的东西。而答案牵出来的,是计算机网络核心的一串概念:网卡、数据包、路由表、socket、DNS。这些概念你在任何一本网络教材里都会遇到,但教材不会告诉你它们和你电脑上那只小猫咪有什么关系。
这篇就讲这件事。
先修概念:四个地基
后面的一切建立在四个概念上。一个一个来。
网卡:进出网络的唯一大门
你电脑上所有进出的网络数据,都必须经过一个叫网卡的硬件。插网线的是有线网卡,连 Wi-Fi 的是无线网卡,本质一样:它是你的机器和外部网络之间的一扇门。
重点是:在常规网络编程模型下,应用程序不直接碰这扇门。程序把数据交给操作系统内核,内核交给网卡发出去;外面的数据进来,方向反过来。“应用程序 → 内核 → 网卡”这个顺序是铁律。
(有例外——DPDK、RDMA 这类 kernel bypass 技术让应用程序绕过内核直接操作网卡,但那是另一个世界的故事,这里不涉及。)
还有一个事实先记下:网卡是内核管理的资源。Linux 和 macOS 上跑一下 ifconfig,你能看到内核手里所有网卡的清单,每张网卡有名字、有 IP、有状态。
数据包:网络传输的最小单位
数据在网络上不是一整条流过去的,是被切成一个个包发出去的。每个包有个信封,上面写着:
- 源 IP、目标 IP:从哪台机器来,到哪台机器去
- 源端口、目标端口:交给对端机器的哪个程序
- 协议:TCP 还是 UDP
这五个字段合称五元组,唯一确定一条数据流。对于 TCP,一条连接的五元组在连接生命周期内不变;对于 UDP,五元组标识的是同一组端点之间的一系列包。后面讲分流的时候你会发现,代理软件做判断的原料,全在这五个字段里。
路由表:内核手里的导航
内核每拿到一个要发出去的包,都要回答一个问题:这个包从哪张网卡扔出去?
回答靠路由表,内核里维护的一张规则清单,长这样:
目标地址段 出口 metric
192.168.1.0/24 → 无线网卡 100
10.0.0.0/8 → 有线网卡 200
0.0.0.0/0 → 无线网卡 100 ← 默认路由
拿到包,看目标 IP,从上到下匹配,命中哪条走哪条。如果多条都匹配,选 metric 值最小的。最后那条 0.0.0.0/0 叫默认路由,是兜底的——前面都不匹配,就走它。
macOS 上跑 netstat -nr 可以看到自己机器的这张表。
现在注意一个推论:谁改写了路由表,谁就控制了全机流量的去向。 这句话是整篇文章的钥匙,先放在这里。
socket:程序碰网络的唯一把手
应用程序想联网,必须通过操作系统给的编程接口。这个接口叫 socket。
别被这个词吓住,看代码就明白了。你在 Node 里写:
const res = await fetch("https://github.com")这行代码剥到最底层,做的事情是:程序对内核说“帮我连 github.com 的 443 端口”,内核建好连接,然后返回一个东西给程序。这个东西叫句柄(handle)。
句柄是个什么?说白了就是一个编号。内核手里有一张大表,表里登记着每一条连接的真实状态——对面是谁、发到哪了、收了多少。这张表程序碰不到,内核只发给程序一个编号,好比银行办业务给你的取号小票。之后程序想在这条连接上发数据、收数据,就举着这个编号找内核:“3 号,写数据”“3 号,有数据来了吗”。
你写前端的 axios、Node 的 fetch、Go 的 http.Get,剥到底全是在操作 socket、举着句柄跟内核打交道。这一点马上有用。
DNS:把域名翻译成 IP 的查号台
最后一块。五元组里填的是 IP,可人记的是域名。github.com 的 IP 是多少?要靠 DNS 查询:系统向 DNS 服务器问一句,拿回一个 IP,才填得进数据包的信封。
注意,DNS 查询本身也是一次网络通信,也要走路由表、过网卡。这个细节后面会成为整个故事里最微妙的一环。
传统 DNS 查询是明文 UDP 包,沿途谁都能看见你在查什么域名。DNS over HTTPS(DoH)和 DNS over TLS(DoT)把查询加密在 HTTPS/TLS 隧道里,让沿途设备只能看到你在和 DNS 服务器通信,看不到查了什么。Clash 的 DNS 配置里可以启用 DoH,这也是对抗 DNS 污染的手段之一。
没有代理时,一次请求的正常旅程
先看基线。你在终端跑 curl https://github.com,没开代理时发生这些事:
- curl 做 DNS 解析,拿到 github.com 的 IP
- curl 向内核申请 socket:“连这个 IP 的 443 端口”
- 内核把数据切成包,信封写好五元组
- 内核查路由表,命中默认路由,从无线网卡发出
- 包经过家里路由器、运营商网络、国际出口,奔向 GitHub
- 响应原路返回
整条路上,数据包对沿途每个网络设备都是可见的——至少信封上的目标 IP 是可见的。在国内的网络环境下,“打不开 GitHub”不只是一种拦截机制,而是好几道:DNS 污染(返回假 IP)、SNI 探测(根据 TLS 握手明文中的域名识别)、IP 封锁(直接丢弃发往特定 IP 的包)、TCP RST 注入(伪造重置包打断连接)。这就是“打不开 GitHub”的物理含义——不是 GitHub 挂了,是包在半路上被拦了。
代理要解决的问题
目标很明确:让访问 github.com 的包,不在国内网络上裸露地奔向目标,而是先送到一台境外的中转服务器,由它替你把数据拿回来。
那台中转服务器就是你订阅的代理服务器。Clash 的角色是跑在你电脑上的客户端,干两件事:
- 接管:把本机要去 github.com 的连接拦下来
- 转发:把拦下的数据通过加密隧道送到代理服务器
对 github.com 来说,访问者是那台境外服务器;对国内沿途的设备来说,只能看到你和境外某台服务器之间有一串加密数据,里面装的什么,看不见。
难的是第一步“接管”。你的电脑上那么多程序,凭什么它们的流量都乖乖经过 Clash?
有两种实现。这就是系统代理和 TUN 模式的区别,也是 Clash 面板上一堆开关背后的故事。

先看全链路,建立整体认知,再往下拆细节:

后面每一节拆的就是这张图上的某一层。
系统代理:靠程序自觉
回忆一下 socket 那节:程序联网要创建 socket,但绝大多数程序不手写这层,用的是语言或系统提供的 HTTP 库。而这些库,通常会读一个配置——系统代理设置。
Clash 的“系统代理”开关打开后,做了两件事:
第一,在本机开一个服务端口,比如 127.0.0.1:7890。127.0.0.1 是本机回环地址,意思是“我自己”,这个服务只接受本机程序的连接。
第二,改操作系统的网络设置,写入一行配置,大意是:“本机的 HTTP/HTTPS 请求,请发给 127.0.0.1:7890。”
之后再跑 curl https://github.com,旅程变了:
- curl 的网络库读到系统代理设置
- curl 不查 DNS、不连 GitHub 的 IP,先连
127.0.0.1:7890,对 Clash 说一句:“请帮我连 github.com:443”——这是 HTTP 协议里的 CONNECT 方法,一次委托 - Clash 接受委托,执行转发
- GitHub 的响应经 Clash 还给 curl
第二步有个细节值得停下来看:域名解析的责任,从 curl 转移到了 Clash。 curl 只把“github.com”这串文字交了出去,自己根本不关心它的 IP。这一点对绕过 DNS 污染很关键——被污染的 DNS 查询正是被墙的第一道关卡。
但这个方案有命门,就在第一步:它依赖程序自觉读系统代理设置。浏览器、curl 这类守规矩的程序没问题;可很多程序——某些命令行工具、游戏、自己实现网络层的客户端——根本不读这个配置。它们的流量依然直来直去。
“明明开了代理,某某软件还是连不上”,原因就在这。
TUN 模式:在内核里设卡
既然靠自觉不靠谱,能不能在更底层拦?能。这就是 TUN 模式。它用到了前面埋的两个伏笔:网卡由内核管理,路由表决定流量去向。
第一步,创建一张虚拟网卡。
“虚拟”的意思是:这张网卡没有对应的硬件,纯粹是内核里的一个软件对象。但在内核眼里,它和无线网卡地位平等——有名字、有 IP、能收发数据包。区别只在一点:数据包“发进”这张网卡,实际上是被内核转手交给了创建它的程序,也就是 Clash。
第二步,改写路由表,把默认路由指向这张虚拟网卡。
还记得那把钥匙吗:谁改写路由表,谁就控制全机流量。Clash 在路由表顶端插上规则,效果是:本机所有程序发出的所有 IP 包,内核查完路由表,全部“发进”虚拟网卡,全部落入 Clash 手中。
这一步完全发生在内核层面,应用程序全程无感知。它照常 fetch、照常发包,包却被路由表截走了。所以 TUN 模式能接管一切程序,包括不读系统代理设置的那些——它压根不指望程序配合。
(当然,Clash 会把代理服务器自己的 IP 在路由表里单独豁免。不然发给代理服务器的包也被自己拦下,就死循环了。)
包是怎么从内核到 Clash 的
你可能会问:路由表指向虚拟网卡之后,包具体是怎么到 Clash 手里的?
这取决于操作系统的内核网络栈。Linux 上,Clash 通过 netfilter(iptables/nftables)在包进入路由决策之前用 TPROXY 或 REDIRECT 目标拦截包,交给本地的 Clash 进程;macOS 上用的是 pf(packet filter)的 rdto 规则做类似的事;Windows 上则是 WFP(Windows Filtering Platform)的 callout 驱动。
名称各不相同,角色一样:在内核网络栈的关键节点上插一个钩子,把包“截住”,送到用户态的 Clash 进程。
TUN 的代价
TUN 模式不是免费的。每个包都要从内核态拷贝到用户态,Clash 处理完再拷回去。这意味着:
- 延迟增加:多了一次内核态↔用户态的上下文切换
- CPU 开销:大流量下 Clash 进程的 CPU 占用不可忽视
- MTU 问题:隧道有额外的协议头,有效 MTU 变小,可能需要调低虚拟网卡的 MTU
所以教程里经常说“能开系统代理就不用 TUN”——系统代理在应用层拦截,没有这些开销。
fake-ip:TUN 模式里最精巧的一环
流量到了 Clash 手里,第一件事是回答:这个包要去哪?
系统代理进来的连接好办,CONNECT 请求里写着“github.com:443”,是域名。
TUN 拦下的包麻烦。信封上只有 IP,没有域名——程序在发包前已经自己查过 DNS 了。可 Clash 的分流规则大量基于域名,比如“*.github.com 走代理”。拿着一个裸 IP,怎么匹配域名规则?
Clash 的解法很直接:既然路由表在我手里,那我顺手把 DNS 查询也接管了。
具体做法是 fake-ip:程序问“github.com 的 IP 是多少”,Clash 不回真实 IP,回一个假 IP——从 198.18.0.0/16 这个保留段里分配一个——同时在小本子上记一笔:198.18.0.5 ↔ github.com。
程序拿着假 IP 开心地发包,包被路由表截进 Clash,Clash 一看目标是 198.18.0.5,翻小本子,反推出真实域名 github.com。域名拿回来了,规则匹配照常进行。
用假 IP 做信物换域名,这是我个人在整套方案里最欣赏的设计。

fake-ip 的局限
但 fake-ip 不是万能的。它的问题在于:程序拿到的是假 IP,不是真实 IP。这会破坏依赖真实 IP 的场景:
- CDN 地理调度:CDN 根据客户端 IP 判断地理位置,返回最近的节点。假 IP 让 CDN 找不到你,可能返回一个遥远节点的 IP,反而更慢。
- API 的 IP 白名单:某些服务要求调用方 IP 在固定范围内,fake-ip 不在话下。
- 反作弊系统:部分游戏和风控系统会检测 IP 伪造,fake-ip 可能触发误判。
Clash 的解法是加一条规则:命中这些场景的域名走 fake-ip-filter,用真实 DNS 解析,不分配假 IP。但这需要你手动维护这个列表——又一个“工具替你做了判断,但判断对不对得你自己验证”的例子。
分流与隧道
目标地址拿到手,Clash 接着做两件事。
分流:决定这个连接出不出国。
拿着目标地址,从上到下过一遍规则列表:
DOMAIN-SUFFIX,github.com → 走代理
DOMAIN-SUFFIX,bilibili.com → 直连
GEOIP,CN → 目标 IP 在国内,直连
MATCH → 都没命中,走代理
命中直连的,Clash 替程序直接连目标,数据不出国;命中代理的,进隧道。这就是分流——该出去的出去,不该出去的别绕路。
这一步能解释很多日常现象。为什么规则列表动辄几万条?因为要替你做“出不出国”的判断。为什么开了代理微信发图变慢?大概率是微信的流量没有被正确分流到直连,每个包都要先过一遍规则引擎。分流不是免费的,每个新连接都要付一次匹配的钱。
隧道:加密之后送出去。
命中“走代理”的连接,Clash 按订阅里的协议——Shadowsocks、Trojan、Hysteria2 等——连上境外代理服务器。协议细节各不相同,共同点是:你原始连接里的数据被加密后塞进隧道,代理服务器在境外解开,替你去连真正的目标。
国内沿途的设备能看到的是“这台电脑在和境外某台服务器通信”,加密之外,目标是谁、内容是什么,一概不知。
响应原路返回:代理服务器塞回隧道,Clash 解密,还给本机程序。程序从 socket 里读到数据,一切如常,从头到尾不知道中间发生的这一切。