【OPS.0x08】搭建安全可靠的 DNS over HTTPS

本文最后更新于:2026年1月8日 凌晨

《爱,死亡,袋鼠校园网》

0x00. 一切开始之前

注:本文 涉及 澳洲大学校园 的网络相关状况! !!不涉及也不适用任何澳洲境外的任何相关或无关场景!!!

众所周知笔者现在在南半球某个充满了袋鼠的国家的某所不知名大学读博 摆烂 当中(说实话呆了这么久,笔者的感受是 袋鼠真没有之前还在国内刷各种视频平台看到的那样有攻击性,大部分都还挺怕人的当然也有可能是因为笔者体积比较大看起来不好惹 ),而出于各种各样的目的(例如为了保护师生的身心健康),校园网中通常都会部署各种各样的硬件或软件的保护措施(例如防火墙等),从而阻止学校内的人员访问校方不想让他们访问的网站,例如笔者在 【OPS.0x02】如何在澳洲校园网内玩原神 这篇博客中就讲到一个例子便是校园网无法访问包括淘宝在内的一些网站:

正如笔者在此前的博客中说到的那样, 虽然说 在学校就该正经搞科研 ,但是笔者还是不太能忍受得了学校内各种网络封锁的存在 ,因此当时笔者的解决方案便是通过使用在云厂商购买的虚拟专用服务器来代理笔者对部分网站的访问流量,从而绕过校园网内对于这些网站的封锁

但随着时间的流逝笔者发现澳洲的网络又存在一个问题便是来自官方的 DNS 污染 ,众所周知我们访问网站的过程可以简单分为两步,第一步是向 DNS 服务器进行 DNS 请求以将域名解析成 IP,第二步才是向这个 IP 发起网络访问请求,而各大 ISP 默认分配给你的 DNS 服务器通常是他们自己的 DNS 服务器,这些 DNS 服务器会出于各种目的对部分网站给出错误的解析结果,例如对盗版网站的封锁:

——如何解决这个问题?一个比较简单的办法就是换用可信的公共 DNS 服务器,例如 Cloudflare 的 1.1.1.1 或是 Google 的 8.8.8.8 ,他们通常都会给你返回可信的 DNS 查询结果,但对于笔者的校园网环境而言,又存在一个问题便是 由于澳洲校园网有防火墙,笔者没有办法访问这些公共 DNS 服务器的 DNS 服务

——如何解决这个问题?我们不难想到的是可以使用一些更加安全的 DNS 服务,例如 DNS over TLS (DoT)DNS over HTTPS (DoH) ,通过加密信道去传递我们的 DNS 请求,这样在“攻击者”(指澳洲校园防火墙)看来他便不能知道我们是在进行 DNS 请求,也不知道我们具体在为访问哪个网站进行 DNS 请求,尤其是 DoH ,其流量特征对于防火墙这种中间人攻击而言便只是我们在正常地访问一个普通的网站,他是没有办法也不可能知道我们具体的流量内容是 DNS 请求的:

——但是这又存在一个问题,既然校园网能够封锁一些普通的网站,而他又要阻止我们访问公共 DNS 服务器, 那么他有什么理由不封锁这些公共 DoH 网站呢?

——不过话又说回来,对于普通的绝大部分网站的访问而言,澳洲校园网是不会进行封锁的,因此我们可以通过自行搭建 DoH 服务器的方式来为自己提供一个安全可靠的 DNS 服务器,从而绕过 DNS 封锁:

因此本篇博客主要讲述如何自行搭建一个可用的 DNS over HTTPS 服务:)

架构设计

我们的基本方案如下图所示:

简而言之便是客户端侧由 systemd-resolved 负责接受所有的 DNS 请求(更适用于 systemd-based 的系统),并转发给 dnscrypt-proxy (支持 DoH),dnscrypt-proxy 再向我们的 VPS 发起 DoH 请求,VPS 前端使用 NGINX 监听并建立一个网站进行伪装,并将 DoH 请求转发给本地的 coredns,由 coredns 负责向可信的公共 DNS 服务器进行请求

