一件事测出四个结果
测试一个 NextDNS 服务 c2aef5.dns.nextdns.io,目标是搞清楚它到底能不能用。
听起来很简单对吧——dig 一下就知道。但现代 DNS 服务同时支持多种传输协议,每种协议的可用性完全可能不同。同一个服务器,不同端口、不同加密方式,回来的结果天差地别。
我的测试环境是阿里云 ECS(10.0.0.x 段),需要通过公网访问这个 NextDNS 节点。以下是完整的排障过程。
四层协议矩阵
开始之前,先明确现代 DNS 的 4 种传输协议:
| 协议 | 端口 | 加密 | 特点 |
|---|---|---|---|
| 传统 DNS (UDP) | 53 | 明文 | 最老、最基础的 DNS 协议,无状态 |
| 传统 DNS (TCP) | 53 | 明文 | 大响应时退化到 TCP,个别场景强制 TCP |
| DNS-over-TLS (DoT) | 853 | 加密 | 专用端口,TLS 加密查询 |
| DNS-over-HTTPS (DoH) | 443 | 加密 | 复用 HTTPS 端口,可走代理,防窥探 |
大部分人只会测 UDP 53——dig @8.8.8.8。但如果你要配置一个加密 DNS 服务,只测 UDP 53 远远不够。
测试工具清单
基础 DNS 查询
# dig 查询 A 记录
dig @c2aef5.dns.nextdns.io google.com A +short
# host 命令
host google.com c2aef5.dns.nextdns.io
# nslookup
nslookup google.com c2aef5.dns.nextdns.io
端口连通性
# TCP 端口测试
timeout 3 bash -c 'echo >/dev/tcp/c2aef5.dns.nextdns.io/53' 2>&1 && echo "OK" || echo "FAIL"
# UDP 端口测试
timeout 3 nc -z -u c2aef5.dns.nextdns.io 53 2>&1 && echo "OK" || echo "FAIL"
# 批量扫描
timeout 5 bash -c 'for p in 53 443 853; do echo >/dev/tcp/$HOST/$p 2>/dev/null && echo "port $p: OPEN" || echo "port $p: closed"; done'
DoH 测试
# 直连
curl -s "https://c2aef5.dns.nextdns.io/dns-query?name=google.com&type=A" \
-H "accept: application/dns-json"
# 通过代理
curl -sx "http://192.168.31.146:7890" \
"https://c2aef5.dns.nextdns.io/dns-query?name=google.com&type=A" \
-H "accept: application/dns-json"
DoT 测试
系统上没有预装 kdig(knot-dnsutils),所以用 Python 手写一个 DoT 查询。核心代码只有几行,不依赖第三方库:
import socket, ssl, struct
def build_dns_query():
tid = 0x1234
flags = 0x0100
qdcount = 1
header = struct.pack('>HHHHHH', tid, flags, qdcount, 0, 0, 0)
qname = b'\x06google\x03com\x00'
qtype = 1 # A
qclass = 1 # IN
return header + qname + struct.pack('>HH', qtype, qclass)
host = 'c2aef5.dns.nextdns.io'
port = 853
sock = socket.create_connection((host, port), timeout=10)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ssock = ctx.wrap_socket(sock, server_hostname=host)
query = build_dns_query()
# DoT 需要在 TCP 数据前加 2 字节长度前缀
ssock.sendall(struct.pack('>H', len(query)) + query)
resp_len = ssock.recv(2)
length = struct.unpack('>H', resp_len)[0]
resp = ssock.recv(length)
关键区别:DoT 协议在 TCP 连接上先发送 2 字节长度前缀,再发送 DNS 查询包。而普通 DNS over TCP 没有这个前缀,直接发 DNS 包。
测试结果:意外连连
直连测试
| 协议 | 端口 | 结果 |
|---|---|---|
| DNS (UDP 53) | 53 | ❌ 超时 |
| DNS (TCP 53) | 53 | ❌ 连接被拒 |
| DoH (443) | 443 | ❌ TLS 握手失败 (exit 35) |
| DoT (853) | 853 | ✅ 成功解析 → 142.250.71.174 |
看到这个结果时有点意外——只有 DoT 853 直连可用。UDP 53 超时(运营商或机房屏蔽了出向 53 端口),TCP 53 被拒,443 端口虽然能建立 TCP 连接但 TLS 握手失败。
通过代理测试
既然有代理可用(飞牛 NAS 上跑着 Mihomo),测一下代理路径:
| 方式 | 结果 |
|---|---|
| HTTP 代理 (7890) → DoH | ✅ 成功 → 返回 6 个 A 记录 IP |
| SOCKS5 (1080) → DoH | ❌ 端口被拒 |
| 代理 → DNS 53 | ❌ 同样超时(代理只代理 HTTP(S)) |
有意思,代理路径下 DoH 工作正常。但 SOCKS5 端口 1080 不通。
顺便扫了一下代理的端口:
port 7890: OPEN ← HTTP 混合端口
port 7891: closed
port 7892: closed
port 1080: closed ← SOCKS5 未开启
port 1081: closed
port 1088: closed
Mihomo 默认配置只开启了 HTTP 混合端口(7890),SOCKS5 需要单独配置。
4 个踩坑
踩坑 1:443 能连 ≠ DoH 能用
TCP 端口开放只说明防火墙没封 443。DoH 需要额外走完 TLS 握手 + SNI 协商 + HTTP 应用层协议。我的环境里 TCP 443 能连,但 curl 报 exit 35(TLS 握手失败)——问题出在证书协商或中间设备干扰。
踩坑 2:别假设代理端口都有
测试前先扫描代理的端口分布,而不是一个个试。这次 SOCKS5 就是关闭的,如果直接从 SOCKS5 开始测会浪费大量时间。
踩坑 3:DoT 没有现成 CLI
kdig 是最方便的 DoT 工具,但大部分服务器不会预装。用 Python 手写 DoT 查询只需要 socket + ssl + struct 三个标准库,不需要 pip install 任何东西。注意 DoT 的 2 字节长度前缀与 DNS over TCP 的区别。
踩坑 4:同一个 DNS,不同协议,三种结果
这是最核心的发现——一个 DNS 服务器是否可用,答案取决于你用什么协议问它:
- 直连 53 端口 → ❌ 不通(运营商屏蔽出向 53)
- 直连 443 端口 → ❌ DoH 不通(TLS 握手被挡)
- 直连 853 端口 → ✅ DoT 正常(走不同处理路径)
- 代理 7890 → ✅ DoH 正常(代理绕过了 IP 限制)
三条解决路径
方案 A:直接配 DoT(最简单)
如果你的应用支持 DNS-over-TLS(如 systemd-resolved、stubby、dnscrypt-proxy),直接配置为:
c2aef5.dns.nextdns.io:853
这是测试中唯一直连可用的路径,不需要额外配置代理。
方案 B:通过代理走 DoH
curl -sx "http://192.168.31.146:7890" \
"https://dns.nextdns.io/c2aef5?name=google.com&type=A" \
-H "accept: application/dns-json"
如果用的是 Mihomo/Clash 类的代理客户端,在配置中直接将 DoH URL 设为 https://dns.nextdns.io/c2aef5,代理客户端内部会自动处理路由。
方案 C:Python 脚本集成
DoT 测试那节的 Python 脚本只有 ~20 行,不依赖第三方库,适合集成到自动化流程中。把它封装成一个函数,需要 DNS 查询时直接调用。
总结
这次测试的核心收获有五条:
- TCP 端口通 ≠ 应用层通 — 443 OK 但 DoH 失败,反复验证了这个原则
- DNS 排障要有协议矩阵思维 — 不要只测
dig @8.8.8.8,4 种协议都要走一遍 - 代理是排障利器 — 直连通不了的问题,走代理可能就通了
- 代理也有协议局限 — HTTP 代理只能代理 HTTP(S),UDP 53 的 DNS 它不管
- 先扫端口再动手 — 端口扫描几分钟的事,能省下大量试错时间
如果你的网络环境相似(云服务器、有代理、海外 DNS 服务),这些排查方法应该能帮你快速定位问题。