跳转至

解决DNS泄露最佳实践——基于 ClashMeta

Abstract

自使用 sing-box 内核进行代理以来,已经过去了进两年,但由于 sing-box 内核并不支持负载均衡、自动回退等技术,导致我不得不另行其道,使用 ClashMeta (a.k.a. mihomo) 作为前者的替代品。

不得不说,ClashMeta 的功能的确比 sing-box 丰富太多,而作为一把双刃剑,ClashMeta 的配置难度也直线上升。用户不仅需要配置出站代理和路由规则(同 sing-box),还必须手写全局配置,而这一操作在 sing-box 系中通常由客户端软件在 GUI 中进行配置。

其中一类全局配置就是 DNS 配置。对于习惯于在 GUI 中进行配置的用户来说,手动编写 DNS 规则无疑是一种跳出舒适区的挑战。然而,正因如此,我们也获得了更高的灵活性:既然必须手动管理 DNS,为何不充分利用这一机会,将其变成一个强大的工具?

一、DNS 泄露的理解

DNS 泄露是在手动配置 DNS 过程中始终绕不开的一个话题。

不同人对 DNS 泄露的定义各不相同。有些人认为,只要 DNS 请求使用了任何大陆的 DNS 提供者,就属于泄露;也有人认为,只要 DNS 请求被运营商污染(例如劫持到广告等返回了意料之外 IP 的情况),就算泄露;还有一些更为激进的观点,认为只要不是由落地代理进行的 DNS 解析,都属于泄露。总之,关于 DNS 泄露的理解五花八门。

我个人认为的 DNS 泄露的定义是:

需要进行代理访问的域名不应该被任何直连 DNS 解析。

这里的直连 DNS 指的是能在大陆保证直连可达的 DNS,这种 DNS 通常为大陆 DNS(可能被污染)或未加密的非大陆 DNS(可能被劫持),故一般只能用于解析 baidu.com 等大陆直连域名。但是如果你能找到一个可直连的加密的非大陆DNS,那么自然无需担心 DNS 泄露问题。

这个理解的核心思想就是:不能让直连 DNS 得知你访问的受代理域名。以 youtube.com 为例,这是一个典型的需要代理访问的域名。如果你向直连 DNS 查询 youtube.com,即使没有用它返回的 IP 建立连接,直连 DNS 依然能获知你的访问意图,这就构成了 DNS 泄露。

为彻底解决 DNS 泄露问题,必须在 ClashMeta 的各项配置中加以优化,确保所有需要通过代理访问的域名都不会被直连 DNS 解析。

二、最佳实践

2.1 在路由规则中避免 DNS 泄露

我们都知道,在应用层发起网络连接时,我们通常可以直接使用域名作为远程地址,而无需提前获知具体的远程 IP。如果代理软件能够识别出应用试图访问的域名,就可以依据域名路由规则判断该域名是否需要通过代理,并据此选择合适的 DNS 解析方式。

但是,如果是 IP 路由规则呢?

默认情况下,ClashMeta 中的 IP 规则并非只在 IP 传入代理时进行匹配。如果传入的是一个域名,则会先进行 DNS 解析,再对解析到的结果与 IP 规则进行比对。如果想让这个路由规则仅在传入 IP 时生效,而在传入域名时什么也不做(跳过 DNS 解析),则需要对该 IP 规则附加 no-resolve 参数,见 文档

路由规则是自上而下依次匹配的,一旦某个域名被域名规则捕获,就不会继续匹配后续规则。利用这一机制,可以减少部分需要代理访问的域名被直连 DNS 解析的情况,这对于使用白名单(即使用 MATCH,DIRECT 兜底)的用户来说略有帮助。但这种方式只能降低 DNS 泄露的概率,无法彻底杜绝,因为未被域名规则覆盖的域名最终仍会被直连 DNS 解析。

IP 规则是导致 DNS 泄露的主要原因。其根本问题在于,匹配路由规则时一旦遇到 IP 规则(显然它此时未被前面的域名规则捕获),无论目标域名是需要直连还是代理访问,此刻都会由 DNS 配置中的 nameserver 字段指定的 DNS 服务器进行解析。这样一来,原本应通过代理访问的域名也可能被直连 DNS 解析,从而引发 DNS 泄露。

由此,我们得到了第 1 条最佳实践:

最佳实践 1

如果没有必要,则不要定义 IP 规则,尽量使用域名规则作为平替。如果非定义不可,则对该 IP 规则附加 no-resolve 参数。

以绕过局域网和内网为例。我们都知道,除了 geosite:private 以外,正常情况下不可能有其他域名能解析到 geoip:lan 中,因此我们可以先设置 GEOSITE,private,DIRECT,然后设置 GEOIP,lan,DIRECT,no-resolve。这样即能保证局域网和内网的请求无论以域名发起还是以 IP 发起,均能被绕过。

题外话

如果域名匹配到代理,则会将域名直接发送给代理,并在流量落地后由落地代理所在的服务器进行解析,而不会使用在客户端定义的任何 DNS 进行解析。按照这种解析方式,自然也就不存在所谓的 “远程 DNS” 的说法。因此,如果能按照本文所述彻底解决 DNS 泄露,完全可以在 nameserver 字段中只指定大陆的 DoH 服务器,以加快解析。

如果你真的这么做了,建议只使用 IP 格式的 DoH 服务器,以避免需要先使用 default-nameserver 对 DoH 域名进行一次额外的解析:

为什么不直接使用非大陆的 DoH 或者 DoT 呢?原因很简单:连不上

