DNS over QUIC(DoQ)
DoQ 把 DNS 直接放到 QUIC 上,中间不经过 HTTP。它继承了 QUIC 的几个好处——没有 TCP 的队头阻塞、握手和加密合并、连接可以跨网络迁移——同时保持了 DNS 报文本身的简单。
基本参数#
| 项目 | 规定 |
|---|---|
| 规范 | RFC 9250,2022 年 5 月 |
| 端口 | UDP 853(与 DoT 同号,但是 UDP) |
| ALPN | doq |
| 加密 | 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 定义了自己的应用层错误码,用于关闭连接或重置流:
| 值 | 名称 | 含义 |
|---|---|---|
| 0x0 | DOQ_NO_ERROR | 正常关闭,没有错误 |
| 0x1 | DOQ_INTERNAL_ERROR | 实现内部出错 |
| 0x2 | DOQ_PROTOCOL_ERROR | 对端违反了协议,例如在同一条流上发了第二个查询 |
| 0x3 | DOQ_REQUEST_CANCELLED | 客户端取消了尚未完成的查询 |
| 0x4 | DOQ_EXCESSIVE_LOAD | 服务器负载过高,关闭连接 |
| 0x5 | DOQ_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:
$ kdig @dns.adguard-dns.com +quic example.com A没有 DoQ 客户端时,也可以先确认 UDP 853 是否可达。很多网络对 UDP 的限制比 TCP 多,这往往是 DoQ 失败的第一原因:
$ nc -vzu dns.adguard-dns.com 853UDP 的“端口开放”测试并不可靠(没有回应不代表被拦截),结论要结合实际的 DoQ 握手结果判断。