一、先分清 DNS 出口、网页出口与规则预期

网页显示代理出口 IP,并不代表域名解析也经过代理。一次访问至少涉及两条路径:应用先解析域名,再向目标地址建立连接。系统代理通常只接管愿意遵守代理设置的应用,系统解析器、应用自带 DNS 和浏览器安全 DNS 可能走不同路径。

本文把“DNS 泄漏”限定为:本应经由指定解析路径处理的查询,实际交给了预期之外的解析器,或经由本地网络直接发出。如果规则本来要求本地域名直连并使用本地解析,检测到本地解析器不一定是故障;如果目标是所有普通查询经代理转发,那么相同结果就值得排查。

观察项能说明什么不能直接证明什么
网页公网 IP该网页请求的出口其他应用或 DNS 也走相同出口
检测站列出的 DNS IP参与递归解析的服务器出口设备到解析器之间是否经过代理
返回 198.18.x.x这次查询拿到了常见 fake-ip 地址段内的映射全部系统查询都已被接管
出现本地运营商解析器存在需要核对的本地解析路径所有连接都绕过代理

二、检测站要做三组对照,不只看国家名称

先记录固定条件

可以使用 dnsleaktest.com 的 Standard test、Extended test,以及 browserleaks.com/dns 交叉观察。它们通过触发测试域名解析,记录查询到达权威服务器时的递归解析出口;不是在浏览器里直接读取操作系统 DNS 设置。

  1. 记录当前网络,例如家庭 Wi-Fi;记录客户端模式、所选节点、TUN 开关与浏览器安全 DNS 状态。
  2. 关闭代理,运行一次测试作为基线。记下公网 IP、DNS 服务商及解析器地址,不必公开完整截图。
  3. 开启代理并保持节点不变,再运行一次。系统代理模式与 TUN 模式分别记录,避免同时修改多个条件。
  4. 临时关闭浏览器安全 DNS,重启浏览器后再测一次。若结果变化,优先检查浏览器自己的 DoH 路径。

这些是建议的操作步骤,不是本站提供的实测数据。对照时重点看“开关前后是否变化”和“是否符合配置策略”,不要把某个国家标签当成唯一判据。公共 DNS 使用任播网络,地理数据库也可能滞后,解析器登记位置与实际服务节点位置未必相同。

按结果缩小范围

  • 网页 IP 变化,DNS 仍是家庭宽带运营商:先查系统 DNS 是否进入内核,以及浏览器是否另有解析设置。
  • 只有某个浏览器出现不同 DNS:优先查该浏览器的安全 DNS、扩展和独立代理设置。
  • 出现两家公共 DNS:可能配置了多个上游或回退解析,不能只凭数量判定泄漏。
  • TUN 开启后结果才符合预期:说明系统代理未覆盖原先的解析路径,继续验证应用范围与 IPv6。

三、用 fake-ip 建立域名映射,再核对解析上游

fake-ip 的作用是向应用返回虚拟地址,并由内核保存域名映射。当后续连接抵达内核时,可以据此使用域名规则进行匹配。它并不是一种加密协议,也不会自动接管操作系统 DNS。配置开关打开、系统却继续询问路由器,仍然可能绕过这条路径。

下面是一段合并到现有配置的 DNS 片段,不是完整订阅。保留现有节点、代理组和规则,将已有的顶层 dns: 段整体检查后再修改,不要在同一文件里再追加第二个同名段。

