写在前面

DNS over HTTPS(缩写:DoH)是域名系统的安全协议,以HTTPS协议完成DNS解析来保护网络主机的隐私,能避免传统DNS协议中用户的DNS解析请求被窃听或者修改(例如中间人攻击)的情况。

DoH可以防止ISP监听到你的DNS查询,减少查询结果被篡改的风险。
如果你并不是很在意自己的隐私问题,自建DoH其实是没有必要的,阿里和腾讯的公共DoH已经能够避免结果篡改了,而且速度绝对比自建的服务来得快 quyinniang_wuyu.png 但如果你希望能够把自己的DNS查询保密起来不让阿里和腾讯或者运营商发现,可以考虑按照下面的步骤自建一个DoH服务。

另,文末会附上一个一键脚本,想偷懒可以直接往下滑 eveonecat.gif


开始前先讲一下什么是EDNS,简单来讲EDNS可以根据你的IP返回最适合的解析结果。比如一个有CDN节点的网站,如果你A地用B地的服务器上的DoH服务,那么DNS结果返回的CDN节点IP是靠近B地的,你在A地的访问就会慢很多。如果开启了EDNS,那么你的IP会传递到B地的服务器,B地服务器再把你的IP所在网段传递到DNS上游,上游再把网段暴露给权威服务器,令其根据你的IP属地返回靠近A地的CDN节点IP,你的网站访问速度就会快很多。不然可能会出现在国内上哔哩哔哩,结果图片都卡到加载不出的情况,一查是DNS返回了美国的节点。别问我怎么知道的 quyinniang_die.png

EDNS有一个问题,就是会把你的IP网段暴露给上游服务器。尽管上游服务器只会得知你的IP所在网段,但终归是暴露了自己的IP地址。(对于直接使用公共DNS的情况,公共DNS的服务器可以直接得知你的确切IP,他们再负责把你的IP所在网段的信息暴露给上游服务器;而对于自建的DNS服务,把你的IP转化为网段的行为是发生在自己的服务器上的,隐私问题相对显得轻些)事实上,很多国外的DNS服务商就认为EDNS是一种侵犯用户隐私的东西,因此不支持EDNS(Cloudflare和DNS.SB就是这种情况),当然也有一些DNS声称自己有EDNS但实际上根本TMD没有,比如OpenDNS和Control D,折腾我老半天才发现的
但不开EDNS访问国内很多网站又会很慢,怎么办呢?可以考虑使用没有EDNS的DNS服务作为默认上游DNS服务器,而针对国内域名单独指定DNS服务器。这个方法在文章后面再讲,总之我们先部署一个带EDNS的DoH出来吧!


部署DoH

0.预备工作

将一个域名(example.com)绑定到你的服务器上
在本教程中,默认你的服务器上已经运行了一个Web服务器,并且支持反代。如果你的服务器并没有运行Web服务,443端口没有被占用,可以适当地修改后面的服务配置文件,让DNS Proxy直接监听443。不过SSL证书恐怕也得手动申请了。
对于运行有Web服务的服务器,使用acme.sh或其他自动化工具或者手动为example.com申请一个SSL证书。假设申请得到的ssl.key和ssl.crt都在root目录下。

1.安装DNS Proxy

