加密 DNS 主机名:dns.23-4.cn| DoH 地址:https://dns.23-4.cn/dns-query| 配置教程 | 实时战报
配完加密 DNS,多数人做的第一件事是 dig @dns.xxx example.com,然后看到 connection refused,就以为没配好或者服务挂了。
其实那一步从一开始就问错了对象。加密 DNS 只监听 443(DoH)和 853(DoT),明文 53 端口是故意关掉的。你用只走 53 的 dig 去问,得到的必然是拒绝——那不是故障,那正是它生效的证据之一。
这篇给一套按顺序执行的验证流程,四个步骤,每一步都能独立得出结论。下面所有输出都是我在一台 Ubuntu 20.04(dig 9.18.30)上实际跑出来的,域名用的是本站的 dns.23-4.cn,换成你的服务地址同样适用。
一、第 0 步:先确认端口开着哪几个
在怀疑配置之前,先确认网络层。这一步能把「服务没起」「防火墙挡了」「端口根本不监听」三种情况分开。
for p in 53 443 853 784; do
printf "port %-4s " "$p"
timeout 4 nc -z dns.23-4.cn "$p" >/dev/null 2>&1 && echo open || echo closed
done实测输出:
port 53 closed
port 443 open
port 853 open
port 784 closed四个端口的含义:
| 端口 | 用途 | 说明 |
|---|---|---|
| 53 | 明文 DNS(UDP/TCP) | 加密 DNS 一般不开放;开放反而说明没配好 |
| 443 | DoH(DNS over HTTPS) | 和普通 HTTPS 共用端口,防火墙最难拦 |
| 853 | DoT(DNS over TLS) | 独立端口,很多企业网会封它 |
| 784 | DoQ(DNS over QUIC) | 支持的服务很少,先别指望 |
没有 nc 的话,用 curl 的 telnet 模式也能测,效果一样:
timeout 4 curl -sS -v telnet://dns.23-4.cn:853 2>&1 | grep -E "Connected|refused"踩坑点:nc -z 在部分发行版(尤其是 BusyBox 的 nc)不支持 -z,会静默返回失败,让你误判成「端口关闭」。BusyBox 环境改用 nc -w 3 host port </dev/null 看退出码,或者直接用上面的 curl 写法。
二、第 1 步:dig +tls 走 DoT
DoT 是最容易验证的一条,因为 dig 原生支持。
dig +short +tls @dns.23-4.cn example.com实测输出:
104.20.23.154
172.66.147.243有 A 记录出来,就说明 TLS 握手成功、证书校验通过、查询也确实被加密服务应答了。
按 man dig,+tls 的语义是:
+tls, +notls
This option indicates whether to use DNS over TLS (DoT) when
querying name servers. When this option is in use, the port
number defaults to 853.端口默认 853,所以 @ 后面只写主机名,不要带端口。要换端口用 -p:
dig +short +tls @dns.23-4.cn -p 8853 example.com踩坑点:如果你的 dig 报 unknown option +tls 之类,说明版本太老。+tls / +https 是较新版本才有的选项,CentOS 7 自带的 9.11 就不认识。先 dig -v 看一眼,老版本要么升级,要么用 kdig(Knot DNS 的工具,DoT/DoH 支持更早)。
还有一点几乎所有人都会误读:dig +tls 默认不校验服务端证书。
这不是猜测,有干净的对照实验。这个服务的证书是 *.23-4.cn,用 IP 直接连必然对不上。先只用 +tls:
dig +short +tls @160.202.238.204 example.com实测——居然通了:
104.20.23.154
172.66.147.243再补上 +tls-ca(按 man dig:This option enables remote server TLS certificate validation):
dig +short +tls +tls-ca @160.202.238.204 example.com实测——这次失败了:
;; TLS peer certificate verification for 160.202.238.204#853 failed: IP address mismatch同一台服务器、同一个端口、同样的报文,只是多了一个 +tls-ca,结论就从「通」变成「证书不匹配」。
这说明:只加 +tls,你验证的是「链路被加密了」,不是「对方身份可信」。 两者不是一回事。做安全评估、或者怀疑被中间人劫持时,必须用 +tls-ca,否则你连自己是不是连到了一个伪造的同名服务都发现不了。
用正确的主机名时两者都通:
dig +short +tls +tls-ca @dns.23-4.cn example.com172.66.147.243
104.20.23.154三、第 2 步:dig +https 走 DoH
DoH 有三种写法,效果一样,但请求方式不同。
# A. 默认:POST,路径用服务端默认
dig +short +https @dns.23-4.cn example.com
# B. 显式指定 HTTP 路径(POST)
dig +short +https=/dns-query @dns.23-4.cn example.com
# C. 强制用 GET(报文塞在 ?dns= 里,可被中间层缓存)
dig +short +https-get=/dns-query @dns.23-4.cn example.com三种写法我都跑过,返回完全一致:
104.20.23.154
172.66.147.243手册原文对应关系:
+https[=value] DoH,端口默认 443,发送查询时用 **POST** 模式;
value 指定时,作为 HTTP endpoint 使用
+https-get[=value] 与 +https 相同,但用 **GET** 模式
+https-post[=value] 同 +https这里有个几乎人人都会踩的写法错误。 很多人会照着 DoH 地址直接写:
dig +short +https @https://dns.23-4.cn/dns-query example.com实测报错:
dig: couldn't get address for 'https://dns.23-4.cn/dns-query': not found原因是 @ 后面只接受主机名,dig 会拿它去做地址解析——把一个 URL 交给它,自然解析不出来。URL 要用 +https= 传,主机名单独放在 @ 后面。
踩坑点:GET 模式(+https-get)的报文进了 URL 的 query string,理论上会被中间代理缓存。查有敏感性的域名时用 POST,别用 GET。
补一句 +http-plain:手册里它是同族选项,走明文 HTTP、端口默认 80。别拿它当验证手段——我实测对本站跑出来是 end of file / no servers could be reached,因为 80 端口根本没有 DoH 服务。它存在只是为了调试反向代理,不是给你日常用的。看到有人推荐 +http-plain 来「测 DoH 通不通」,那是个误解:明文 HTTP 上跑的 DoH 已经没有任何加密意义,测通了也不能说明加密链路可用。
四、第 3 步:curl 直接看 HTTP 层
前两步验证的是「协议能用」。如果你还想确认 HTTP 层面到底返回了什么(状态码、Content-Type、报文长度),curl 是最直接的。
DoH 的协议很简单:把一个 DNS 查询报文(二进制)用 HTTP 发过去,服务端返回一个 DNS 应答报文(二进制)。
构造 example.com A 查询报文,标准做法是手工拼 header + question:
import base64, struct
def build_query(name, qtype=1):
# 12 字节 header:ID=0, flags=0x0100(RD=1), QDCOUNT=1, 其余 0
header = struct.pack('!HHHHHH', 0x0000, 0x0100, 1, 0, 0, 0)
q = b''
for label in name.split('.'):
q += bytes([len(label)]) + label.encode('ascii')
q += b'\x00' + struct.pack('!HH', qtype, 1) # QTYPE=A, QCLASS=IN
return header + q
q = build_query('example.com')
print(len(q), q.hex())
print(base64.urlsafe_b64encode(q).rstrip(b'=').decode())实测输出:
29 000001000001000000000000076578616d706c6503636f6d0000010001
AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAEAAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE 这串就是可以直接贴进 URL 的 base64url 形式(注意要去掉 = 填充、并把 +/ 换成 -_)。
拿它去请求:
curl -sS -o /dev/null -w 'code=%{http_code} type=%{content_type}\n' \
-H 'accept: application/dns-message' \
'https://dns.23-4.cn/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'实测:
code=200 type=application/dns-messagePOST 也一样:
python3 -c "import sys,struct;sys.stdout.buffer.write(struct.pack('!HHHHHH',0,0x0100,1,0,0,0)+b'\x07example\x03com\x00'+struct.pack('!HH',1,1))" \
| curl -sS -o /dev/null -w 'code=%{http_code} type=%{content_type}\n' \
-H 'content-type: application/dns-message' \
--data-binary @- \
https://dns.23-4.cn/dns-query实测:
code=200 type=application/dns-message三个必须对上的点:
Content-Type(请求)和Accept(响应)都必须是application/dns-message。写成application/json会直接 400。- GET 模式下参数名固定是
dns,值是 base64url(不是标准 base64)——+和/在 URL 里会被解释成别的意思。 - 响应体是二进制报文,不是 JSON。想看到解析结果得自己解码,或者用第 1、2 步的
dig。
五、四个常见误判,对照一下
| 你看到的 | 可能的真相 | 怎么确认 |
|---|---|---|
dig @host 报 connection refused | 明文 53 被故意关闭,正常 | 第 0 步查端口 |
dig +tls 能出结果但浏览器还是走明文 | 只是命令行验证通过,系统/浏览器还没配 | 查 /etc/resolv.conf 或系统 DNS 设置 |
curl 返回 400 | Content-Type 或报文格式不对 | 看响应体的错误文字 |
dig +https 报 couldn't get address | 把 URL 写在了 @ 后面 | 用 +https=/path 传路径 |
还有一类更隐蔽的:验证通了,但实际没生效。因为命令行工具用的是你显式指定的 @host,而系统其它程序用的是 resolv.conf 里的配置。这两条路径完全独立——dig 通了不代表浏览器在走加密 DNS。要确认系统层面,得看 resolv.conf、或者抓包看 53 端口有没有流量出去。
如果 +tls 或 +https 直接报错而不是返回记录,按这个顺序缩小范围:
- 报
unknown option→ dig 版本太老,换kdig或升级; - 报
connection refused→ 端口没开,回到第 0 步; - 报
network unreachable/ 超时 → 路径被拦。853 在不少企业网和公共 WiFi 上是被封的,这时改测 443(DoH)——它和普通 HTTPS 同端口,最难拦; - 报证书相关错误 → 你加了
+tls-ca但主机名和证书 SAN 对不上,检查@后面写的是不是证书覆盖的那个名字。
第 3 条值得单独说:DoT 被封、DoH 还能用是很常见的网络环境。这也是为什么公开服务通常两个都提供——不是重复建设,是给不同网络条件下留退路。
六、一页验证清单
# 0. 端口:443/853 应为 open,53 通常为 closed
for p in 53 443 853; do printf "%-4s " $p; timeout 4 nc -z dns.23-4.cn $p >/dev/null 2>&1 && echo open || echo closed; done
# 1. DoT
dig +short +tls @dns.23-4.cn example.com
# 2. DoH(POST)
dig +short +https @dns.23-4.cn example.com
# 3. DoH(GET)
dig +short +https-get=/dns-query @dns.23-4.cn example.com
# 4. HTTP 层:应为 200 + application/dns-message
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' \
-H 'accept: application/dns-message' \
'https://dns.23-4.cn/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'四步全过,说明服务端是好的。至于你这台机器实际有没有在用它,那是另一个问题——那要看 resolv.conf 和抓包,不是看 dig。