4. Specifications (规范)
4.1. Connection Establishment (连接建立)
DoQ 连接按照 QUIC 传输规范 [RFC9000] 中的描述建立. 在连接建立期间, 通过在加密握手中选择应用层协议协商 (Application-Layer Protocol Negotiation, ALPN) 令牌 "doq" 来指示 DoQ 支持.
4.1.1. Port Selection (端口选择)
默认情况下, 支持 DoQ 的 DNS 服务器必须 (MUST) 在专用 UDP 端口 853 (第 8 节) 上侦听并接受 QUIC 连接, 除非有相互协议使用另一个端口.
默认情况下, 希望与特定服务器使用 DoQ 的 DNS 客户端必须 (MUST) 建立到服务器 UDP 端口 853 的 QUIC 连接, 除非有相互协议使用另一个端口.
DoQ 连接禁止 (MUST NOT) 使用 UDP 端口 53. 不建议将端口 53 用于 DoQ 是为了避免 DoQ 与 DNS over UDP [RFC1035] 的使用之间的混淆. 即使双方同意使用端口 53, 混淆的风险仍然存在, 因为不知道该协议的其他方可能仍会尝试使用该端口.
在存根到递归场景中, 使用端口 443 作为相互协议的替代端口在操作上可能是有益的, 因为端口 443 被许多使用 QUIC 和 HTTP-3 的服务使用, 因此比其他端口更不容易被阻止. 存根发现提供加密传输的递归器 (包括使用自定义端口) 的几种机制正在进行中.
4.2. Stream Mapping and Usage (流映射和使用)
DNS 流量在 QUIC 流上的映射利用了 [RFC9000] (QUIC 传输规范) 第 2 节中详述的 QUIC 流特性.
DNS 查询/响应流量 [RFC1034] [RFC1035] 遵循一个简单的模式, 其中客户端发送查询, 服务器提供一个或多个响应 (多个响应可能发生在区域传输中).
此处指定的映射要求客户端为每个查询选择一个单独的 QUIC 流. 然后服务器使用相同的流为该查询提供所有响应消息. 为了解析多个响应, 使用 2 字节长度字段的方式与为 DNS over TCP [RFC1035] 定义的 2 字节长度字段完全相同. 这样做的实际结果是, 每个 QUIC 流的内容与恰好管理一个查询的 TCP 连接的内容完全相同.
通过 DoQ 连接发送的所有 DNS 消息 (查询和响应) 必须 (MUST) 编码为 2 字节长度字段, 后跟 [RFC1035] 中指定的消息内容.
客户端必须 (MUST) 为 QUIC 连接上的每个后续查询选择下一个可用的客户端发起的双向流, 符合 QUIC 传输规范 [RFC9000]. 数据包丢失和其他网络事件可能导致查询以不同的顺序到达. 服务器应该 (SHOULD) 在查询到达时处理它们, 因为不这样做会导致不必要的延迟.
客户端必须 (MUST) 通过选定的流发送 DNS 查询, 并且必须 (MUST) 通过 STREAM FIN 机制指示不会在该流上发送更多数据.
服务器必须 (MUST) 在同一流上发送响应, 并且必须 (MUST) 在最后一个响应之后通过 STREAM FIN 机制指示不会在该流上发送更多数据.
因此, 单个 DNS 事务消耗单个双向客户端发起的流. 这意味着客户端的第一个查询发生在 QUIC 流 0 上, 第二个在 4 上, 依此类推 (参见 [RFC9000] 的第 2.1 节).
服务器可以 (MAY) 推迟处理查询, 直到在客户端选择的流上指示 STREAM FIN.
服务器和客户端可以 (MAY) 监视 "悬空" 流的数量. 这些是在实现定义的超时后未发生以下事件的打开流:
-
未收到预期的查询或响应, 或
-
已收到预期的查询或响应但未收到 STREAM FIN
实现可以 (MAY) 对此类悬空流的数量施加限制. 如果遇到限制, 实现可以 (MAY) 关闭连接.
4.2.1. DNS Message IDs (DNS 消息 ID)
在通过 QUIC 连接发送查询时, DNS 消息 ID 必须 (MUST) 设置为 0. DoQ 的流映射允许查询和响应的明确关联, 因此不需要消息 ID 字段.
这对于在 DoQ 和其他传输之间代理 DoQ 消息有影响. 例如, 代理可能必须管理这样一个事实: DoQ 可以在单个连接上支持比 DNS over TCP 更多的未完成查询, 因为 DoQ 不受消息 ID 空间的限制. 这个问题已经存在于 DoH 中, 其中建议使用消息 ID 为 0.
当通过另一个传输转发来自 DoQ 的 DNS 消息时, 必须 (MUST) 根据正在使用的协议的规则生成 DNS 消息 ID. 当通过 DoQ 转发来自另一个传输的 DNS 消息时, 消息 ID 必须 (MUST) 设置为 0.
4.3. DoQ Error Codes (DoQ 错误代码)
定义了以下错误代码, 用于突然终止流时使用, 用作中止读取流时的应用协议错误代码, 或用于立即关闭连接:
DOQ_NO_ERROR (0x0): 无错误. 当需要关闭连接或流但没有错误要发出信号时使用.
DOQ_INTERNAL_ERROR (0x1): DoQ 实现遇到内部错误, 无法继续事务或连接.
DOQ_PROTOCOL_ERROR (0x2): DoQ 实现遇到协议错误并强制中止连接.
DOQ_REQUEST_CANCELLED (0x3): DoQ 客户端使用此代码来表示它想要取消未完成的事务.
DOQ_EXCESSIVE_LOAD (0x4): DoQ 实现使用此代码在由于负载过大而关闭连接时发出信号.
DOQ_UNSPECIFIED_ERROR (0x5): DoQ 实现在没有更具体的错误代码时使用此代码.
DOQ_ERROR_RESERVED (0xd098ea5e): 用于测试的替代错误代码.
有关注册新错误代码的详细信息, 请参见第 8.4 节.
4.3.1. Transaction Cancellation (事务取消)
在 QUIC 中, 发送 STOP_SENDING 请求对等方停止在流上传输. 如果 DoQ 客户端希望取消未完成的请求, 它必须 (MUST) 发出 QUIC STOP_SENDING, 并且应该 (SHOULD) 使用错误代码 DOQ_REQUEST_CANCELLED. 它可以 (MAY) 使用根据第 8.4 节注册的更具体的错误代码. STOP_SENDING 请求可以随时发送, 但如果服务器响应已经发送, 则不会产生任何效果, 在这种情况下, 客户端将简单地丢弃传入的响应. 必须 (MUST) 放弃相应的 DNS 事务.
接收 STOP_SENDING 的服务器按照 [RFC9000] 的第 3.5 节行事. 如果服务器收到 STOP_SENDING, 则不应该 (SHOULD NOT) 继续处理 DNS 事务.
服务器可以 (MAY) 对取消请求的总数或速率施加实现限制. 如果遇到限制, 服务器可以 (MAY) 关闭连接. 在这种情况下, 想要帮助客户端调试的服务器可以 (MAY) 使用错误代码 DOQ_EXCESSIVE_LOAD. 在帮助善意客户端调试问题和允许拒绝服务攻击者测试服务器防御之间总是存在权衡; 根据情况, 服务器很可能选择发送不同的错误代码.
请注意, 此机制提供了一种方式, 使辅助服务器可以取消在给定流上发生的单个区域传输, 而无需关闭 QUIC 连接.
如果服务器在客户端指示 STREAM FIN 之前从客户端收到 RESET_STREAM 请求, 则禁止 (MUST NOT) 继续处理 DNS 事务. 服务器必须 (MUST) 发出 RESET_STREAM 以指示事务被放弃, 除非:
-
它已经因其他原因这样做了, 或
-
它已经发送了响应并指示了 STREAM FIN.
4.3.2. Transaction Errors (事务错误)
服务器通常通过在事务的流上发送 DNS 响应来完成事务, 包括 DNS 响应指示 DNS 错误的情况. 例如, 应该 (SHOULD) 通过将响应代码设置为 SERVFAIL 的响应通知客户端服务器失败 (SERVFAIL, [RFC1035]).
如果服务器由于内部错误而无法发送 DNS 响应, 它应该 (SHOULD) 发出 QUIC RESET_STREAM 帧. 错误代码应该 (SHOULD) 设置为 DOQ_INTERNAL_ERROR. 必须 (MUST) 放弃相应的 DNS 事务. 客户端可以 (MAY) 限制在选择关闭连接之前在连接上接收的未经请求的 QUIC RESET_STREAM 帧的数量.
请注意, 此机制提供了一种方式, 使主服务器可以中止在给定流上发生的单个区域传输, 而无需关闭 QUIC 连接.
4.3.3. Protocol Errors (协议错误)
由于事务期间的格式错误、不完整或意外消息, 可能会发生其他错误场景. 这些包括 (但不限于):
-
客户端或服务器接收到具有非零消息 ID 的消息
-
客户端或服务器在接收 2 字节长度字段中指示的消息的所有字节之前接收到 STREAM FIN
-
客户端在接收所有预期响应之前接收到 STREAM FIN
-
服务器在流上接收多个查询
-
客户端在流上接收到的响应数量与预期不同 (例如, 对 A 记录查询的多个响应)
-
客户端接收 STOP_SENDING 请求
-
客户端或服务器在发送请求或响应后未指示预期的 STREAM FIN (参见第 4.2 节)
-
实现接收包含 edns-tcp-keepalive EDNS(0) 选项 [RFC7828] 的消息 (参见第 5.5.2 节)
-
客户端或服务器尝试打开单向 QUIC 流
-
服务器尝试打开服务器发起的双向 QUIC 流
-
服务器在 0-RTT 数据中接收 "可重放" 事务 (对于不愿意处理此情况的服务器, 参见第 4.5 节)
如果对等方遇到此类错误条件, 则将其视为致命错误. 它应该 (SHOULD) 使用 QUIC 的 CONNECTION_CLOSE 机制强制中止连接, 并且应该 (SHOULD) 使用 DoQ 错误代码 DOQ_PROTOCOL_ERROR. 在某些情况下, 它可以 (MAY) 改为静默放弃连接, 这使用较少的本地资源, 但使得在违规节点上的调试更加困难.
请注意, 对上述 EDNS(0) 选项使用的限制对于通过 DoQ 代理来自 TCP/DoT/DoH 的消息有影响.
4.3.4. Alternative Error Codes (替代错误代码)
本规范在第 4.3.1、4.3.2 和 4.3.3 节中描述了特定的错误代码. 这些错误代码旨在促进故障和其他事件的调查. 可以在 DoQ 的未来版本中定义新的错误代码, 或按照第 8.4 节中的规定进行注册.
因为可以在没有协商的情况下定义新的错误代码, 所以在意外上下文中使用错误代码或接收未知错误代码必须 (MUST) 被视为等同于 DOQ_UNSPECIFIED_ERROR.
实现可以 (MAY) 希望通过使用本文档中未列出的错误代码来测试对错误代码扩展机制的支持, 或者它们可以 (MAY) 使用 DOQ_ERROR_RESERVED.
4.4. Connection Management (连接管理)
[RFC9000] (QUIC 传输规范) 的第 10 节规定连接可以通过三种方式关闭:
-
空闲超时
-
立即关闭
-
无状态重置
实现 DoQ 的客户端和服务器应该 (SHOULD) 协商使用空闲超时. 在空闲超时时关闭无需任何数据包交换, 这最小化了协议开销. 根据 [RFC9000] (QUIC 传输规范) 的第 10.1 节, 空闲超时的有效值计算为两个端点通告的值的最小值. 关于设置空闲超时的实际考虑在第 5.5.2 节中讨论.
客户端应该 (SHOULD) 监视其与服务器的连接上产生的空闲时间, 定义为自从接收到来自服务器的最后一个数据包以来所花费的时间. 当客户端准备向服务器发送新的 DNS 查询时, 它应该 (SHOULD) 检查空闲时间是否充分低于空闲计时器. 如果是, 客户端应该 (SHOULD) 通过现有连接发送 DNS 查询. 如果不是, 客户端应该 (SHOULD) 建立新连接并通过该连接发送查询.
客户端可以 (MAY) 在空闲超时到期之前丢弃其与服务器的连接. 具有未完成查询的客户端应该 (SHOULD) 使用 QUIC 的 CONNECTION_CLOSE 机制和 DoQ 错误代码 DOQ_NO_ERROR 显式关闭连接.
客户端和服务器可以 (MAY) 出于各种其他原因关闭连接, 使用 QUIC 的 CONNECTION_CLOSE 指示. 通过其对等方丢弃的连接发送数据包的客户端和服务器可能会收到无状态重置指示. 如果连接失败, 则必须 (MUST) 放弃该连接上的所有正在进行的事务.
4.5. Session Resumption and 0-RTT (会话恢复和 0-RTT)
如果服务器支持, 客户端可以 (MAY) 利用 QUIC 传输 [RFC9000] 和 QUIC TLS [RFC9001] 支持的会话恢复和 0-RTT 机制. 客户端在决定使用此机制之前应该 (SHOULD) 考虑与会话恢复相关的潜在隐私问题, 并专门评估本文档各个部分中提出的权衡. 隐私问题在第 7.1 和 7.2 节中详细说明, 实现考虑在第 5.5.3 节中讨论.
0-RTT 机制禁止 (MUST NOT) 用于发送不是 "可重放" 事务的 DNS 请求. 在本规范中, 只有 OPCODE 为 QUERY 或 NOTIFY 的事务被认为是可重放的; 因此, 其他 OPCODE 禁止 (MUST NOT) 在 0-RTT 数据中发送. 有关为什么在此包含 NOTIFY 的详细讨论, 请参见附录 A.
服务器可以 (MAY) 支持会话恢复, 并且可以 (MAY) 在支持或不支持 0-RTT 的情况下这样做, 使用 [RFC9001] 第 4.6.1 节中描述的机制. 支持 0-RTT 的服务器禁止 (MUST NOT) 立即处理在 0-RTT 数据中接收的不可重放事务, 而必须 (MUST) 采用以下行为之一:
-
将违规事务排队, 并仅在 QUIC 握手完成后处理它, 如 [RFC9001] 的第 4.1.1 节中定义.
-
使用响应代码 REFUSED 和扩展 DNS 错误代码 (EDE) "Too Early" 回复违规事务, 使用 [RFC6891] 中定义的扩展 RCODE 机制和 [RFC8914] 中定义的扩展 DNS 错误; 参见第 8.3 节.
-
使用错误代码 DOQ_PROTOCOL_ERROR 关闭连接.
4.6. Message Sizes (消息大小)
DoQ 查询和响应在 QUIC 流上发送, 理论上可以承载最多 2^62 字节. 然而, DNS 消息在实践中被限制为最大 65535 字节. 此最大大小由 DNS over TCP [RFC1035] 和 DoT [RFC7858] 中使用的 2 字节消息长度字段以及 DoH [RFC8484] 的 "application/dns-message" 定义强制执行. DoQ 强制执行相同的限制.
DNS 扩展机制 (EDNS(0)) [RFC6891] 允许对等方指定 UDP 消息大小. DoQ 忽略此参数. DoQ 实现始终假设最大消息大小为 65535 字节.