config.yaml · DNS 配置片段
dns:
  enable: true
  listen: 127.0.0.1:1053
  ipv6: false
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  fake-ip-filter:
    - '+.lan'
    - '+.local'
  respect-rules: true
  default-nameserver:
    - 223.5.5.5
  proxy-server-nameserver:
    - 223.5.5.5
  nameserver:
    - https://cloudflare-dns.com/dns-query
  • listen 是内核 DNS 监听地址。示例的 1053 适合先做定向测试,不会自动让系统改用这个端口。
  • default-nameserver 用于解析 DNS 服务器自身的域名,是启动解析链的一环,不是普通查询的常规上游列表。
  • proxy-server-nameserver 用于解析代理节点域名,避免“解析节点要先连代理,连代理又要先解析节点”的循环依赖。
  • nameserver 中的 DoH 地址用于常规上游解析。HTTPS 加密的是到解析服务的传输,并不等于这条传输必定走代理。
  • fake-ip-filter 中的域名不使用 fake-ip 映射;该列表不等于直连规则,也不代表这些域名不再发起 DNS 查询。

四、respect-rules 决定上游连接怎么走,不负责接管系统

respect-rules: true 让相关 DNS 上游连接遵循路由规则。它解决的是“内核向上游发请求时选哪条出口”,不是“系统的请求有没有进入内核”。这两件事分开验证,排障会少绕一圈。

核对最终命中的规则

  1. 确认当前使用规则模式,即 mode: rule。全局模式与直连模式下的行为不能拿来代替规则模式验证。
  2. 检查 DNS 服务域名是否被前置规则或规则集匹配到 DIRECT。如果命中直连,开启 respect-rules 也不会把它变成代理。
  3. 通过客户端连接面板及日志核对上游连接的规则与出口;必要时短时间提高日志级别,排查后恢复,避免持续记录过多域名。
  4. 若需要为 DNS 服务域名单独指定出口,使用现有代理组的准确名称,并放在会提前命中的宽泛规则之前。不要复制一个配置中根本不存在的组名。

原配置若含有 nameserver-policy、direct-nameserver 或 fallback,也要一并检查。只修改 nameserver,可能根本没有改变某些域名实际使用的解析器。首次定位时宜减少变量,确认最小配置正常后,再逐项恢复策略。

fallback 也不是一个简单的“主 DNS 超时才启动”开关。回退结果的选用与 fallback-filter 等条件有关;为了修复泄漏而随手堆上多个解析器,通常只是把观察结果变得更热闹。

五、系统 DNS 接管:TUN 与 53 端口各管什么

多数桌面系统的网络设置只能填写 DNS 服务器地址,不能填写 1053 这样的自定义端口。因此,把系统 DNS 写成 127.0.0.1,却让内核只监听 127.0.0.1:1053,会造成端口不匹配。使用系统直连本地 DNS 的方案时,需要监听 53 端口,并处理权限与占用;不要盲目把监听地址改为对整个局域网开放。

另一条常用路径是 TUN 接管网络流量,并把进入 TUN 的普通 DNS 请求交给内核处理。以下仍是合并片段,应先确认当前客户端没有通过界面生成另一份 TUN 配置。

config.yaml · TUN 配置片段
tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  dns-hijack:
    - any:53
    - tcp://any:53
  • any:53 与 tcp://any:53 分别覆盖进入 TUN 的 UDP、TCP 53 端口 DNS 请求。
  • auto-route 负责自动配置相关路由;启动失败时,应先检查服务权限、路由冲突和其他 VPN,而非反复切换 DNS 地址。
  • 发往本地链路或被路由排除的 DNS 请求,未必会进入 TUN。路由器 DNS、其他虚拟网卡和本地回环解析器都需要单独验证。
  • DoH 通常使用 HTTPS 的 443 端口,DoT 通常使用 853 端口。53 端口劫持不会自动把它们转换成内核 DNS 查询。

六、Windows、macOS、Linux 与 Android 逐个平台复测

Windows:区分内核监听测试与系统路径测试

以管理员权限启动客户端所需的服务或 TUN 后,先用下面两条命令检查。第一条明确查询本地 1053 端口,第二条使用系统默认 DNS,二者验证的不是同一条路径。

Windows · PowerShell 或命令提示符
nslookup -port=1053 example.com 127.0.0.1
nslookup example.com
ipconfig /all
ipconfig /flushdns

