DNS 服务多协议连通性测试:从直连 UDP 到代理 DoH 的排障方案

2026-07-24

一件事测出四个结果

测试一个 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-resolvedstubbydnscrypt-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 查询时直接调用。

总结

这次测试的核心收获有五条:

  1. TCP 端口通 ≠ 应用层通 — 443 OK 但 DoH 失败,反复验证了这个原则
  2. DNS 排障要有协议矩阵思维 — 不要只测 dig @8.8.8.8,4 种协议都要走一遍
  3. 代理是排障利器 — 直连通不了的问题,走代理可能就通了
  4. 代理也有协议局限 — HTTP 代理只能代理 HTTP(S),UDP 53 的 DNS 它不管
  5. 先扫端口再动手 — 端口扫描几分钟的事,能省下大量试错时间

如果你的网络环境相似(云服务器、有代理、海外 DNS 服务),这些排查方法应该能帮你快速定位问题。