0x01. DoH 服务器配置

NGINX 配置

我们首先进行服务端的配置,由于 dnscrypt-proxy 的 DoH 默认使用 POST 方法,Content-Type 自动设置为 application/dns-message ,这是我们在服务端可以将 DoH 请求与普通 Web 访问请求进行区分的一个方法,因此接下来我们先在公网服务器搭建一个网站(怎么搭建就不用👴多说了8),然后修改 NGINX 相关配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 匹配咱们的参数并进行标识的变量
map "$request_method:$http_content_type:$uri" $proxy_flag {
default 0;
"POST:application/dns-message:/index.html" "doh";
}

server {
# ...
# 网页基本配置比如说 ssl 证书路径之类的,这里就直接省略了

location = /doh {
if ($proxy_flag != "doh") {
return 404;
}

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_http_version 1.1;
proxy_buffering off;

proxy_pass http://127.0.0.1:8053/dns-query;
}

location = /index.html {
root 你的网页文件夹路径;

if ($proxy_flag = "doh") {
rewrite ^ /doh last;
}

try_files /index.html =404;
}

# 你的一些其他网页配置
}

完成后重启 NGINX:

1
$ sudo systemctl restart nginx

CoreDNS 配置

然后是配置服务器的 DoH 后端,这里我们选择 CoreDNS ,其使用 Golang 进行编写,有着更好的性能表现以及高并发支持,这里我们使用 Docker 进行安装(因为这东西虽然火但不知道为什么没有进到各大发行版软件仓库),首先将镜像拉到本地:

1
$ docker pull coredns/coredns:latest

接下来编写配置文件(例如 /home/你的用户名/coredns/Corefile ,后面我们会将其映射进容器里),配置仅监听 127.0.0.1 的 8053 端口,并将 DNS 请求转发到大家所熟知的 Cloudflare 的 DNS 服务器 1.1.1.1

1
2
3
4
5
6
7
https://.:8053 {
bind 127.0.0.1
forward . 1.1.1.1
cache 30
log
errors
}

这里不使用 8.8.8.8 是因为 8.8.8.8部分解析结果不准 ,例如 dnslog.cn 使用 8.8.8.8 查询 居然得到 127.0.0.1如果查询结果失真的话那咱们搞这个 DoH 就没意义了 ,因此这里笔者使用 Cloudflare 的 1.1.1.1 服务器而非 Google 的 8.8.8.8

然后启动 docker,这里配置使用本地网络,不然会有一些奇奇怪怪的问题:

1
2
3
4
5
6
$ docker run -d \
--network=host \
--restart=always \
--name=coredns \
-v 你的Corefile配置文件路径:/Corefile \
coredns/coredns:latest

现在咱们的安全的 DNS 服务器就完成配置了:)

0x02. 客户端配置

接下来我们进行客户端的配置,不同的操作系统平台配置方式不同

Linux

在 Linux 下我们使用 dnscrypt-proxy + systemd-resolved 的配置方案

配置 dnscrypt-proxy

我们首先为本地回环 lo 添加一个新的本地 IP 127.0.0.2 ,我们的 dnscrypt-proxy 将监听这个 IP :

1
$ sudo ip addr add 127.0.0.2/8 dev lo

接下来安装 dnscrypt-proxy,笔者是 Gentoo Linux 系统:

1
$ sudo emerge -av net-dns/dnscrypt-proxy

接下来配置 dnscrypt-proxy 使用安全的 DNS over HTTPS ,修改配置文件 /etc/dnscrypt-proxy/dnscrypt-proxy.toml 对应项为如下内容,这里注意的一点是虽然 DoH 在 RFC8484 中定义的默认路径是 /dns-query ,但是对于中间人攻击而言这个路径对其是可见的,从而可能会阻断我们的 DoH 查询,因此这里用正常的 index.html 做伪装:

这里的 stamp 的生成可以使用 https://dnscrypt.info/stamps/ ,简而言之如图:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 自定义配置名,对应下面的 static.
server_names = ['mydoh']

# 监听的地址与端口
listen_addresses = ['127.0.0.2:53']

# 启动阶段的 DNS,负责解析域名对应的 IP,这里笔者只是惯例写了两个常见的公共 DNS,但你有可能需要额外添加上 ISP 自动分配给你的 DNS,因为就像笔者之前说的,公共 DNS 不一定可用,这种情况下你只能先依赖 ISP 默认分配的 DNS 进行这一阶段的域名解析
bootstrap_resolvers = ['223.5.5.5:53', '114.114.114.114:53']

# 具体配置
[static]
[static.mydoh]
stamp = "自己生成"

然后启用服务:

1
$ sudo systemctl enable --now dnscrypt-proxy.service

配置 systemd-resolved

接下来我们让 systemd-resolved 完全接管本地的 DNS 请求并建立转发到 dnscrypt-proxy ,首先将 /etc/resolv.conf 建立为一个到 /run/systemd/resolve/stub-resolv.conf 的符号链接:

1
$ sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

然后让 systemd-resolved 将 DNS 请求转发至 dnscrypt-proxy ,编辑 /etc/systemd/resolved.conf 配置如下内容:

1
2
3
4
5
6
7
8
9
10
11
[Resolve]
# 默认 DNS 服务器
DNS=127.0.0.2
# 在本地(默认 127.0.0.53:53)建立 DNS 服务器监听,并分发至指定的 DNS
DNSStubListener=yes
# 不回落到任何其他 DNS
FallbackDNS=
# 强制 resolved 忽略所有 link DNS
Domains=~.
# 不使用 DNSSEC,否则部分网站的解析结果有可能会出现失败
DNSSEC=no

然后重启 systemd-resolved (该服务是默认启用的):

1
$ sudo systemctl restart systemd-resolved.service

简单测试一下,我们使用 tcpdump 在服务端捕获 tcp 包,可以看到在我们进行 DNS 请求的过程当中服务端的 CoreDNS 确实接收到了包,在客户端也确实成功地获得了正确的 DNS 解析结果,至此我们成功完成了 DoH 的搭建与使用:

Windows(🕊🕊🕊)

Windows 下的可操作空间似乎是没有 Linux 那么多的,不过 dnscrypt-proxy 是一个跨平台软件,因此我们也可以使用类似的解决方案

配置 dnscrypt-proxy

首先前往官方的发布页面 https://github.com/DNSCrypt/dnscrypt-proxy/releases 下载 Windows 的包,这里笔者选择下载 MSI 包并安装到系统中:

dnscrypt-proxy 会被安装到 C:\Program Files\DNSCrypt 目录下,因此接下来我们来到这个目录下,其同时会提供一份 example-dnscrypt-proxy.toml ,我们将其复制并更名为 dnscrypt-proxy.toml ,然后按照和 Linux 一样的方法添加配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 自定义配置名,对应下面的 static.
server_names = ['mydoh']

# 监听的地址与端口
listen_addresses = ['127.0.0.1:53']

# 启动阶段的 DNS,负责解析域名对应的 IP,这里笔者只是惯例写了两个常见的公共 DNS,但你有可能需要额外添加上 ISP 自动分配给你的 DNS,因为就像笔者之前说的,公共 DNS 不一定可用,这种情况下你只能先依赖 ISP 默认分配的 DNS 进行这一阶段的域名解析
bootstrap_resolvers = ['223.5.5.5:53', '114.114.114.114:53']

# 具体配置
[static]
[static.mydoh]
stamp = "自己生成"

然后将 dnscrypt-proxy 注册为服务:

1
PS> 

【OPS.0x08】搭建安全可靠的 DNS over HTTPS
http://example.com/2025/08/25/OPS-0X08-SECURE_DNS/
作者
arttnba3
发布于
2025年8月25日
许可协议