2.2 在系统代理场景下避免 DNS 泄露

在启用系统代理时,只有遵守系统代理规则的应用才能使用 ClashMeta 提供的代理,这些应用会在应用层向 ClashMeta 为本地提供的 mixed://127.0.0.1:7890 发起代理请求。而无论是 http 代理还是 socks5 代理,都可以只向代理服务器发送域名,无需由应用本身发起 DNS 请求。

因此,在系统代理场景下,只要用户正确配置了路由规则,则不可能发生 DNS 泄露。

2.3 在 TUN 场景下避免 DNS 泄露

TUN 场景主要带来了两个问题。首先,由于 TUN 对应用来说是透明的。因此应用会像正常情况下那样,由自身发起 DNS 解析,并按照 DNS 应答结果再对 IP 发起连接。其次,TUN 设备工作在网络层,在网络层截获的数据包并不包含域名,故域名规则相当于全部失效。

在 Android / iOS 系统上,启用 VPN 等同于启用了 TUN。

当然,系统代理与 TUN 并不冲突,你完全可以在启用 TUN 的同时也启用系统代理。对于浏览器等严格遵循系统代理规则的软件来说,它们会优先选择系统代理,从而规避上述两个问题。总的来说,启用系统代理几乎没有任何负面影响。

最佳实践 2

建议总是启用系统代理。

并不是所有的应用都会像浏览器那样严格遵守系统代理,这也是启用 TUN 的直接理由。在启用 TUN 的情况下,想要完全避免 DNS 泄露必须劫持本地 DNS 请求甚至使用 Fake IP。

DNS 劫持

由于 TUN 对应用是透明的,应用会自行使用系统 DNS 进行解析,这显然属于 DNS 泄露。为了解决这个问题,可以让 TUN 在网络层拦截所有发往 53 端口的数据包,并将其导入 ClashMeta 的 DNS 模块。要这么做,只需按照 文档 在 TUN 设置中指定:

tun:
  dns-hijack:
    - any:53
    - tcp://any:53

注意

  • MacOS / Windows 无法自动劫持发往局域网的 DNS 请求
  • Android 如开启 私人 DNS 则无法自动劫持 DNS 请求

Fake IP

Fake IP 的原理其实也依赖于 DNS 劫持。

具体来说,当应用试图自行向系统 DNS 发起查询时,TUN 会在网络层拦截所有发往 53 端口的数据包,并将其导入 ClashMeta 的 DNS 模块。之后 DNS 模块不会立即解析域名,而是先记录本次查询的域名,并直接返回一个 Fake IP 地址(通常属于 TUN 网段,如 198.18.0.1/16)。

应用收到这个 Fake IP 后,会向该 IP 发起连接。代理客户端在截获这个连接的同时,可以通过之前记录的 DNS 查询反查出对应的域名。这样,代理客户端就能利用域名规则进行流量分流。

如果应用遵循系统代理,则会将域名通过 http 或者 socks5 代理发往 ClashMeta 内核,而不再会向系统 DNS 发起查询,之后的“解析”流程和上述相同,不再赘述。

启用 Fake IP 会影响所有需要获取真实 IP 的应用,例如 ping 命令或 traceroute 工具。此外,如果某些域名本应解析为局域网地址(如 raspberrypimiwifi.com 或某些校园网的局域网地址),但却被落地代理解析,结果往往是错误的。为避免此类问题,需要获取真实 IP 的域名加入 Fake IP 白名单。常见需要加入白名单的域名如下:

dns:
  fake-ip-filter:
    - "geosite:private"
    - "geosite:category-education-cn"
    - "+.push.apple.com"
    - "+.market.xiaomi.com"

最佳实践 3

TUN 场景下必须启用 DNS 劫持,若已经启用了 Fake IP,则无需额外再启用 DNS 劫持。

2.4 DNS 代理

伤敌一千自损八百的做法,就是将 DNS 设置成非大陆的 DoH 或者 DoT,并让 DNS 流量也走代理,做法很简单,以下是两种等价的做法。

第一种做法是直接启用 respect-rules 参数:

dns:
  respect-rules: true

此时 DNS 查询会视为普通请求,其流量会遵守路由规则,即按照首个成功匹配的规则进行出站。通常情况下,如果你配置的是非大陆的 DoH 或 DoT,并且路由规则设置得当,DNS 流量会通过代理服务器转发。

第二种做法是在 nameserver 字段中明确指定用哪个代理,以一个叫做 myproxy 的代理为例:

dns:
  nameserver:
    - 'https://8.8.8.8/dns-query#myproxy'

proxies:
  - name: myproxy
    type: ss

此时 DNS 流量总会使用 myproxy 进行转发。

DNS 代理的做法虽然简单,但由于所有 DNS 流量都通过代理转发,访问大陆域名时会显著影响解析性能。此外,这种配置如同悬在用户头上的达摩克利斯之剑:一旦代理服务器失效,所有 DNS 查询将无法完成,导致网络连接全面受阻。

三、结论

DNS 泄露是代理配置中常被忽视但极为关键的问题。通过合理配置 ClashMeta 的路由规则、DNS 设置以及系统代理和 TUN 相关参数,可以有效避免 DNS 泄露。

最佳实践包括优先使用域名规则、慎用 IP 规则并加上 no-resolve,始终启用系统代理,以及在 TUN 场景下开启 DNS 劫持或 Fake IP。对于特殊需求,可以让 DNS 流量走代理,但需权衡性能与可用性。

评论