DNS 手册域名系统参考

DNS over QUIC(DoQ)

DoQ 把 DNS 直接放到 QUIC 上,中间不经过 HTTP。它继承了 QUIC 的几个好处——没有 TCP 的队头阻塞、握手和加密合并、连接可以跨网络迁移——同时保持了 DNS 报文本身的简单。

最后修订 · 约 4 分钟读完

基本参数#

项目规定
规范RFC 9250,2022 年 5 月
端口UDP 853(与 DoT 同号,但是 UDP)
ALPNdoq
加密QUIC 内置的 TLS 1.3
适用范围stub 到递归、递归到权威,以及区传送

报文怎样映射到 QUIC#

  • 一个查询一条流。客户端为每个查询新开一条双向流,发完查询就关闭自己这一侧的发送;服务器在同一条流上发回应答后关闭。流之间互相独立。
  • 2 字节长度前缀。每个报文前加 2 字节长度,用法与 DNS over TCP 的长度字段完全相同,实现可以复用现有的 TCP 代码路径。
  • 报文 ID 必须为 0。流本身已经完成了配对,ID 不再需要;固定为 0 也去掉了一个可以被观察的变化量。

“一个查询一条流”是 DoQ 相对 DoT 的关键改进。DoT 在一条 TCP 连接上复用多个查询,一旦某个 TCP 段丢失,后面所有查询的数据都要等它重传;QUIC 的丢包只影响它所在的那条流。

0-RTT 及其限制#

QUIC 支持 0-RTT:恢复会话时,客户端可以在第一个包里就带上数据。代价是 0-RTT 数据可以被攻击者截获后重放。RFC 9250 因此规定,0-RTT 里只能放可以安全重复执行的请求——OPCODE 为 QUERY 或 NOTIFY 的报文;动态更新这类会改变服务器状态的请求不得使用 0-RTT。服务器也可以完全拒绝 0-RTT。

普通查询被重放,代价通常只是服务器多做一次解析,但重放仍可能让观察者把两次连接关联起来。对隐私敏感的客户端可以选择不用 0-RTT,只用一个往返的会话恢复。

错误码#

DoQ 定义了自己的应用层错误码,用于关闭连接或重置流:

值名称含义
0x0DOQ_NO_ERROR正常关闭,没有错误
0x1DOQ_INTERNAL_ERROR实现内部出错
0x2DOQ_PROTOCOL_ERROR对端违反了协议,例如在同一条流上发了第二个查询
0x3DOQ_REQUEST_CANCELLED客户端取消了尚未完成的查询
0x4DOQ_EXCESSIVE_LOAD服务器负载过高,关闭连接
0x5DOQ_UNSPECIFIED_ERROR未细分的错误

注意:DNS 层面的失败(比如 SERVFAIL)仍然用正常的 DNS 应答表达,不使用这些错误码。

和 DoH over HTTP/3 有什么不同#

两者底层都是 QUIC,但定位不同:

  • DoQ 没有 HTTP 层,报文更小,实现更简单,适合解析器之间、解析器与权威之间这类“机器对机器”的通信。
  • DoQ 使用专用的 853/UDP 端口和专用 ALPN,很容易被识别和单独拦截;DoH over HTTP/3 在 443 端口上与普通网页流量没有区别。
  • 浏览器只实现了 DoH,没有实现 DoQ;DoQ 的客户端主要是专门的 DNS 工具、部分操作系统组件和第三方应用。

长度与填充#

和其他加密传输一样,DoQ 的密文长度会泄露信息。RFC 9250 §5.4 在这里比 DoT 的规范更严格:实现必须把查询和应答填充到少数几种固定长度,可以在 QUIC 层填充数据包,也可以用 EDNS(0) 的 Padding 选项(RFC 7830)。具体用多大的块,可以参考 RFC 8467 的方案。

区传送#

RFC 9250 也规定了在 DoQ 上做 AXFR 和 IXFR:整个区传送在一条流上完成,多个应答报文依次发出。它的思路与 RFC 9103 定义的 DNS over TLS 区传送(XoT)一致,目的是让主辅服务器之间的同步内容不再以明文跨越网络。

试一试#

较新的 kdig 支持 DoQ:

shell
$ kdig @dns.adguard-dns.com +quic example.com A

没有 DoQ 客户端时,也可以先确认 UDP 853 是否可达。很多网络对 UDP 的限制比 TCP 多,这往往是 DoQ 失败的第一原因:

shell
$ nc -vzu dns.adguard-dns.com 853

UDP 的“端口开放”测试并不可靠(没有回应不代表被拦截),结论要结合实际的 DoQ 握手结果判断。

DNS 手册 · 内容依据 IETF RFC 与各运营方公开资料整理,出现分歧时以原文为准。回到顶部