代理环境下的 DNS 行为解析:分流与泄漏
本文仅作技术探讨,本站不提供代理服务,亦不提供搭建代理的完整教程。
词汇表
为了便于理解,这里统一一下本文语境中用到的词汇的具体含义。
- 客户端 (Client):需要发送网络请求的软件,比如浏览器。
- 代理 (Proxy Client):运行在本地或本地网络中的一个代理软件,比如 Clash。
- 代理节点 (Proxy Node):转发用户流量到目标网站的服务器。
- 本地 DNS (Local DNS):代理软件请求的 DNS。
- 远程 DNS (Remote DNS):代理节点请求的 DNS。
- 系统 DNS (System DNS): 在没有代理软件的情况下其他程序默认请求的 DNS。
常规 DNS
DNS 可以说是现代互联网的基石之一,因为几乎所有的请求都是通过域名发起,但互联网通信必须通过 IP 完成,负责把域名解析成 IP 的服务就是 DNS。2025 年十月,AWS DynamoDB 在 us-east-1 区域发生大规模故障,继而导致多达百余个 AWS 产品级联故障,包括 Steam, Slack 等在内的著名服务均受到严重影响。此次事故的根源就是 DNS 错误,其重要性可见一斑。
作为底层服务,从客户端的角度说,DNS 请求非常简单,简单到可以没有握手、没有连接,一个数据包丢过去等着回复就行了。在没有代理的普通环境下,简化后的一次网页访问请求如下:
sequenceDiagram Client->>DNS: Query "qq.com" DNS->>Client: 113.108.81.189 Client->>QQ: Send request to 113.108.81.189
- 客户端想要访问一个网站,但不知道 IP 地址(假设本地没有缓存)。
- 客户端(或操作系统)向本地 DNS 服务器发送一个查询请求。
- 得到 DNS 响应后,客户端才能真正开始访问网站。
代理下的 DNS
代理模式
在介绍代理环境下的 DNS 行为之前,我们要先清楚哪些流量可以被代理捕获,这取决于代理模式。常见的有两种:系统代理与 TUN。
这里暂时只考虑基于 UDP 的明文 DNS。DoH 与 DoT 为加密流量,从代理角度看,它们通常会被视为与访问网站类似的应用流量而不是 DNS。
系统代理
sequenceDiagram
Client->>+Clash: "https://google.com"
Clash->>...:
...->>Clash: Google
Clash->>Client:
系统代理更像是一种契约。代理软件会修改系统设置来宣称“请大家都把流量交给我,不要自己发出去了”,可惜并不是所有软件都理会这个标记,就像你不会因为门口有一个促销的招牌就一定会去消费一样。一般来说浏览器是最听话的,而 Steam、游戏等软件会无视。如上图所示,重点在于系统代理基本都是不支持 udp 流量的 HTTP 代理,客户端就自然不会头铁地自己查询 DNS 了。**此时代理软件会收到基于域名的 HTTP 请求,极少收到 DNS 流量。**之所以讲极少而不是没有,是因为部分系统或软件也支持 socks5 代理。尽管大部分情况下客户端还是会直接发送域名,但技术上的确可以通过支持 UDP 的 socks5 来自行查询 DNS(上图未体现)。
TUN
sequenceDiagram
Client->>+Clash: Query "google.com"
Clash->>-Client: "209.85.2.2"
Client->>+Clash: HTTP "209.85.2.2"
Clash->>+...:
...->>-Clash: Google
Clash->>-Client:
相比之下 TUN 可以算是铁腕代理。它的原理是创建一个虚拟网卡来接管系统的所有出口流量,TCP/UDP 甚至 ICMP 通吃,因此对包括游戏在内的几乎所有软件生效。客户端并不知道 TUN 的存在,因此会照常亲自查询 DNS,并且被代理软件捕获。此时代理会收到大量 DNS 流量,通常不会遇到基于域名的 HTTP 请求。
由于 TLS 协议的 SNI 特性,以及 HTTP 协议的要求,请求中大概率会携带域名信息,但从 TCP 角度看,目的主机是 IP 而非域名。
分流与 DNS 模式
现在我们进入正题,代理环境下的 DNS 本身就很棘手。你可能会说,这有什么难的,把 DNS 的请求也转发给代理不就行了?问题在于,“代理”通常用于解决本地与目标网站之间的连通性问题,在“DNS 查询”语境下,目标网站是 DNS 服务器,它多数情况下是一个境内的服务,本来就可以直接连接。真正的问题是,境内的 DNS 服务很可能已经被污染,对于部分境外域名会返回虚假的 IP。代理不会改变最终要访问的目标,因此”把 DNS 查询转发给代理节点“不会改变它请求的是一个境内服务器的事实,也不会改变得到虚假 IP 的结局。所以这里的关键不是“要不要转发 DNS 流量”,而是必须劫持客户端的 DNS 请求,视情况强制使用更可靠的 DNS 服务器。
这么说可能有点绕,举个例子,假设一台设备系统 DNS 是 223.6.6.6(阿里 DNS),而它想访问 google.com。那么第一步是向 223.6.6.6 发送 DNS 请求来解析 “google.com” 的 IP 地址。 对于这个请求来说,要访问的目标服务器是阿里而不是 Google,即使被转发到代理节点,它也是去请求阿里。因为阿里的 DNS 已经被污染了,最后还是会得到错误的 IP。
其次,大多时候我们会配置基于域名与 IP 的分流规则。要想这些规则能够生效,代理软件必须同时知道请求的目标域名与 IP。但在 TUN 模式下,客户端只会发送基于 IP 的 HTTP 请求,代理软件也只能看到 IP。
有一个办法可以一次性解决上面两个问题:代理软件把自己变成 DNS 服务器。一方面拥有了决定上游 DNS 的权利从而避免得到被污染的结果,同时可以把域名与 IP 关联起来用于后续反向查询。
redir-host
由于存在包括客户端需要等待 DNS 而导致的首包延迟在内的众多问题,redir-host 几乎被事实上弃用。
redir-host 一开始的想法很单纯:做 DNS 该做的事,顺便记录一下结果。这样对于基于域名的请求能解析出 IP,对于基于 IP 的请求能反向查找之前处理 DNS 请求时记下的域名。结果就是被狠狠地套路了一把。防火墙经常使用名为 DNS 污染的招数来屏蔽它不喜欢的网站。具体来说它会在真正的权威服务器之前用一个虚假的 IP 抢答 DNS 请求,同时通过其他方式对缓存服务器投毒,除了以加密方式查询境外 DNS 之外客户端无法获得正确的结果。
但每次都查询境外服务器也太慢了,于是 redir-host 打了个补丁:fallback。核心思想是:优先使用本地 DNS 的结果,但如果返回的 IP 比较可疑,那么就等待通过代理查询境外 DNS 得到更可信的结果。根据代理软件的不同,“可疑”的标准一定程度上可以配置,默认规则通常是“一切境外 IP”。这是因为 DNS 污染通常返回归属地为境外的虚假 IP,以避免把流量引导至境内合法节点而产生意料之外的副作用。
值得注意的是,对于客户端基于 IP 的请求,Clash 会先查询自己的 域名-IP 映射表尝试还原出域名,再把基于域名的请求转发给代理服务器。代理节点则在远端重新查询 DNS 并利用得到的新鲜结果请求目标网站。即代理软件解析域名只用来分流,具体访问目标网站的哪个 IP 是代理节点上的 DNS 决定的。这么做主要因为很多服务都设有全球 CDN 并通过 DNS 分配最近的服务器。让最终去访问网站的节点自己查询 DNS 通常可以得到更快的响应。
下图展示了在 TUN 模式下一个请求的完整流程。对于系统代理模式,则是省略前半部分的 DNS 的查询,客户端直接向代理软件请求 HTTP “google.com”。
sequenceDiagram
Client->>+Clash: Query "google.com"
Clash->>+Local DNS:
Local DNS->>-Clash: 5.5.5.5
opt Local DNS returned a suspicious IP
Clash->>+Fallback DNS: Query "google.com"
Fallback DNS->>-Clash: 209.85.2.2
end
Clash->>-Client:
Client->>+Clash: HTTP "209.85.2.2"
Clash->>+Proxy: HTTP "google.com"
Proxy->>+Remote DNS: Query "google.com"
Remote DNS->>-Proxy: 209.85.3.3
Proxy->>+Google: HTTP "209.85.3.3"
Google->>-Proxy: HTTP Response
Proxy->>-Clash:
Clash->>-Client:
为了能从 IP 反查出域名来匹配规则,redir-host 兜了一大圈,结果还是有两个比较明显的缺陷:
- 即使我们假设代理总是可以得到未污染的 DNS 结果,但因为 CDN 的存在,很有可能多个域名指向同一 IP,这就导致代理客户端还是不能根据 IP 反查域名。尤其在 TUN 模式下这大大降低了分流的精度。
- 如果目标网站能够匹配上基于域名的规则(即无需匹配 IP 规则),那么绕一大圈得到的未污染的 DNS 结果其实没有任何意义。客户端也得傻乎乎地等待 DNS 响应才能构建并发送 TCP (HTTP) 数据包。这一过程对于境外域名来说徒增了上百毫秒的延迟。
fakeip
fakeip 是目前最流行的 DNS 模式,也叫 fakedns 或虚拟 DNS。
fakeip 解决了上述 redir-host 的两大缺点。我们已经知道,代理软件解析出的 IP 不用于实际访问,因为它会在转发流量到代理节点之前尝试把请求中的 IP 还原成域名。既然这样,何不闭着眼响应一个假 IP?于是 fakeip 横空出世。
每当收到 DNS 请求,代理会从一个私有地址段中取一个来响应,并记录下与域名的关联。因为 IP 是代理生成的,因此能够保证每一个域名都对应唯一的 IP。这样在处理后续基于 IP 的请求时就一定能反查出域名并发给代理节点。
此时一个典型的请求流程如下:
sequenceDiagram
Client->>+Clash: Query "google.com"
Clash->>-Client: 198.18.0.16
Client->>+Clash: HTTP "198.18.0.16"
Clash->>+Proxy: HTTP "google.com"
Proxy->>+Remote DNS: Query "google.com"
Remote DNS->>-Proxy: 209.85.3.3
Proxy->>+Google: HTTP "209.85.3.3"
Google->>-Proxy: HTTP Response
Proxy->>-Clash:
Clash->>-Client:
不过 fakeip 也有它自己的小毛病:
- 有些软件会验证解析出的 IP 的有效性,对明显不正常的结果会直接视为网络故障。
- 如果客户端强行缓存了解析出的 IP,且代理软件未运行,那么显然所有请求都会失败,直到刷新 DNS。
- 如果需要匹配基于 IP 的规则,则还是无法省略本地 DNS 查询。不过可以通过先返回后查询的方式优化一丢丢延迟。
对于第一个缺点,大部分的代理都支持设置过滤名单。名单中的域名会回退到 redir-host 的行为,即查询 DNS 后返回给客户端真实 IP。在名单配置比较完善的前提下,fakeip 没有明显缺点。
realip
尽管 fakeip 解决了延迟问题,并通过白名单方式来避免影响某些软件的运行。但实际上长期维护一个域名列表并不容易,甚至出于反作弊等需求,一些游戏还会进行针对性检测。此外 DNS 泄漏难以在 fakeip 下彻底解决(见下文)。于是乎人们提出了 realip。
顾名思义,realip 即通过 DNS 查询来给客户端响应真实的 IP,听起来是不是和 redir-host 差不多?核心区别有两点:
- DNS 本身也支持(基于域名的)分流,不会向境内 DNS 查询境外域名,不仅防止了泄漏,也完全避免了获得被污染的 IP 的可能性。
- 代理软件支持流量嗅探 (sniff)。
借用马斯克推崇的第一性原理,realip 不打算解决之前方案中的缺点,而是从最初的问题出发构建新的方案。那么先来回顾一下原始问题,也就是 redir-host 诞生的背景:
- 客户端请求系统默认的 DNS 可能得到被污染的结果。
- 代理软件无法通过 IP 反向查询出域名,从而无法匹配基于域名的分流规则。
realip 对这两个问题给出的答案是:基于域名的 DNS 分流,以及域名嗅探。realip 本身不是一个功能,而是基于这两个功能的一套特定配置。因此所有支持这两个特性的代理软件都可以实现 realip。
基于域名的 DNS 分流
在传统实现中,代理软件侧的域名解析总会向境内 DNS 发送请求。realip 则根据要查询的域名来选择 DNS 服务器。尽管这不能显著降低延迟但可以避免 DNS 泄漏。注意这里要区别于“对 DNS 流量的分流”。后者决定 “通过哪个线路来请求 DNS 服务器”,大部分代理都支持;前者决定的是“向哪个 DNS 服务器请求”,并经常与后者配合使用,因为境外的 DNS 服务器基本上都被劫持或阻断了。
域名嗅探 (sniff)
前面介绍过,TUN 模式下代理收到的流量往往是基于 IP 的,多数情况下是 TCP/UDP 数据包,且负载 (payload) 是 TLS 或 HTTP 流量。那么就非常有可能从 SNI 或 Host HTTP Header 中提取到域名。
SNI (Server Name Indication)是一个 TLS 扩展,用于解决单个 IP 主机实际托管了多个域名的情况下,不知道该用哪一个证书来和客户端握手的问题。方法也非常粗暴,就是要求客户端在握手阶段明文发送想要访问的域名。
之前的代理客户端不会拆开 L3 数据包去看它的内容。域名嗅探则是尝试解析负载,若是匹配到支持的协议,则提取出域名用于分流规则匹配。这样就无需再依赖处理 DNS 请求时建立的 IP-域名 映射表来查询了。
realip 下的典型请求流程如下(省略了代理节点处理 HTTP 请求的过程):
sequenceDiagram
Client->>+SingBox: Query "xxx.com"
alt Domestic domain
SingBox->>+Local DNS: Query "xxx.com"
Local DNS->>-SingBox: 209.85.3.3
else Overseas domain
SingBox->>+Proxy: Query "xxx.com"
Proxy->>+Remote DNS: Query "xxx.com"
Remote DNS->>-Proxy: 209.85.3.3
Proxy->>-SingBox: 209.85.3.3
end
SingBox->>-Client: 209.85.3.3
Client->>+SingBox: HTTP "209.85.3.3"
Note over SingBox: infer domain via sniff to match rules
SingBox->>Proxy: HTTP "xxx.com"
不难看出,realip 有与 redir-host 一样因查询境外 DNS 而导致首次访问延迟的问题。只要代理程序想要返回真实的 IP 这个问题就不可能解决,算是与软件兼容性的一个权衡吧。
DNS 泄漏
谈到「泄漏」,至少要包含两个要素:负载(泄漏的东西)与对象(泄漏给了谁)。在 DNS 泄漏语境中,负载指的是 DNS 查询请求的负载,简单说也就是客户端要访问的域名。而对象就复杂一点,在最严格的定义下,指除了目标 DNS 服务器之外的任何人。因为目前 DoH 或 DoT 加密 DNS 尚未完全普及,多数 DNS 请求还是通过明文 UDP 传送,因此 DNS 泄漏实际上是司空见惯的。那为什么这里还需要单独一节来讲述呢?
可能的后果
DNS 最多只能泄漏客户端要访问的域名,无论是具体的 URL 还是内容都依然是加密的,常规情况下没有太大问题,即使考虑到隐私问题也不算严重,因为早已普及的 SNI 一样会把目标域名在 TLS (HTTPS) 握手阶段泄漏出去。但是在代理环境下,尤其是跨境代理下就有更大的潜在影响,主要包括:
- 向境内运营商或网络上的其他旁路设备泄漏客户端尝试访问的网站。
- 增加代理节点被识别与封禁的风险。
- 使被访问的网站可以识别出用户正在使用代理的事实。
我们知道代理之所以能解决被屏蔽的站点的连接问题,主要靠的是代理节点本身不在防火墙的黑名单里,加之专用的协议可以把流量伪装成访问未被限制的站点的请求,最终在防火墙下暗渡陈仓。而 DNS 泄漏在一定程度上暴露了统计学特征。设想一个客户端,刚刚解析完某个敏感网站的域名,马上就向某 IP(代理节点)开始传送数据。久而久之,随着样本数据的累积,不难推断出此 IP 实际上是用于绕过网络屏蔽的代理服务器。按照现在的机器学习技术,全自动识别这种流量模式应该很成熟甚至早就部署到生产环境了。因此 DNS 泄漏的后果之一就是增加代理节点被识别并封禁的风险。
另外,从技术上来讲各大网站可以利用 DNS 来检测用户是否使用了代理。要解释这一点我们先来回顾一下简化的 DNS 工作原理,以查询 “netflix.com” 为例:
- 客户端向递归服务器(比如阿里公共 DNS
223.5.5.5)查询域名 “netflix.com”。 - 递归服务器向根服务器查询 “netflix.com”,但它只知道负责 “.com” 的顶级域 (TLD) 服务器是 1.1.1.1。
- 递归服务器接着向 1.1.1.1 查询,但它也只知道负责 “netflix.com” 的权威服务器是 2.2.2.2(也就是域名的 NS 记录),不知道这个域名实际对应的 IP。
- 递归服务器继续向 2.2.2.2 查询,终于得到了结果。
这一路我们查询的所有服务器中,只有最终的权威服务器是域名所有者可以自行指定的。尽管大部分情况下网站会使用域名注册商或知名云计算公司托管的权威服务器来保证稳定性并降低运维成本,但出于特殊目的他们理论上可以将 NS 记录指向一个自行部署的实例,那么也就可以轻松得到请求者的 IP。注意这里的请求者不是我们的客户端,而是递归服务器。但这已经足够判断用户的大致地区,因为不管是用户习惯还是大部分递归服务器都使用 Anycast 的事实,都决定了权威服务器得到的请求者 IP 不会离实际用户很远。最后网站只需比较查询 DNS 的请求 IP 与实际发送 HTTP 请求的 IP 是否归属于相近的地区,就能识别出潜在的赛博移民。所以 DNS 泄漏可能导致网站识别出用户正在使用代理的事实,进而采取屏蔽版权内容、遣返回高价区等措施。注意,基于 DNS 的地理位置检测只是网站识别代理的方案之一,还有 EDNS 以及 WebRTC 等其他手段。
如何检测
为了便于描述,在接下来章节中,「DNS 泄漏」 的定义为:向境内的 DNS 服务器查询了境外的域名,无论是以加密还是明文的方式请求。
- 启动代理并使用惯用设置(但不要使用全局模式)。
- 浏览器打开 https://browserleaks.com/dns 并观察 “Your DNS Servers” 字段捕获到的 DNS 服务器 IP 与归属地。
- 注意,要想测试结果可靠,上面的域名不可以被基于域名的分流规则匹配为使用代理,否则属于自欺欺人的鸵鸟操作。
如果测试网站捕获到了位于境内的 IP 则表示发生了 DNS 泄漏。
注意,根据网络配置的不同,网站测试存在假阴性的可能性(即泄漏了但没检测出来)。 更准确的检测方法是在 TUN 模式下通过 Wireshark 等软件抓包,所有 DNS 请求必须是下列情况之一,否则表示发生了泄漏:
- 境内域名查询。
- 以加密形式(或通过代理节点)发送给境外 DNS 服务器的查询。
常见原因与解决方案
不完善的分流规则
上文提到,除了命中直连规则外,对于一个基于域名的 HTTP 请求,代理软件在本地发起 DNS 查询的唯一动机是用于匹配基于 IP 的分流规则。例如下面的规则:
IP-CIDR,64.233.177.188/32,PROXY
DOMAIN,google.com,PROXY虽然域名 “google.com” 在规则列表中,但因为顺序问题,代理软件会优先检查第一个基于 IP 的规则。如果请求目标是个域名,就会尝试解析出 IP,尽管某些代理支持对 DNS 分流,但既然普通规则都配置错了很可能 DNS 规则也不完善,最终把请求发给了境内的 DNS 服务器。
还有一个更隐蔽的例子:
...
DOMAIN,google.com,PROXY
GEOIP,CN,DIRECT如果客户端尝试访问一个不在规则列表中的小众境外网站,为了匹配末尾的 GEOIP 规则同样会进行 DNS 查询。
上述例子的缓解措施有:
- 优先配置完善的基于域名的规则,并始终放在 IP 规则之前。
- 推荐给 IP 规则(包括代理软件内置的
GEOIP)加上no-resolve关键字或其他等价的配置。这样对于目标为域名的请求,会直接跳过 IP 规则的匹配,而不是尝试解析它。为了保证分流的精度,这么做的前提是已经配置了完善的域名规则。
不完善的 DNS 配置
上面的措施可以避免为了匹配规则代理软件自发进行的 DNS 查询,但无法阻止为了响应客户端的 DNS 请求而发起的查询(主要是 TUN 模式下)。解决思路有两个:
- 干脆不要查询,直接响应假 IP:使用 fakeip 模式。
- 可以查询,但对于境外域名要严格使用境外 DNS(最好是 DoH 加密请求)。
- 需要代理支持对 DNS 的分流。
- 需要配置完善的域名规则。
- 为了避免小众网站不在规则中,建议只匹配境内域名,并默认为其他所有域名使用境外 DNS。
可以发现,方案 2 其实就是上文介绍的 realip DNS 模式。
软件与系统行为
理论上在 fakeip 模式下,如果不需要匹配 IP 规则,是不会进行本地 DNS 查询的,但部分代理可能对 UDP 流量强制解析域名。而最新的 HTTP3 协议是基于 QUIC 的,也就是基于 UDP。可以预见,未来会有越来越多的 UDP 流量。除了换个代理客户端外,可以临时禁用浏览器的 QUIC 支持。以 Chrome 为例,打开 chrome://flags 页面,搜索 “Experimental QUIC protocol” 并改为 “Disabled”。
除此之外,在 TUN 模式下,理论上包括 DNS 查询在内的所有流量都只会发送给代理的虚拟网卡,但是实际上 Windows 系统有一个叫做“智能多宿主名称解析”的优化,会把 DNS 请求发送给所有网卡。可以在组策略编辑器中找到“计算机配置 - 管理模板 - 网络 - DNS 客户端 - 禁用智能多宿主名称解析”,并改成“启用”来关闭这个功能。
总结
本文讨论了在代理环境下复杂的 DNS 行为,它同时取决于代理模式、DNS 模式以及分流规则。没有一套万金油方案,大家得根据自己的需求来配置代理。主流方案的对比总结如下:
| 行为 | 优点 | 缺点 | |
|---|---|---|---|
| redir-host | 从本地查询境内外 DNS | 能够得到真实 IP,兼容性好 | 延迟高;存在 DNS 泄漏;可能无法反查域名而导致分流不准确。 |
| fakeip | 总是返回虚假 IP | 延迟低 | 兼容性略差;存在小概率 DNS 泄漏 |
| realip | 基于规则的 DNS 查询 + 域名嗅探 | 兼容性好;无 DNS 泄漏 | 延迟高 |
不管选用哪个方案,强烈推荐总是优先使用域名分流。
如果不在意把小众境外网站的 DNS 请求泄漏给境内服务,那么可以在后面追加基于 IP 的兜底规则;否则需使用白名单模式,即匹配到规则的网站走直连,对其他所有网站使用代理,不要包含任何基于 IP 的规则,或者加上 no-resolve 之类的等价配置。
越过长城,走向世界。
—— 1987年9月14日由中国发出的第一封电子邮件