VERSION=$(curl -s https://api.github.com/repos/AdguardTeam/dnsproxy/releases/latest | grep tag_name | cut -d '"' -f 4) && echo "Latest AdguardTeam dnsproxy version is $VERSION"
wget -O dnsproxy.tar.gz "https://github.com/AdguardTeam/dnsproxy/releases/download/${VERSION}/dnsproxy-linux-amd64-${VERSION}.tar.gz"
tar -xzvf dnsproxy.tar.gz
cd linux-amd64
mv dnsproxy /usr/bin/dnsproxy

2.配置服务

vi /etc/systemd/system/dnsproxy.service

写入:

[Unit]
Description=DNS Proxy
After=network.target
Requires=network.target

[Service]
Type=simple
ExecStart=/usr/bin/dnsproxy -l 127.0.0.1 -p 0 -u https://dns.google/dns-query -u https://unfiltered.adguard-dns.com/dns-query -u https://9.9.9.11/dns-query -b https://1.1.1.1/dns-query --https-port=444 --tls-crt=/root/ssl.crt --tls-key=/root/ssl.key --upstream-mode parallel --cache --edns
Restart=on-failure

[Install]
WantedBy=multi-user.target

解释一下上面的dnsproxy各个参数
-l 指定监听地址
-p 指定常规DNS服务要监听的TCP或UDP端口,此处设置为0,即不监听
-u 指定上游DNS服务器,我这里用到的是谷歌、AdGuard和Quad9的DNS,其中谷歌和AdGuard我都测试过是支持EDNS的,而且在海外的速度都还可以,Quad9的9.9.9.11官方声称是支持EDNS的,但我看了一下dnsproxy的日志,它怎么一直返回403啊 quyinniang_yiwen.png 我也不知道到底好不好使,反正在我服务器上返回速度好像一直没有前两个快...我测试下来有EDNS的国外公共DNS服务商只有这几个了,其他有的是不支持,有的是说支持实际上不支持。
-b 设置Bootstrap DNS,用来解析上游DNS服务器的域名,必须是纯IP的DNS,这里用了Cloudflare的DoH。
--http-ports DoH服务要监听的端口,这里指定为444,如果你的服务器443端口没有被占用可以考虑直接监听443
--tls-crt和--tls-key SSL的证书和密钥文件。这里假设都存放在root目录下
--upstream-mode parallel 采用并行请求,同时查询所有的上游服务器,返回最快的结果
--cache 开启DNS缓存
--edns 启用EDNS

然后启动服务

systemctl daemon-reload
systemctl enable --now dnsproxy

3.配置Web服务器

让Web服务器反代域名example.com到https://127.0.0.1:444,如果这个域名还有其他用处,可以仅针对访问/dns-query的时候反代。下面给一个kangle的配置示例,nginx或其他Web服务器可自行配置。
kangle的配置示例

在自己的设备上启用DoH

按照上述方式配置完成后,DoH地址为https://example.com/dns-query
对于Windows和Android,可以自行百度DoH设置方法,我自己是直接在两台设备上装了AdGuardHome然后设置DNS的,但对没有Root的手机没法这样做。
对于Linux,使用上文的DNS Proxy就可以连接,在安装DNS Proxy后添加相应的dnsproxy服务,但是需要修改服务文件中的启动命令ExecStart为

/usr/bin/dnsproxy -l 127.0.0.1 -p 53 -u https://example.com/dns-query -b https://1.1.1.1/dns-query

启动服务,然后修改系统本地的DNS

echo "nameserver 127.0.0.1" > /etc/resolv.conf

即可。
如果你希望在建立DoH的服务器上也直接使用这个DoH服务,在第二步配置服务的时候将-p参数设置为53,然后再修改本地的DNS即可。

改善网站速度和隐私性

虽然理论上采用EDNS可以做到让国内域名解析到国内IP,但在实践过程中我发现并不是这样的 quyinniang_tuxie.png CSDN、知网等等许多网站返回的IP还是国外节点,因此,最好的办法还是针对国内域名使用国内的DNS解析。此外,EDNS仍然存在隐私上的问题。如果可以将国内域名指定为国内DNS解析,并且将默认解析的DNS设置为不支持EDNS的Cloudflare、DNS.SB等服务商的DNS,那我们就能在速度和隐私之间取得平衡。
所幸,DNS Proxy允许针对特定域名设置特定的DNS,

You can specify upstreams that will be used for a specific domain(s). We use the dnsmasq-like syntax, decorating domains with brackets (see --server description).
Syntax: /[domain1/]upstreamString
Where upstreamString is one or many upstreams separated by space (e.g. 1.1.1.1 or 1.1.1.1 2.2.2.2).

我们可以通过项目dnsmasq-china-list来实现需求,该项目提供的accelerated-domains.china.conf包含了一个及时更新的国内域名列表,而且可以很方便地转换为DNS Proxy能够识别的格式,这里提供一个现成的转换脚本,运行后可以自动地生成list.txt,其中包含了要DNS Proxy默认使用的DNS和针对国内域名使用的DNS。如果你正在使用AdGuardHome,这个脚本生成的文件也是可以用的,将AdGuardHome配置文件中的upstream_dns_file设置为list.txt的路径即可。

#!/bin/bash
echo "https://1.1.1.1/dns-query
https://hk-hkg.doh.sb/dns-query
https://jp-nrt.doh.sb/dns-query" > /root/list.txt #这是默认解析国外域名用的DNS,可按照需要更改
curl -s https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf| awk -F'[=/]' '{print "[/" $3 "/]https://doh.pub/dns-query"}' >> /root/list.txt #针对国内域名使用腾讯的DNSPod解析
systemctl restart dnsproxy

我们把转换脚本存成一个文件update.sh,然后新建一个crontab任务,定时运行这个脚本

(echo "0 */3 * * * bash /root/update.sh" && crontab -l)|crontab

为了方便后续操作,我们先手动运行一下这个脚本,生成一个list.txt出来

bash /root/update.sh

DNS Proxy的-u选项支持直接指定一个包含所有上游服务器的文件,因此我们将上文的dnsproxy.service简单地修改一下

[Unit]
Description=DNS Proxy
After=network.target
Requires=network.target

[Service]
Type=simple
ExecStart=/usr/bin/dnsproxy -l 127.0.0.1 -p 0 -u /root/list.txt -b https://1.1.1.1/dns-query --https-port=444 --tls-crt=/root/ssl.crt --tls-key=/root/ssl.key --upstream-mode parallel --cache --edns
Restart=on-failure

[Install]
WantedBy=multi-user.target

最后重启服务

systemctl restart dnsproxy

大功告成!

防止主动探测

/dns-query是极其常见的DoH查询接口,为了规避主动探测,防止他人滥用,我们可以配合Web服务器或其他反代工具改写一下目录,从而实现使用其他路径来访问DoH的目的
这里同样附上一个kangle的配置示例,nginx或其他Web服务器可自行配置。
kangle的配置示例

其他相关方案

我后来发现了chinadns-ng,这个项目也可以很方便地实现国内域名走国内解析,国外域名走国外解析,不过看上去最好还是要搭配DNS Proxy或其他工具才能搭建出DoH服务,我懒得再折腾了 quyinniang_die.png ,有兴趣的朋友可以自己试试。

关于文章中提到的几个DNS的EDNS支持情况

我在文章中提到了好几个国内外的DNS,简单讲一下他们的使用体验和EDNS的支持情况,仅供参考
国内的:

  • 114.114.114.114 史
  • 阿里云DNS 虽然说是支持EDNS,但实际使用下来发现许多网站,例如CSDN和知网,似乎存在和国外那些支持EDNS的DNS一样的问题,所以可以看到我在update.sh里针对国内域名设置的DNS换成了腾讯的DNSPod
  • DNSPod 支持EDNS,目前用起来感觉还可以

国外的:

  • 谷歌DNS 支持EDNS,海外域名解析首选
  • Cloudflare 不支持EDNS,海外域名解析备选
  • DNS.SB 不支持EDNS,有针对不同地区的专用域名,速度其实还可以
  • AdGuard 支持EDNS,提供拦截广告和不拦截广告两种服务,文中使用的是后者
  • Quad9 提供支持和不支持EDNS的两种DNS,文中使用的是前者,在欧洲地区速度相对好一些
  • OpenDNS 官方声称是支持EDNS的,但实际上使用下来并没有返回合适的IP,可能是我用法不对?
  • Control D 同OpenDNS

关于海外域名的DNS选择,个人建议注重隐私优先考虑Cloudflare和DNS.SB,注重体验优先考虑谷歌。

关于DoH、DoT、DoQ

除了DoH(DNS over HTTPS)外,还有DoT(DNS over TLS)和DoQ(DNS over QUIC),都可以实现类似DoH的效果。
其中DoQ涉及到UDP,在国内网络质量可能不能保证,DoT相比DoH要快些,但流量特征明显,容易被封锁。DoH和普通的Web流量混在一起,因此相对好一些。
DNS Proxy可以直接配置DoT,设置--tls-port即可,具体可查看Github上的使用说明。

一键脚本

写了个一键脚本,理论支持CentOS, Fedora, Debian和Ubuntu,项目地址,欢迎反馈

wget -q https://raw.githubusercontent.com/funnycups/one-click-doh/main/install.sh && bash install.sh

该脚本默认针对国内域名使用DNSPod,国外域名使用Cloudflare和DNS.SB
如果希望修改具体使用的DNS,可以在安装后自行修改/home/dnsproxy/update.sh中的代码

参考

  1. GitHub - AdguardTeam/dnsproxy: Simple DNS proxy with DoH, DoT, DoQ and DNSCrypt support
  2. How to install DoH client on Linux - DNS.SB
  3. EDNS Client Subnet 协议简介
本文作者:小欢

本文链接:使用DNS Proxy自建DoH服务 - https://www.xh-ws.com/archives/self-build-doh.html

版权声明:如无特别声明,本文即为原创文章,仅代表个人观点,版权归 小欢博客 所有,遵循知识共享署名-相同方式共享 4.0 国际许可协议。转载请注明出处!