一个下午就花在这上面了……

为什么要用 DNS 分流

  1. 如果只用国外 DNS,一些国内域名会被解析到国外 CDN 的 IP,访问缓慢。
  2. 如果只用国内 DNS,会有 DNS 污染 的问题。而且和国外 DNS 相比,使用国内 DNS 隐私风险相对较高。

之前所用方案及其痛点

我之前使用 SmartDNS 作为 DNS 服务器,用 dnsmasq-china-list 实现 DNS 分流。

dnsmasq-china-list 这类分流方案的核心是维护一个常用的国内域名列表,当查询列表内域名的 IP 时,DNS 服务器选用国内 DNS 查询,其它情况下,使用国外 DNS 查询,V2Ray 也能这么实现 DNS 分流。V2Ray 有 domain-list-community 这样的官方列表,也有 v2ray-rules-dat 这种第三方路由规则文件加强版。

我日常使用 V2Ray 跨越长城,配置 V2Ray 的路由让 geosite:geolocation-cn 列表里的域名直连。用 dnsmasq-china-list 分流 DNS 的时候,可能会遇到某个域名「国内 DNS 解析出国内 IP 后走代理 / 国外 DNS 解析出国外 IP 后直连」的情况,而我的期望是:对于国内域名,使用国内 DNS 解析并直连,其它的任何情况都使用国外 DNS 解析并通过代理连接。

由此可见,解决方法有以下几种:

  1. 让 DNS 服务器使用 domain-list-community。
  2. 让 V2Ray 使用 dnsmasq-china-list 分流。
  3. 让 V2Ray 和 DNS 服务器都用 v2ray-rules-dat。

本文介绍的是第一种方法:直接把 V2Ray 配置成 DNS 服务器。

V2Ray as a Standalone DNS Server

我最初的想法是单独跑一个 V2Ray 作为 DNS 服务器,所用配置1是这样的:

{
    "dns": {
        "servers": [
            // 如果想配置域名 DoH 服务器(例如 https+local://dns.google/dns-query),
            // 得另外加个 IP 服务器(例如 https+local://1.1.1.1/dns-query)
            // 并让 dns.google 匹配到该 IP 服务器解析。
            "https+local://1.1.1.1/dns-query",
            "https+local://1.0.0.1/dns-query",
            {
                "address": "https+local://223.5.5.5/dns-query",
                "domains": [
                    "geosite:geolocation-cn"
                ]
            }

        ]
    },
    "inbounds": [
        {
            "listen": "127.0.0.1",
            "port": 53,
            "protocol": "dokodemo-door",
            "tag": "dns-in",
            "settings": {
                // A 记录和 AAAA 记录会由上面配置的 DNS 服务器处理,
                // 不会被转发到这里。
                // 这里只是流量转发,不能像上面那样写 DoH。
                "address": "8.8.8.8",
                "port": 53,
                "network": "tcp,udp"
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "dns",
            "tag": "dns-out"
        }
    ],
    "routing": {
        "rules": [
            {
                "type": "field",
                "inboundTag": "dns-in",
                "outboundTag": "dns-out"
            }
        ]
    }
}

按照配置,V2Ray 是这么工作的:V2Ray 监听本地的 53 端口,程序向它发送 DNS 请求时,V2Ray 通过路由将其发送至 DNS 出站协议2,而 DNS 出站协议会将 IP 查询(即 A 记录和 AAAA 记录)转发至 V2Ray 内置的 DNS 服务器3,再通过配置 V2Ray 的内置 DNS 服务器实现分流。

用了一会,发现:

  1. 通过 https+local://223.5.5.5/dns-query 查询未缓存的国内域名耗时 1.04-1.10s,作为对比,SmartDNS 下查询未缓存的域名耗时 35-120ms。
  2. 通过 https+local://1.1.1.1/dns-query 查询未缓存的国外域名耗时 1.24-1.27s,作为对比,SmartDNS 下查询未缓存的域名耗时 0.236s-1.46s。
  3. 查询已经缓存的域名仍需 1.01-1.02s,作为对比,SmartDNS4 下查询已缓存域名只需 9-15ms5

这样的速度实在是不可接受。

SmartDNS in Front of V2Ray DNS Server

我们可以在 V2Ray 前面加一个 DNS 服务器用作缓存,让该缓存 DNS 服务器监听本地 53 端口,同时配置上游 DNS 为 V2Ray。

以下是 SmartDNS 的配置示例:

bind [::]:53

speed-check-mode none

server 127.0.0.1:5353

同时,V2Ray inbounds 部分的 ports 也要改成 5353。

One More Thing

$ dig google.com TXT +short
93.46.8.90
$ dig google.com TXT +short
8.7.198.46
$ dig baidu.com TXT +short
"google-site-verification=GHb98-6msqyx_qqjGl5eRatD3QTHyVB6-xQ3gJB5UwM"
"v=spf1 include:spf1.baidu.com include:spf2.baidu.com include:spf3.baidu.com a mx ptr -all"

前面提到过,V2Ray 的内置 DNS 服务器只支持 A 和 AAAA 记录,如果查询其它记录,会将请求转发至 8.8.8.8 的 53 端口,所以我们查询 google.com 的 TXT 记录时,返回的结果就被污染了。

解决方法很简单:在 V2Ray 后面加一个 DNS 服务器,专门处理非 A 和 AAAA 记录的查询。

以下是 SmartDNS 的配置示例:

bind [::]:5354

speed-check-mode none

server-https https://1.1.1.1/dns-query

同时,V2Ray inbounds 部分的 settings 中的 addressport 要分别改为 127.0.0.1 和 5354。

如果将两个配置写在一起:

bind [::]:53 -group front
bind [::]:5354 -group back

speed-check-mode none

server 127.0.0.1:5353 -group front
# 如果想配置域名 DoH 服务器,参见 https://github.com/pymumu/smartdns/issues/613。
server-https https://1.1.1.1/dns-query -group back

之后,DNS 记录就不会被污染了:

$ dig google.com TXT +short
"facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95"
"docusign=1b0a6754-49b1-4db5-8540-d2c12664b289"
"globalsign-smime-dv=CDYX+XFHUw2wml6/Gb8+59BsH31KzUr6c1l2BPvqKX8="
"docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e"
"v=spf1 include:_spf.google.com ~all"

其它方案

以下是我想到的其它方案,不再赘述:

  1. 只让 V2Ray DNS 承担分流工作,具体解析交给 SmartDNS 进行,这样或许能降低查询时间,也能用上 SmartDNS 的测速功能。

  2. 不让 V2Ray 接管系统 DNS,以 domain-list-community 或 v2ray-rules-dat 为基础生成 SmartDNS 的分流配置6,这样也能避免「某个域名国内 DNS 解析后走代理 / 国外 DNS 解析后直连」的情况。


  1. 对外开放 v2ray 的 DNS 服务 为基础修改而成。 ↩︎

  2. https://www.v2fly.org/config/protocols/dns.html ↩︎

  3. https://www.v2fly.org/config/dns.html ↩︎

  4. 为控制变量,SmartDNS 的上游 DNS 服务器配置分别为 server-https https://223.5.5.5/dns-queryserver-https https://1.1.1.1/dns-query,并关闭了测速功能。 ↩︎

  5. 以上时间通过类似 time dig google.com 的命令测出,仅供参考。 ↩︎

  6. 我没找到现成的轮子。 ↩︎