6. 连接处理 (Connection Handling)
6.1. 当前实践 (Current Practices)
[RFC1035] 第 4.2.2 节说:
-
服务器应假定客户端会发起连接关闭, 并且应延迟关闭其连接端, 直到所有未完成的客户端请求均已得到满足.
-
如果服务器需要关闭休眠连接以回收资源, 它应等到该连接空闲达到大约两分钟的时间量级. 特别是, 服务器应允许 SOA 和 AXFR 请求序列 (它会开始一次刷新操作) 在单条连接上完成. 由于服务器无论如何都无法回答查询, 可以使用单方面关闭或重置来代替优雅关闭.
其他更现代的协议 (例如 HTTP/1.1 [RFC7230], HTTP/2 [RFC7540]) 默认支持所有请求使用持久 TCP 连接. 随后, 连接通常通过某一方发出的 'connection close' 信号关闭.
[RFC1035] 中的描述清楚表明, 服务器应将连接视为持久连接 (尤其是在收到 SOA 之后), 但遗憾的是, 对于 SOA 之外的查询, 它没有提供足够细节来无歧义地解释客户端行为. 此外, DNS 尚无用于连接超时或关闭的信令机制, 尽管已经有人提出过一些机制.
6.1.1. 客户端 (Clients)
目前没有任何 RFC 明确指导 DNS 客户端应在何时关闭 TCP 连接, 也没有关于 DNS 客户端空闲超时的具体建议. 不过, 在撰写本文时, 客户端在发送单个请求后关闭 TCP 连接是常见实践 (SOA/AXFR 情况除外).
6.1.2. 服务器 (Servers)
许多 DNS 服务器实现使用较长的固定空闲超时, 并默认采用较少数量的 TCP 连接. 它们在 TCP 连接管理选项方面也提供得很少. 这种做法的缺点包括:
-
运行经验表明, 较长的服务器超时在重负载下很容易导致资源耗尽和响应不佳.
-
故意打开大量连接并让它们保持空闲, 可以轻易形成 TCP denial of service (DoS) 攻击, 因为许多 DNS 服务器并不善于通过修改其空闲超时或其他连接管理策略来防御这种攻击.
-
数量不多的一组客户端如果都并发尝试对这样的服务器使用具有非零空闲超时的持久连接, 也可能无意中造成同样的 DoS 问题.
注意, 这种 DoS 只发生在 TCP 服务上. 然而, 在这些情况下, 它不仅影响出于运行原因希望对查询使用 TCP 的客户端, 也影响所有在收到 TC=1 标志后选择从 UDP 回退到 TCP 的客户端.
6.2. 建议 (Recommendations)
以下各节包含的建议旨在形成更加一致且更具可扩展性的 DNS-over-TCP 实现.
6.2.1. 连接复用 (Connection Reuse)
DNS over TCP 的一个感知缺点是额外的连接建立时延, 通常等于一个 RTT. 为分摊连接建立成本, 客户端和服务器都 SHOULD 支持连接复用, 即在单条持久 TCP 连接上发送多个查询和响应.
在 TCP 连接上发送多个查询时, 为避免 Message ID 冲突, 客户端 MUST NOT 复用该连接上正在处理的查询的 DNS Message ID. 如果服务器可能执行乱序处理 (见 Section 7), 这一点尤其重要.
6.2.1.1. 查询流水线化 (Query Pipelining)
由于历史上 TCP 主要用于区域传送和截断响应, 现有 RFC 没有讨论过在 TCP 连接上流水线化 DNS 查询的概念.
为达到与 UDP 相当的性能, DNS 客户端 SHOULD 对其查询进行流水线化. 当 DNS 客户端向服务器发送多个查询时, 它 SHOULD NOT 在发送下一个查询前等待未完成的回复. 客户端在考虑何时发送某个特定查询时, SHOULD 等同对待 TCP 和 UDP.
为了提供 UDP 传输所可能达到的性能水平, DNS 服务器很可能需要并发处理流水线化查询, 并通过 TCP 发送乱序响应. 如果 TCP 性能很重要, 客户端可能会发现, 将服务器处理时间作为服务器和传输选择算法的输入会很有用.
DNS 服务器 (尤其是递归服务器) MUST 预期会收到流水线化查询. 服务器 SHOULD 并发处理 TCP 查询, 就像处理 UDP 查询一样. 服务器 SHOULD 回答所有流水线化查询, 即便它们是快速连续收到的. 对流水线化查询响应的处理见 Section 7.
6.2.2. 并发连接 (Concurrent Connections)
为降低无意造成服务器过载的风险, DNS 客户端 MUST 注意尽量减少到任一单独服务器的并发 TCP 连接数量. RECOMMENDED 的做法是, 对于任一给定的客户端/服务器交互, 常规查询 SHOULD 不超过一条连接, 区域传送 SHOULD 不超过一条连接, 并且在 TCP 之上使用的每种协议各 SHOULD 不超过一条连接 (例如解析器正在使用 TLS 时). 不过需要注意, 某些包含许多繁忙区域的主/辅配置可能出于运行原因需要为区域传送使用多条 TCP 连接 (例如, 为了支持多个区域的并发传送).
类似地, 服务器 MAY 对为任何特定客户端 IP 地址或子网处理的并发 TCP 连接数量施加限制. 这些限制 SHOULD 比上述客户端准则宽松得多, 因为服务器并不知道例如某个客户端 IP 地址是属于单个客户端, 是单台机器上的多个解析器, 还是位于执行 Network Address Translation (NAT) 的设备之后的多个客户端.
6.2.3. 空闲超时 (Idle Timeouts)
为降低无意造成服务器过载的风险, DNS 客户端 MUST 注意尽量减少到任一单独服务器的已建立 DNS-over-TCP 会话的空闲时间. DNS 客户端 SHOULD 关闭空闲会话的 TCP 连接, 除非已使用某种其他信令机制建立了空闲超时, 例如 [edns-tcp-keepalive].
为降低无意造成服务器过载的风险, RECOMMENDED 的做法是将默认服务器应用层空闲周期设为秒级, 但本文不指定具体值. 实践中, 空闲周期可以动态变化, 且服务器 MAY 在资源允许时允许空闲连接保持打开更长时间. 对于正常运行, 建议至少使用几秒的超时, 以支持那些期望 SOA 和 AXFR 请求序列按 [RFC1035] 最初规定在单条连接上完成的客户端. 服务器在承受重负载或遭受攻击时 MAY 使用零超时.
通过 TCP 递送的 DNS 报文可能分多个段到达. 如果 DNS 服务器在收到单个段后就重置其空闲超时, 它可能容易受到 "slow-read attack". 因此, 服务器 SHOULD 在收到完整 DNS 报文时重置空闲超时, 而不是在收到 DNS 报文的任意部分时重置.
6.2.4. 拆除 (Teardown)
在正常运行下, DNS 客户端通常会在空闲连接上发起连接关闭; 然而, 如果超过本地策略设置的空闲超时, DNS 服务器也可以关闭连接. 此外, 在防御攻击或系统故障/重启等异常条件下, 任一端都可以关闭连接.
如果连接在收到所有未完成响应之前关闭, DNS 客户端 SHOULD 重试尚未回答的查询. 本文档不指定具体重试算法.
如果 DNS 服务器发现 DNS 客户端在所有待发送响应发出之前已经关闭 TCP 会话 (或该会话以其他方式中断), 则服务器 MUST NOT 尝试发送这些响应. 当然, DNS 服务器 MAY 缓存这些响应.