第一条在域名不属于过滤列表时,通常应返回配置地址池中的 fake-ip。第二条即使命中 TUN 劫持,界面仍可能显示原先配置的 DNS 服务器名称,不能只看那一行就判定失败。结合结果地址、日志和检测站交叉确认。清理缓存后重新访问测试域名,避免旧答案干扰。

macOS:看系统解析器列表,不只看 Wi-Fi 页面

macOS · 终端
scutil --dns
dig @127.0.0.1 -p 1053 example.com A
dscacheutil -q host -a name example.com
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

scutil --dns 可以查看包含按接口或域名划分的解析器配置。dig 的定向查询用于检查内核监听;dscacheutil 更适合补充观察系统解析路径。启用 TUN 所需的授权后,再复测浏览器;不要因为 dig 成功就省略系统层检查。

Linux:注意 systemd-resolved 与回环地址

Linux · 使用 systemd-resolved 的系统
resolvectl status
resolvectl query example.com
dig @127.0.0.1 -p 1053 example.com A
sudo resolvectl flush-caches

/etc/resolv.conf 中出现 127.0.0.53,通常表示应用先访问本地解析存根,不代表最终上游就是本机。继续查看 resolvectl status 的每接口 DNS。未使用 systemd-resolved 的发行版,应检查 NetworkManager 或实际 DNS 管理服务,不要直接覆盖由服务自动生成的文件。

Android:私人 DNS 与浏览器安全 DNS 分开排查

在原生 Android 常见路径“设置”→“网络和互联网”→“私人 DNS”中,临时关闭指定的私人 DNS,再重跑同一组测试;不同厂商菜单名称可能不同。确认客户端已获得 VPN 授权,且分应用代理没有排除测试浏览器。Android 通常只能同时运行一个普通 VPN 服务,另一个 VPN 或本地过滤工具可能占用这条路径。

Chrome 桌面版常见路径为“设置”→“隐私和安全”→“安全”→“使用安全 DNS”。关闭它只是用于定位变量,测试结束后可以恢复,再核对该 DoH 连接的实际出口。保留浏览器 DoH 不一定构成泄漏,关键是它是否符合你的解析与路由策略。

七、fake-ip 已生效,仍有异常时检查这四项

  1. IPv6 走了另一条路:dns.ipv6: false 控制内核 DNS 对 AAAA 查询的处理,不等于关闭系统 IPv6。应用缓存、自带解析器或已有 IPv6 地址仍可能建立连接,应核对 IPv6 路由与 TUN 覆盖范围。
  2. 订阅更新覆盖了手工修改:重新加载后检查运行中的有效配置,而不只是编辑器里的文件。需要长期保留的字段应通过客户端支持的覆写、合并或持久化配置机制管理。
  3. 过滤列表过大:把大量公共域名加入 fake-ip-filter 会让它们返回真实地址,但不会自动修正解析出口。只为确实存在兼容问题的域名添加例外,每次添加后复测。
  4. 缓存与旧连接残留:系统缓存、浏览器缓存、内核映射和已建立连接各有生命周期。重载后先关闭测试标签页,必要时重启浏览器,再执行新的检测,不要拿旧连接给新配置打分。

最后用一张验收清单收口

  • 定向查询 127.0.0.1:1053 有响应,普通测试域名能得到预期的 fake-ip 映射。
  • 系统默认解析与实际浏览器测试均已检查,不只是测试过内核端口。
  • DNS 上游的出口与规则一致,节点域名和启动解析的例外已明确记录。
  • 分别检查普通 DNS、浏览器 DoH、IPv6 与分应用排除项,没有把 53 端口劫持当成全协议接管。
  • 订阅更新、客户端重启以及 Wi-Fi 切换后重新测试,结果仍符合既定策略。

排查顺序可以压缩成三句话:先证明查询进入内核,再证明上游连接走对出口,最后证明常用应用没有另走一条路。fake-ip 解决域名映射,respect-rules 解决上游选路,系统 DNS 接管解决入口覆盖;三者分工明确,日志才不会变成猜谜游戏。