搭建全协议DNS服务器
DNS污染现状
从没有一个国家像我们一样拥有如此严重的DNS缓存投毒,而与此对抗的最有力手段则是加密DNS查询。
DNS劫持与污染是GFW与各ISP服务商的常见手段,常用于屏蔽网站与广告引流。DNS服务器负责将域名解析为对应的IP地址,这个过程默认使用UDP的53号端口进行通讯,而这种未经加密的数据包极易被修改伪造,把域名指向不正确的IP地址,从而造成DNS劫持。
操作系统一般会有一层DNS缓存用于加快解析速度,在查询过一个域名后,得到的数据会被缓存下来,下一次查询时直接将数据返回而无需再次发起DNS请求。但如果第一次查询得到的数据是被劫持过的虚假数据,那系统将在缓存有效期内持续提供错误结果,这个现象称为 DNS缓存投毒
。这种DNS污染行为不仅针对用户终端系统,对于公共DNS服务器(Public DNS,大型公司或组织设立)和本地DNS服务器(Local DNS,运营商提供或局域网自建)同样有效,它们一般都会配置缓存,因此也会被污染。
处于国内的DNS服务器在对国外网站发起执行DNS迭代查询时,由于域名的权威服务器位于海外,数据包不可避免的要经过防火长城,而部署于其上的审查机制就会生效,一旦匹配到黑名单的解析请求,马上伪装成目标服务器返回虚假的查询结果,由于查询者接受最先得到的解析结果,无论真正的UDP数据包无论有没有返回都会被丢弃,造成错误的DNS解析。这个过程是GFW屏蔽网站的最基本手段,它会波及到绝大多数的境内DNS服务器,进而导致全国性的DNS污染,严重的时候甚至会扩散到周边国家。
同样的,ISP服务商也可以对DNS数据包进行劫持,这种行为一般用于广告引流,将被劫持的域名指向自己的服务器IP,提供广告或其他内容获利。由于用户的所有流量都流经ISP服务商的网络,即使不使用运营商分配的DNS服务器,返回的数据包仍然可能被劫持,这种行为在大多数用户不察觉的情况下发生,因此常规DNS解析难以保证安全性。
DNS规范在制定的时候并未考虑过如此严重的劫持行为,直接使用UDP数据包明文通信,即使后面各组织为此做出不少努力,这个缺陷也从来没有被真正弥补过。IETF曾在1997年推出DNSSEC这种签名机制,它被列入了互联网标准化文档中,是最早大规模部署的DNS安全协议,但是时至今日,全世界启用完整DNSSEC的域名仅有24.66%,而在中国仅有1.18%的覆盖率,位列全球倒数第二,足可见形势不容乐观。你可以在APNIC(亚太互联网络信息中心)官网上看到全球的DNSSEC覆盖情况与国内的DNSSEC分析。
另一方面,DNSSEC仅仅使用了信任链签名机制,它能保证查询结果的准确性,这种方案在大多数网络下已经能保证足够的可用性,但在GFW面前却如螳臂当车,因为DNS签名机制虽不能被伪造,但可以被截断,也就是直接丢弃该数据包,或返回一个无签名的虚假数据包。此外,即便启用了DNSSEC,请求时也仅能保证迭代查询的正确性,本机发起的递归查询返回的结果仍可能被劫持,更何况大量普通用户使用运营商的DNS服务器,这种情况下本地DNS服务器就是内鬼,DNS签名机制形同虚设。
所以,在每况愈下的环境中,为了对抗DNS劫持与污染,各种DNS加密查询协议应运而生,目前主流的有 DNS-over-TLS
、DNS-over-HTTPS
、DNSCrypt
,还有萌芽阶段的 DNS-over-QUIC
。
DNS各协议简介
以下几种DNS查询机制是目前公开的协议,有规范化的文档与实现,后面将搭建支持以下全部协议的DNS服务器
Plain DNS
Plain DNS即常规DNS,它的格式为单个IPv4或IPv6地址,查询时默认使用UDP或TCP的 53
端口通讯,必要时可手动指定其他端口(需要系统或软件支持)。该请求信息未经加密,易被伪造,是目前使用范围最广的查询方式,上文所提及的DNS劫持与污染均基于该机制实现,目前正逐步被其他加密DNS查询所替代。
DNS-over-TLS
DNS-over-TLS简称 DoT
,是一种基于TLS进行报文加密的DNS请求方式,查询时默认使用TCP的 853
端口进行通讯,在TLS握手以后再执行DNS请求与应答,TLS可以保证通信双方数据的保密性和完整性,这种DNS请求方式几乎不可能被劫持,且由于请求直接从用户终端发起,ISP也无法对其内容进行修改。
由于TLS证书上有CA的签名,如果系统的根证书信任未被篡改过,那就可以保证通讯服务器不是GFW伪造的,因此GFW只能阻断指定域名或IP的TCP连来拦截DoT,这种行为最多只能让DNS查询超时,但无论如何都不会得到错误的结果。
相较于常规DNS查询,DoT明显有保密性和完整性的优势,但代价就是更高的时延,理论上至少为常规DNS的四倍。同时,DNS服务器需要接受多次TSL连接,在高并发的环境下对性能也有更高的要求。
DoT延时较高的问题在实际使用中感知不强,因为整个解析过程最多也就几百毫秒,相比于网页的加载时间并不明显,况且系统中也存在DNS缓存,短时间内重复查询与常规DNS无异。而相比于这些,更应该关心的是能否查到正确的DNS解析结果,这才是DoT的意义所在。
不过DoT也有其弱点所在,它默认使用853接口,一旦GFW决定大规模封杀它的使用,只需要对853端口的出境流量严格审查或直接切断,即使一部分系统或软件支持切换端口,但也仅仅是治标不治本。这是DoT相较于DoH的最明显缺点,当然两者在不同环境下各有优势,不可一概而论。
DoT服务的格式为 tls://$域名
或 tls://$IP
,IP地址若为IPv6,需要括上 []
符号。此外,搭建DoT服务时需要有一张针对域名或IP地址的TLS证书,它同时也可以用在HTTPS认证上。
DNS-over-HTTPS
DNS-over-HTTPS简称 DoH
,是一种基于HTTPS协议进行数据传输和加密的DNS请求方式,查询时默认使用TCP的 443
端口进行通讯。直观讲就是在DNS外面套一层HTTPS,或者说把DNS请求变成HTTPS请求,从而达到加密的效果。
相比于DoT,DoH使用HTTPS进行连接,由于HTTPS协议也是由TLS加密的,因此DoH和DoT在本质上是相同的,即DoH也可以保证数据的保密性和完整性。不同的是,DoT默认使用853端口,DoH则默认使用HTTPS标准的443端口。
在逻辑上,DoH的实现更加简单,只需把正常的DNS查询报文先处理为 base64url
编码,接在 dns-query
后的HTTP请求参数中发起GET请求即可,这个过程在RFC8484中有详细说明。
DoH相较于DoT有一些独有优势,一方面由于它默认走443端口,可以与其他网页流量混合在一起,出境时在GFW看来与网页访问没有明显区别。当然,它没有到达某些加密代理工具的反检测高度,在需要的时候GFW只需做一次主动探测,或对于DNS服务器发起重放攻击,就可以立刻识别出目标服务器的性质,但无论如何,DoH的隐蔽性和反检测性都是高于DoT的。另一方面,由于请求包含在HTTP流量中,DoH请求可以透过CDN加速,即使它是动态请求需要实时回源,但在大部分网络环境下CDN的边缘节点会有更好的数据路由,加快请求速度。
DoH同时也有一些DoT不具备的缺点,譬如它的基于HTTP协议,延迟相较于DoT会稍高一些,不过这一点几乎可以忽略不计。在网络环境差的情况下,DoH性能也会一定程度上低于DoT,总之,两者各有优劣胜负所在。
DoT服务的格式一般为 https://$域名/dns-query
或 https://$IP/dns-query
,IP地址如果为IPv6地址,需要括上 []
符号。与DoT相同,DoH机制也需要针对域名或IP的TLS证书,这个机制可以保证客户端在查询时不被劫持到错误的服务器上。
DNS-over-QUIC
QUIC是 Quick UDP Internet Connection
的缩写,是由谷歌制定的一种传输层协议。它基于UDP协议,融合了TCP、TLS、HTTP/2等协议的特性,在UDP的不可靠性上实现了可靠的加密链路,得益于UDP的低延迟与相对效率更高的特性,QUIC相比于传统TCP+TLS有着更低的连接延迟、更高的传输效率、更稳定的传输效果。
QUIC可以简单理解为在UDP协议上重新实现了HTTP/2,即 QUIC = UDP + TLS + HTTP2
,但比原来的协议栈有着各方面的优势,因此,在2018的IETF会议上,HTTP-over-QUIC被定义为HTTP/3。当然,现阶段QUIC还没有形成标准RFC文档,仍处于测试阶段,但已经有不少浏览器与服务根据草案开始支持该协议。
基于QUIC的DNS查询称为DNS-over-QUIC,简称 DoQ
,默认使用UDP的 784
或 8853
端口(QUIC基于UDP协议)。相比于DoH与DoT,它拥有QUIC协议的优势,重点表现在低延迟上,缓解了前两者一直被诟病的卡顿问题。在QUIC协议中,首次连接需要1-RTT的延迟,此后连接时可以达到0-RTT的连接延迟,远胜于TCP+TLS的3-RTT连接延迟,而DoH与DoT都没有脱离传统TCP协议栈,在延迟上逊于QUIC协议。在一些测试中,DoQ在弱网络中相比DoH或DoT有着明显优势,体验上可以接近传统的UDP查询。
另一方面,使用特定默认端口的DoQ很容易GFW识别并拦截,与DoT相同,这个问题可以通过修改端口缓解,但它并不受一些系统与客户端的支持。幸运的是,一旦HTTP/3能定稿并普及开来,基于该协议的DNS查询就能兼具低延迟与隐蔽性的优势了,但这或许需要很长一段时间。
当下,QUIC还是一个全新的协议,标准文档也还未出炉,支持的服务很少,而支持DoQ的软件就更少了,当下也仅有AdGuard的系列软件有对它启用实验性的支持。DoQ服务的格式一般为 quic://$域名
或 quic://$IP
。
DNSCrypt
DNSCrypt在目标上与DoT、DoH、DoQ类似,都是将DNS查询放在加密链路上传输,防止被窃听或劫持,但是它的实现方式略有不同。相比于前三者,它不需要借助于域名工作,也不依赖于被信任的TLS证书,它仅仅提供了一种针对性的协议用于加密客户端与DNS服务器之间的通信。
你可以将它理解为一个独立的协议,不依赖于既有的HTTP、TLS等协议,有着自己的一套机制用于保证数据的传输安全与身份认证。它的v2版本在2013年发布,官网介绍称其是“迄今为止部署最多的加密DNS协议”。在该协议中,DNS服务器上会生成一对公私钥,并将公钥派发给客户端用于认证与加密,因此,DNSCrypt客户端必须明确信任所选提供者的公钥,想使用哪个DNSCrypt服务器,就需要预先安装该服务器的公钥,而不是通过常规的受信任证书颁发机构列表签名获取信任。
DNSCrypt协议是一个自由开放协议,不属于任何个人或组织,官网上也有它详细的规范文档,不过由于作者没有申请将其列入标准化文档,因此目前并不是一个正式的协议,在大规模的应用中存在一定的局限性。尽管如此,它却是目前使用量最大的加密DNS查询协议,有不少基于它实现的客户端与服务。
使用DNSCrypt时客户端需要三个必备参数:服务器IP地址、公钥、证书名称,其中IP地址若为IPv6需要括上 []
。由于多个参数在共享时并不方便,一般使用 DNS Stamp
将其封装为 sdns://···
的格式,当然DNS Stamp不仅仅可用于封装DNSCrypt,同样也支持DoT、DoH、DoQ等协议,你可以在这里看到它的完整介绍。
公共DNS服务器
Public DNS,即公共DNS服务器,一般由大型互联网公司推出,节点分布全球各地,延迟较低,用户数量大,具有一定的安全性和抗攻击性。目前有不少公共DNS服务,在不同地区的使用体验不尽相同,这里有一份较为完整的全球公共DNS列表,上面列举了绝大多数常用的公共DNS服务及对应地址。
国内常用公共DNS
国内首选阿里或腾讯DNS,污染较小且稳定性好,且阿里DNS更有完整IPv6支持
$alidns_ip
为阿里DNS的IPv4地址
IPv4地址:
223.5.5.5
或223.6.6.6
IPv6地址:
2400:3200::1
或2400:3200:baba::1
DoT地址:
tls://dns.alidns.com
或tls://$alidns_ip
DoH地址:
https://dns.alidns.com/dns-query
或https://$alidns_ip/dns-query
IPv4地址:
119.29.29.29
或119.28.28.28
DoT地址:
tls://dot.pub
或tls://dns.pub
DoH地址:
https://doh.pub/dns-query
或https://dns.pub/dns-query
IPv4地址:
180.76.76.76
IPv6地址:
2400:da00::6666
- IPv4地址:
114.114.114.114
或114.114.115.115
。
国外常用公共DNS
在国内使用延迟较高,国外服务器首选Cloudflare DNS或Google DNS。
$cfdns_ip
为Cloudflare DNS的IP地址,IPv6地址需要用 []
括起。
IPv4地址:
1.1.1.1
或1.0.0.1
IPv6地址:
2606:4700:4700::1111
或2606:4700:4700::1001
DoT地址:
tls://$cfdns_ip
DoH地址:
https://dns.cloudflare.com/dns-query
或https://$cfdns_ip/dns-query
$googledns_ip
为Google DNS的IP地址,IPv6地址需要用 []
括起,DoH查询的Host仅支持IPv4地址。
IPv4地址:
8.8.8.8
或8.8.4.4
IPv6地址:
2001:4860:4860::8888
或2001:4860:4860::8844
DoT地址:
tls://dns.google
或tls://$googledns_ip
DoH地址:
https://dns.google/dns-query
或https://$googledns_ipv4/dns-query
测试方法
在搭建DNS服务器时,我们需要有一个简单的方式确保服务是正常工作的,这里建议使用dnslookup进行测试,它是一个专用于多协议DNS调试的命令行工具,基于MIT许可证开源,支持所有已知的DNS协议,包括常规DNS、DoH、DoT、DoQ以及DNSCrypt。它类似于dig工具,但是更纯粹易用,使用时只需指定目标域名和DNS服务器地址,同时也提供了指定DNS服务器的IP地址、输出JSON格式、跳过证书验证等功能。
获取工具
你可以直接下载它的Release到本地使用。Windows系统下直接把 dnslookup.exe
解压到 C:\Windows\System32\
下即可,Linux系统可按如下命令操作:
# v1.4.5是当下的最新版本,实际下载时需要到release页获取最新链接
shell> wget "https://github.com/ameshkov/dnslookup/releases/download/v1.4.5/dnslookup-linux-amd64-v1.4.5.tar.gz"
···
# 解压并安装
shell> tar xf dnslookup-linux-amd64-v1.4.5.tar.gz
shell> mv linux-amd64/dnslookup /usr/bin/
shell> rm -rf dnslookup-linux-amd64-v1.4.5.tar.gz linux-amd64/
安装完毕后使用命令 dnslookup --help
输出使用说明,可用于确认是否安装成功。
shell> dnslookup --help
dnslookup v1.4.5
Usage: dnslookup <domain> <server> [<providerName> <serverPk>]
<domain>: mandatory, domain name to lookup
<server>: mandatory, server address. Supported: plain, tls:// (DOT), https:// (DOH), sdns:// (DNSCrypt), quic:// (DOQ)
<providerName>: optional, DNSCrypt provider name
<serverPk>: optional, DNSCrypt server public key
使用示例
这里使用AdGuardHome提供的DNS服务进行测试,后期可能会被GFW屏蔽
常规DNS
在国内网络查询谷歌、推特等被封锁的域名会受到污染,这里使用 baidu.com
进行查询。
shell> dnslookup baidu.com 176.103.130.130
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 37745
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;baidu.com. IN A
;; ANSWER SECTION:
baidu.com. 33 IN A 220.181.38.148
baidu.com. 33 IN A 39.156.69.79
DNS-over-TLS
shell> dnslookup google.com tls://dns.adguard.com
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 37703
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;google.com. IN A
;; ANSWER SECTION:
google.com. 163 IN A 216.58.197.14
DNS-over-HTTPS
shell> dnslookup youtube.com https://dns.adguard.com/dns-query
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 60383
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN A
;; ANSWER SECTION:
youtube.com. 199 IN A 172.217.161.206
DNS-over-QUIC
shell> dnslookup facebook.com quic://dns.adguard.com
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 65467
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;facebook.com. IN A
;; ANSWER SECTION:
facebook.com. 296 IN A 185.60.216.35
DNSCrypt
shell> dnslookup twitter.com sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIj
IuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 27774
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;twitter.com. IN A
;; ANSWER SECTION:
twitter.com. 168 IN A 104.244.42.1
安装服务
部署AdGuardHome服务提供多协议的DNS服务,如果想实现无污染DNS,则服务器必须在国外
AdGuard是一个常用的广告拦截器,AdGuardHome是其下的一个项目,它部署在路由器或服务器上,可以在DNS解析层面拦截广告。虽然AdGuard是商业化软件,但AdGuardHome却是免费的,它基于GPL3.0协议开源,没有付费增强的商业化机制,且社区与交流群活跃,项目更新速度快。
在AdGuardHome官网上,大部分介绍均为去广告功能,虽然名义上如此,但它是目前唯一一个能提供全协议的DNS服务工具,我们可以把它当作DNS服务使用,侧重其加密解析功能。它可以自定义拦截与重写列表,对不同的客户端进行管理,对不同的域名与请求来源进行统计,更有完善的图形化界面。所以说,去广告只是一个可选的附加功能,甚至默认的广告拦截规则也是开源的,需要的时候也可以编写自己的规则。
AdGuardHome使用Go语言编写,如果本机有Go编程环境也可以克隆源码自行编译。不过,它的Github仓库中有编译好的二进制资源,安装时直接下载对应版本的Release即可,一般有以下两种安装方式:
注意,AdGuardHome默认占用系统TCP的3000端口,如果该端口上存在其他服务(如Gitea等)会导致启动失败,遇到该情况请先暂时关闭该端口上的服务,安装后AdGuardHome可以修改为在其他端口上运行。
一键安装
AdGuardHome提供了全自动的脚本安装,只需运行安装脚本,即可一键安装最新稳定版(Latest release)。不过,由于Github在国内经常受到干扰,脚本获取与安装可能出现运行缓慢或异常报错,因此大陆地区的VPS可以尝试手动安装的方式。
# 运行安装脚本
shell> curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh
···
[info] AdGuard Home will be installed to /opt/AdGuardHome
[info] Downloading package from https://static.adguard.com/adguardhome/release/AdGuardHome_linux_amd64.tar.gz -> AdGuardHome_linux_amd64.tar.gz
[info] Unpacking package from AdGuardHome_linux_amd64.tar.gz -> /opt
···
AdGuard Home is successfully installed and will automatically start on boot.
···
··· [info] AdGuard Home is available on the following addresses:
··· [info] Go to http://127.0.0.1:3000
··· [info] Go to http://172.17.36.87:3000
··· [info] Action install has been done successfully on linux-systemd
[info] AdGuard Home is now installed and running.
···
# 安装完成
运行该命令后,脚本会把AdGuardHome默认安装到 /opt/AdGuardHome
下,如果不习惯也可以将其链接到 /etc
目录
# 创建一个软链接
shell> ln -s /opt/AdGuardHome/ /etc/
# 进入AdGuardHome文件夹
shell> cd /etc/AdGuardHome
脚本同时会创建一个 systemctl
服务,你可以通过如下命令查看运行情况
# 出现下列语句即服务运行正常
shell> systemctl status AdGuardHome
···
Active: active (running) ···
···
AdGuardHome默认监听TCP的3000号端口,可以通过以下命令查看它的占用情况
# 查询AdGuardHome在TCP端口的监听情况
shell> netstat -tlnp | grep AdGuardHome
tcp6 0 0 :::3000 :::* LISTEN 22587/AdGuardHome
# 可以确定AdGuardHome在全局监听TCP的3000号端口
手动安装
在AdGuardHome项目的Release页,下载当前系统对应版本的安装包,解压到指定目录并配置为系统服务即可。
# 下载安装包
shell> wget https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.104.3/AdGuardHome_linux_amd64.tar.gz
···
# 解压到 /opt 目录下
shell> tar xf AdGuardHome_linux_amd64.tar.gz -C /opt/
# 删除下载的包
shell> rm -f AdGuardHome_linux_amd64.tar.gz
# 创建软连接到/etc下
shell> ln -s /opt/AdGuardHome/ /etc/
手动配置systemctl服务
# 创建并写入下述配置
shell> vim /etc/systemd/system/AdGuardHome.service
填入以下配置
[Unit]
Description=AdGuard Home: Network-level blocker
ConditionFileIsExecutable=/opt/AdGuardHome/AdGuardHome
After=syslog.target network-online.target
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/opt/AdGuardHome/AdGuardHome "-s" "run"
WorkingDirectory=/root
Restart=always
RestartSec=10
EnvironmentFile=-/etc/sysconfig/AdGuardHome
[Install]
WantedBy=multi-user.target
重载systemctl,开启AdGuardHome服务并设置为开机启动
shell> systemctl daemon-reload
shell> systemctl start AdGuardHome
shell> systemctl enable AdGuardHome
Created symlink from /etc/systemd/system/multi-user.target.wants/AdGuardHome.service to /etc/systemd/system/AdGuardHome.service.
查看AdGuardHome服务运行状态
# 出现下列语句即服务运行正常
shell> systemctl status AdGuardHome
···
Active: active (running) ···
···
AdGuardHome管理界面默认监听TCP的3000端口,如果服务器防火墙不拦截该端口,可以直接通过 服务器IP:3000
的方式访问,也可以按下文的方式配置反向代理。
# 查询AdGuardHome在TCP端口的监听情况
netstat -tlnp | grep 3000
tcp6 0 0 :::3000 :::* LISTEN 22587/AdGuardHome
# 可以确定AdGuardHome在全局监听TCP的3000号端口
配置服务
后文使用
dns.dnomd343.top
作为示例域名
配置反向代理
配置 dns.dnomd343.top
解析到当前服务器,通过Nginx实现反向代理本地3000端口。
shell> cd /etc/nginx/conf.d/
shell> vim dns.conf
写入以下配置,注意这里需要TLS证书,申请方式可见acme.sh使用说明
server {
listen 80;
server_name dns.dnomd343.top;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name dns.dnomd343.top;
ssl_certificate /etc/ssl/certs/dnomd343.top/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/dnomd343.top/privkey.pem;
location / {
proxy_pass http://localhost:3000;
}
}
# 重启nginx
shell> nginx -s reload
此时访问 https://dns.dnomd343.top
,你的访问数据会被代理到AdGuardHome提供的服务端口上
如果配置正确会显示如下网页
点击下一步后开始配置端口
网页管理界面监听接口按情况分类,如果是反向代理模式可以选 lo - 127.0.0.1
,否则可以选所有接口,端口将默认的80改为3000。DNS服务器选择监听所有接口,端口为53不变用于提供常规DNS服务。不过,如果你的服务器上运行有其他DNS软件(BIND等),53端口会可能被占用,你也可以按需要改成其他端口。
输入用户名和密码后下一步
这一步会显示一些提示,上方的监听地址分别为本机回环地址和内网IP
到这一步就配置完成了,点击进入仪表盘
注意,此时网页会跳到 http://dns.dnomd343.top:3000/login.html
的登陆页面,如果你的防火墙没有放行3000端口,这里将会出现无法访问的错误,需要手动转到 https://dns.dnomd343.top/login.html
的反向代理页面。
如果不想访问dns.dnomd343.top时直接跳出AdGuardHome的登录页,可以将其隐藏在 /admin
下,注意这里必须配置 login.html
页面的跳转,否则访问 https://dns.dnomd343.top/admin
将会跳转到 https://dns.dnomd343.top/login.html
导致404错误。
shell> vim /etc/nginx/conf.d/dns.conf
修改配置文件为以下内容
server {
listen 80;
server_name dns.dnomd343.top;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name dns.dnomd343.top;
ssl_certificate /etc/ssl/certs/dnomd343.top/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/dnomd343.top/privkey.pem;
location /admin/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:3000/;
}
location /login.html {
return 301 https://$server_name/admin/login.html;
}
}
# 重启Nginx
shell> nginx -s reload
此时访问 https://dns.dnomd343.top
将会是nginx的默认主页,而访问 https://dns.dnomd343.top/admin
将会跳转到AdGuardHome的登录页面。
输入设置的用户名和密码后进入仪表盘。
你可以在这里调整AdGuardHome的各项配置以满足需求。
配置DNS上游
在设置 - DNS设置中可以设置上游DNS服务器,当AdGuardHome收到合法的DNS请求后,会按配置通过规则以及host等模块拦截或改写一部分查询,剩余的请求就会转发给上游DNS服务器。与下游查询协议类似,AdGuardHome同样也支持多种上游DNS查询协议,即常规DNS、DoH、DoT、DoQ和DNSCrypt等,通过这些协议向大型公共DNS请求数据。
由于服务器在国外基本不受到DNS污染,可以直接使用常规DNS查询,一般情况下可用直接设置为常用公共DNS的IP地址,如Cloudflare DNS(1.1.1.1)、Google DNS(8.8.8.8)、Quan9 DNS(9.9.9.9)等,你也可以在上文公共DNS服务器的列表中选择其它服务或协议。
在配置完成后,AdGuardHome就已经能正常运作了,为了测试服务的可用性,可以发起常规DNS查询测试是否配置成功。
# 向配置好的服务器发起常规DNS请求
shell> dnslookup baidu.com 47.242.30.65
dnslookup v1.4.5
dnslookup result:
;; opcode: QUERY, status: NOERROR, id: 26085
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;baidu.com. IN A
;; ANSWER SECTION:
baidu.com. 415 IN A 39.156.69.79
baidu.com. 415 IN A 220.181.38.148
# 正确返回了百度服务器的IP地址
这里不要用谷歌、油管等黑名单网站测试,否则怎么查询都会返回一个错误的IP。
配置加密DNS
选择设置 - 加密设置,在此处配置上文提及的几种加密DNS协议
勾选启用加密,开启AdGuardHome的加密DNS协议支持,在这一页可以配置DoH、DoT与DoQ的选项。如果你当前的TCP/443端口已经被nginx或其他网页服务器占用,右下角会出现如图443端口不可用的报错,此处直接忽略即可。
服务器名称填入用于DoH与DoT的域名,后方的重定向无需勾选,这个功能是将来自80端口的HTTP流量重定向到HTTPS,而我们这部分功能已经交给Nginx处理了。
此处HTTPS端口不同于之前配置的3000端口,这个端口负责提供DoH查询服务,同时也兼具网页管理的功能,总地来讲,可以认为它与此前的3000功能相同,但是在 /dns-query
下额外加入了DoH支持。这里的端口号我们直接将其设置为3001即可,同样不对外暴露,交给Nginx处理。至于DoT与DoQ的端口,则保持默认的 853
与 784
不变,直接对外提供服务。
下方需要我们提供TLS证书和私钥,内容对应上方填写的服务器名称,方式建议选择设置路径而不是粘贴内容,这样子以后更新证书的时候就不需要重新填写。此处还要注意证书私钥的权限问题,一般情况下它会被设置为其他用户不可读的状态,遇到这种情况可以手动配置文件属性,或者将AdGuardHome配置为root用户下运行。
此外,如果你的证书不仅包含域名,还包含服务器IP地址,那将可以以IP地址的形式进行DoH和DoT查询。
在改完并保存后,网页会自动跳转到 https://dns.dnomd343.top:3001/#encryption
,与上文的情况一样,如果防火墙未放行3001端口将会出现连接超时的错误。不过这里我们并不需要这个页面,而是配置Nginx的反向代理来接管3001端口的DoH查询流量,识别并把它转交给AdGuardHome。
shell> vim /etc/nginx/conf.d/dns.conf
修改配置文件为以下内容
server {
listen 80;
server_name dns.dnomd343.top;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name dns.dnomd343.top;
ssl_certificate /etc/ssl/certs/dnomd343.top/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/dnomd343.top/privkey.pem;
location /admin/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:3000/;
}
location /login.html {
return 301 https://$server_name/admin/login.html;
}
location /dns-query {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://127.0.0.1:3001/dns-query;
}
}
# 重启Nginx
shell> nginx -s reload
这种配置下,Nginx一旦接到 https://dns.dnomd343.top/dns-query
的访问,就会把内容转交给本地3001端口上的AdGuardHome处理。另外,/dns-query
部分的 proxy_set_header
参数必须加上,以达到在HTTP头中添加客户端来源端口的目的,否则AdGuardHome的统计中将无法出现真实的客户端IP。
配置DNSCrypt
AdguardHome目前还不支持在前端直接配置DNSCrypt协议,需要你在后台手动改写配置文件,配置文件中将会提供证书名称,私钥公钥等其他信息。
这份配置需要使用dnscrypt工具生成DNSCrypt的密钥对,直接下载最新版Release到本地运行。
# 获取release并解压
shell> wget https://github.com/ameshkov/dnscrypt/releases/download/v2.0.2/dnscrypt-linux-amd64-v2.0.2.tar.gz
···
shell> tar xf dnscrypt-linux-amd64-v2.0.2.tar.gz
# 直接复制安装
shell> mv linux-amd64/dnscrypt /usr/bin/
# 删除残余文件
shell> rm -rf dnscrypt-linux-amd64-v2.0.2.tar.gz linux-amd64/
生成一对密钥,使用 --provider-name
指定证书名,--out
指定输出文件
shell> dnscrypt generate --provider-name '2.dnscrypt-cert.dnomd343.top' --out /opt/AdGuardHome/dnscrypt.yaml
2021/01/26 03:35:40 [info] Generating configuration for 2.dnscrypt-cert.dnomd343.top
2021/01/26 03:35:40 [info] Configuration has been written to /opt/AdGuardHome/dnscrypt.yaml
2021/01/26 03:35:40 [info] Go to https://dnscrypt.info/stamps to generate an SDNS stamp
查看生成的内容
shell> cat /opt/AdGuardHome/dnscrypt.yaml
provider_name: 2.dnscrypt-cert.dnomd343.top
public_key: 358DEA7C201B068DCD8A1DFCF607C951F6FAC2148ACDC602B176A810818B30D1
private_key: 2360AA8CD96516D0E29948F64A51ECF54E95A9924AD342285FA125C0A4156278358DEA7C201B068DCD8A1DFCF607C951F6FAC2148ACDC602B176A810818B30D1
resolver_secret: FD0927401827CF442A7CC530E6F401395A3986F637168F3E7100230184C5F26A
resolver_public: E647313744A35958775E35225BB948C09E35E45BDF939B15D9188CB80F80416F
es_version: 1
certificate_ttl: 0s
编辑AdGuardHome的配置文件
shell> vim /opt/AdGuardHome/AdGuardHome.yaml
将 port_dnscrypt
从0改为5443(也可以是其他自定义端口),dnscrypt_config_file
填入刚刚生成的配置文件路径
tls:
···
port_dnscrypt: 5443
dnscrypt_config_file: /opt/AdGuardHome/dnscrypt.yaml
···
重载AdGuardHome
shell> systemctl restart AdGuardHome
如果成功支持了DNSCrypt,那系统的TCP与UDP的5443端口将被AdGuardHome占用
# 查看AdGuardHome是否成功占用5443端口
shell> netstat -tlnpu | grep 5443
tcp6 0 0 :::5443 :::* LISTEN 29538/AdGuardHome
udp6 0 0 :::5443 :::* 29538/AdGuardHome
# 成功启用了DNSCrypt
在使用时,我们需要提供 IP地址及端口号
公钥
证书名称
这三个数据,从生成的 /opt/AdGuardHome/dnscrypt.yaml
文件中可以拿到这些信息,其中公钥为 public_key
内容,证书名称为 provider_name
。
接着,我们需要把这些信息打包成DNS Stamp格式,你可以在这里在线生成sdns链接,填入服务器IP和端口、公钥、证书名称即可,复制右端生成的sdns链接。
sdns://AQcAAAAAAAAAETQ3LjI0Mi4zMC42NTo1NDQzIDWN6nwgGwaNzYod_PYHyVH2-sIUis3GArF2qBCBizDRHDIuZG5zY3J5cHQtY2VydC5kbm9tZDM0My50b3A
DoQ多端口支持
AdGuardHome
的DoQ默认监听在UDP的784
端口,但有部分客户端默认端口为8853
,为了保障兼容性,需要同时兼容两个端口。
原理上,我们可以直接使用 iptables
内核模块改写UDP流量目标,将访问8853端口的UDP流量重定向到784端口,这个过程并不是用户层流量转发,因此 AdGuardHome
中记录的客户端IP地址不变(如果借助socat等工具来转发,客户端IP地址会变成 127.0.0.1
)。使用root权限执行以下命令:
# 添加重定向
shell> iptables -t nat -A PREROUTING -p udp --dport 8853 -j REDIRECT --to-port 784
# 查看添加结果
shell> iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
REDIRECT udp -- anywhere anywhere udp dpt:8853 redir ports 784
Chain INPUT (policy ACCEPT)
target prot opt source destination
···
# 删除重定向(此处1表示为第1行,应视具体情况修改)
shell> iptables -t nat -D PREROUTING 1
若服务器支持IPv6,需使用ip6tables命令多执行一次,以支持IPv6下的UDP
8853
端口查询
为了方便使用,可执行以下脚本自动添加:
if [ `whoami` = "root" ]; then
STATUS=$(iptables -t nat -L | grep 8853)
if [ -z "$STATUS" ]; then
iptables -t nat -A PREROUTING -p udp --dport 8853 -j REDIRECT --to-port 784
fi
fi
可将脚本保存到
/etc/profile.d/
目录下实现开机自启。
# 测试多端口是否正常
shell> dnslookup google.com quic://dns.dnomd343.top:784
···
shell> dnslookup google.com quic://dns.dnomd343.top:8853
···
# 两者均输出正确DNS响应即配置成功
测试服务
到这里已经将全部协议配置好了,我们可以通过AdGuardHome的端口占用情况来回顾各DNS查询协议。
# 列出AdGuardHome占用的全部TCP与UDP端口
shell> netstat -tlnpu | grep AdGuardHome
tcp6 0 0 :::853 :::* LISTEN 24295/AdGuardHome
tcp6 0 0 :::53 :::* LISTEN 24295/AdGuardHome
tcp6 0 0 :::3000 :::* LISTEN 24295/AdGuardHome
tcp6 0 0 :::3001 :::* LISTEN 24295/AdGuardHome
tcp6 0 0 :::5443 :::* LISTEN 24295/AdGuardHome
udp6 0 0 :::53 :::* 24295/AdGuardHome
udp6 0 0 :::784 :::* 24295/AdGuardHome
udp6 0 0 :::5443 :::* 24295/AdGuardHome
TCP/853
:DNS-over-TLSTCP/53
:常规DNSTCP/3000
:网页管理界面TCP/3001
:DNS-over-HTTPS,同时也是网页管理界面TCP/5443
:DNSCryptUDP/53
:常规DNSUDP/784
:DNS-over-QUICUDP/5443
:DNSCrypt
以上端口中,建议防火墙屏蔽 TCP/3000
和 TCP/3001
,借助Nginx反向代理,其他端口均放行。
最后,我们仍然借助dnslookup工具对建成的全协议DNS服务器进行测试。这里目标域名可以任意选择,因为查询过程都是加密链路,基本不受墙的DNS污染影响。
# DoH测试
shell> dnslookup google.com https://dns.dnomd343.top/dns-query
···
# DoT测试
shell> dnslookup google.com tls://dns.dnomd343.top
···
# DoQ测试
shell> dnslookup google.com quic://dns.dnomd343.top
···
# DNSCrypt测试
shell> dnslookup google.com sdns://AQcAAAAAAAAAETQ3LjI0Mi4zMC42NTo1NDQzIDWN6nwgGwaNzYod_PYHyVH2-sIUis3GArF2qBCBizDRHDIuZG5zY3J5cHQtY2VydC5kbm9tZDM0My50b3A
···
# 以上DNS接口已经停用,仅供示例
写在最后
可用性与普适性
正常情况下,大公司会在世界各地配置多台服务器来应对不同业务,如果直接使用自建的境外服务器做DNS解析,会把域名解析到国外的服务器上,由于国内直连相对较慢,将会导致网页访问卡顿。对于这种情况,可以配置EDNS来缓解这个问题,将来源IP地址一并加入请求中,从而正确地将域名解析到国内服务器上,不过它并不能完善地解决问题,上游DNS不一定支持扩展协议,也不一定就能返回最佳结果。因此,最佳的解决方案应该是建立分流器,将请求分为国内组与国外组,国内网站走国内公共DNS,国外网站走境外无污染DNS,这样就可以相对稳定地提供服务。前者我们使用阿里、腾讯等国内大型公共DNS服务器的DoH或DoT,免去运营商的劫持,而后者就只能靠我们自己的境外DNS服务器来实现,毕竟大部分国外DoH、DoT服务都被防火长城干扰或阻断。
基于上面的思路,不少人的第一反应是用国内服务器做分流,这个方法有诸多问题。首先就是国内三大运营商的网络之间通讯较慢,因此大型网站一般都会在各大运营商网络下设置多台服务器,通过配置DNS把不同网络的请求解析到对应服务器上,这将导致国内服务器统一请求的DNS不能兼容多个运营商,无法解析到最适合的服务器,虽然支持EDNS的服务可以在一定程度上修正这个问题,不过同样也不能确保解析结果的普适性。另一个方面则源自国内的管控,根据相关法律,普通人是不能在境内私自搭建DNS服务器的,没有备案的话就会收到服务商的警告与封锁,虽然搭建DoH服务可以绕过云服务商的监测,但是普适性就大大下降了。综合上述问题,使用境内服务器配置DNS分流并不是一个完善的解决方案。
更常规的做法是在本地路由器或内网设置一个DNS服务器,在其上配置完善的DNS分流机制,即使失去了移动设备外出时的可用性,但也没有上述公网服务器的问题。目前确实有很多基于这方面的实现,结合 DNSmasq
、SmartDNS
、ChinaDNS
等工具做分流与优选,同时加上去广告、强制重定向等功能,也有封装了完整配置的Docker镜像可以作为容器直接使用。
有效性与必要性
回到问题的本质上,我们要对抗的是DNS污染与劫持,即运营商的劫持和GFW的污染。解决前者很轻松,正常情况下不使用运营商分配的DNS就可以避免,而后者则要棘手得多,我们可以从有效性和必要性这两个方面来讨论。
要知道,GFW在屏蔽域名或IP时有多种手段,针对域名的封锁最基本也最方便的就是DNS污染,以极低的成本就能让普通用户无法正常访问,早期不少屏蔽都是针对DNS的,因而兴起了修改host或是基于加密DNS的抗污染工具,即使到现在,对于部分域名的屏蔽仍是最简单的DNS污染,这也是无污染DNS唯一能解决的封锁方式。而在针对连接方面,封锁IP可以粗暴地从网络层直接切断,我们对此无能为力,但针对域名的连接识别与阻断则稍微复杂。
在早期,大部分网站基于明文HTTP协议,GFW可以对HTTP请求头的host参数或内容敏感词进行匹配。而随着HTTPS的兴起,HTTP数据嵌套在SSL/TLS加密层内,防火长城无法窥探其中内容,但是它可以识别到访问目标IP与连接host,并以此屏蔽黑名单网站。一般情况下,GFW会对TLS连接的SNI进行嗅探,或者读取服务器返回的证书(当然前者成本更低),一旦识别成功就用中间人身份重置TCP连接,从而达到屏蔽作用。在TLS1.3中,新增的ESNI特性通过域前置方式,有效地加密了SNI内容,从而规避掉SNI嗅探,对于这种流量,GFW只有两个选择,不拦截或全部拦截,而显然的,防火长城选择了后者,直接暴力地屏蔽了所有带有ESNI的TLS1.3流量,具体的细节可以关注这篇文章。
所以,抗污染DNS服务在面对GFW的连接封锁方面是无力的,其上限也只是抵御一部分屏蔽,而上述TCP阻断等屏蔽措施都是在DNS层面无法解决的。不过,大多数情况下有总比没有强,况且还有为数不少的黑名单域名只享受到了DNS污染的待遇,GFW没有足够的资源或精力去阻断这些连接,因此自建DNS服务还是可以在一定程度上优化体验的。而对于科学上网的需求,则交给SS、SSR、Trojan、VMess、VLESS等更专业的协议去完成,同样的,它们可以把DNS请求转发到远端给海外服务器,算是一举两得,在绕过防火长城屏蔽的同时,也无需再考虑DNS污染问题。
因此,自建DNS的有效性是不可否认的,它确实能抵抗污染与劫持,绕过一部分DNS封锁,但是效果上并不是十全十美,这也就引出了必要性问题,在GFW面前,直接使用代理协议无疑是更为明智的选择,它们绕过了所有封锁的同时还加快了访问速度。不过再说回来,自建DNS也不是一无是处,至少在优化体验的同时能与代理协议互补,总体上还是值得的。