为什么要用 DNS 分流
- 如果只用国外 DNS,一些国内域名会被解析到国外 CDN 的 IP,访问缓慢。
- 如果只用国内 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 解析并通过代理连接。
由此可见,解决方法有以下几种:
- 让 DNS 服务器使用 domain-list-community。
- 让 V2Ray 使用 dnsmasq-china-list 分流。
- 让 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 服务器实现分流。
用了一会,发现:
- 通过
https+local://223.5.5.5/dns-query
查询未缓存的国内域名耗时 1.04-1.10s,作为对比,SmartDNS 下查询未缓存的域名耗时 35-120ms。 - 通过
https+local://1.1.1.1/dns-query
查询未缓存的国外域名耗时 1.24-1.27s,作为对比,SmartDNS 下查询未缓存的域名耗时 0.236s-1.46s。 - 查询已经缓存的域名仍需 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
中的 address
与 port
要分别改为 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"
其它方案
以下是我想到的其它方案,不再赘述:
- 只让 V2Ray DNS 承担分流工作,具体解析交给 SmartDNS 进行,这样或许能降低查询时间,也能用上 SmartDNS 的测速功能。
- 不让 V2Ray 接管系统 DNS,以 domain-list-community 或 v2ray-rules-dat 为基础生成 SmartDNS 的分流配置6,这样也能避免「某个域名国内 DNS 解析后走代理 / 国外 DNS 解析后直连」的情况。
- 以 对外开放 v2ray 的 DNS 服务 为基础修改而成。
- https://www.v2fly.org/config/protocols/dns.html
- https://www.v2fly.org/config/dns.html
- 为控制变量,SmartDNS 的上游 DNS 服务器配置分别为
server-https https://223.5.5.5/dns-query
和server-https https://1.1.1.1/dns-query
,并关闭了测速功能。 - 以上时间通过类似
time dig google.com
的命令测出,仅供参考。 - 我没找到现成的轮子。
以上方法过于笨拙丑陋,配置也很复杂。SmartDNS 和 V2Ray 相互依赖,如果任意一个挂掉,整个系统的 DNS 服务就都没了。而且就算让 V2Ray 和 SmartDNS 协同工作,查询未缓存的域名仍需 1s 以上,这是几乎在给自己找罪受。
目前所用方案
目前,我用的是 cgproxy,让它捕获本机发送的 DNS 查询,转发给 V2Ray 处理,这样能避免「国内 DNS 解析出国内 IP 后走代理 / 国外 DNS 解析出国外 IP 后直连」,唯一的坏处是「V2Ray 只支持最基本的 IP 查询(A 和 AAAA 记录)」。但仔细一想,我其实很少查询其它类型的 DNS 记录,所以也没关系。
同时,cgproxy 的确是个相当好用的工具,用它实现全局透明代理后,不需要额外配置就能让所有程序都走代理,这样也能充分利用 V2Ray 的分流功能。
V2Ray 的 DNS 分流配置大致如下:
{
"dns": {
"disableFallback": true,
"servers": [
"https://cloudflare-dns.com/dns-query",
{
"address": "https+local://223.5.5.5/dns-query",
"domains": [
"geosite:geolocation-cn"
]
}
]
}
}