摘要

  • RFC 9520 要求解析失败至少缓存一秒,最长不得超过五分钟;命中缓存时停止相应的上游查询,但不能把失败解释成 NXDOMAIN、NODATA 或域名已经消失。
  • 只有当所有可用服务器和传输路径都未返回有用响应时,才成立“解析失败”。任何一条路径返回数据、下级区转介或有效的否定信息,都会推翻这个判断。
  • 可审计的失败记录必须保留路径集合、重试次数、合并的客户端请求、缓存键与作用域、退避、被阻止的父区/祖先查询、资源压力以及受控恢复探测。

一次故障,为何让健康的 .com 承担噪声

2021 年 10 月 4 日,一条原本用于评估骨干网容量的维护命令意外切断了 Facebook 数据中心之间的连接。Meta 的事故说明写得很清楚:权威 DNS 站点因为无法与数据中心通信,按健康策略撤回了 BGP 通告。DNS 服务器本身仍在运行,但互联网已经没有路径抵达它们。

权威区的沉默并没有停在自己的边界。Verisign 的测量显示,facebook.com、instagram.com 与 whatsapp.net 平时在 .com、.net 权威系统产生的查询总量约为每秒 7,000 次;在接近六小时的故障期间,峰值超过每秒 900,000 次。来自最活跃 Google 与 Cloudflare 递归解析来源的相关查询,分别一度达到平时约 7,000 倍和 2,000 倍。

父区的委派没有坏。反复向父区询问,无法让已经撤回的权威路由重新出现。然而,解析器的重试把一个网络的不可达,转换成了上层健康基础设施的负载。这使一个配置项成为明确的权责问题:没有答案时,谁有权决定停止询问?

RFC 9520给出的权力很窄。当所有可用路径都没有产生有用响应,解析器必须缓存这个解析失败;后续匹配同一失败的请求,在条目过期前不得再次触发相应的上游查询。缓存记录的是“本机暂时不再花费解析工作”,不是“这个名称不存在”。

NXDOMAIN 与 NODATA 是有内容的否定信息。解析失败恰恰表示没有得到足以判断数据是否存在的信息。前者可以在权威证据和 TTL 边界内支持 DNS 内容结论;后者最多支持一个本地、短期、可撤销的背压决定。两者都可能以“没拿到期望数据”出现在用户眼前,却绝不能共用同一个事实标签。

路径未尽,失败就没有成立

递归解析通常有多条路。存根客户端可以配置多个递归地址;迭代解析器面对一个区时,可能有多个 NS 名称,每个名称又有多个地址,同一地址还可能支持不同 DNS 传输。因此,一个地址超时不是完整的解析失败,一次 SERVFAIL、一次 REFUSED 或一次 TLS 握手失败也不是。

RFC 9520 把“有用响应”限定为三类:请求的数据、指向下级区的转介、或者说明该名称不存在相应数据的有效信息。只要任何可用服务器返回其中一种,解析过程就没有失败。于是,失败缓存的第一份证据不是错误码,而是路径清单:哪些服务器地址与传输符合条件,实际尝试了哪些,每次尝试何时开始、何时结束、最终收到什么。

重试可以发生,但受到硬边界约束。针对同一查询、同一服务器地址和同一传输,初次发送之后最多再重试两次,总计三次。若已知同一服务器还有另一种可用传输,且符合安全策略,解析器可以改走那条路径。RFC 没有规定统一超时,只指出常见实现大约落在三到三十秒之间。

没有统一超时并不意味着任意。路径时延、握手成本、任播落点和业务预算都不同,具体值应留给运行者决定;但运行者必须公开配置和结果。五秒超时与二十秒超时可以同时合理。只显示“服务器失败”、却隐藏路径集合、传输与计时,才是不完整的记录。

错误原因很多,缓存权限只有一种

SERVFAIL 是宽泛信号。权威服务器可能拿不到有效区数据;递归服务器可能已经耗尽上游尝试、DNSSEC 验证失败,或者遇到其他内部问题。REFUSED 往往涉及策略:服务器并不为该区提供权威回答,或递归服务拒绝访问控制列表之外的来源。超时表示在本地期限内没有响应;ICMP 不可达、TCP 拒绝连接、TLS 握手失败则可能更早终止一条传输路径。

委派循环与别名循环是另一类失败。两个区若互相依赖对方名下的服务器地址,缺乏循环检测的解析器可能制造接近 DDoS 规模的流量。CNAME 或 DNAME 链也可能绕回起点。DNSSEC 验证失败可能来自签名、DNSKEY、不存在性证明、时钟或信任链。FORMERR 有时反映报文或 EDNS 兼容问题。

实现可以把原因分别保存,但共同权限仍然只是:在有界时间内停止相应上游工作。解析器不能把 REFUSED 改写成 SERVFAIL,不能把验证失败的数据当成已验证内容,也不能因为本机没有成功就告诉客户名称不存在。原因必须保留,因为修复责任和恢复机制不同;保留原因不等于获得编造更强答案的权力。

扩展 DNS 错误 RFC 8914可以让日志和客户端看到 Cached Error、No Reachable Authority、DNSSEC Bogus 等更具体的背景。但 EDE 不改变普通 RCODE 的处理语义。它是解释,不是新的权威来源,也不是可自动信任的事实证明。

一组相同请求,只应对应一项上游工作

设想一个热门名称的全部权威地址都不再响应。在解析器等待超时的几秒内,数百个客户端可能提交完全相同的 QNAME、QTYPE 与 QCLASS。能“合并在途请求”的实现,会把这些客户挂到同一项上游解析事务;不能合并的实现,则可能为每个客户各发一次,再分别重试。

前者把需求留在本地边界,后者把客户端洪峰复制进 DNS 层级,还会乘以服务器地址、传输和重试次数。RFC 9520 引用的 2021 年DNS-OARC 僵尸网络实验提供了另一组尺度:测试域权威服务正常时,平均查询量约每秒 50 次;当所有权威服务器都返回 SERVFAIL 时,查询量约升至每秒 60,000 次,根和 TLD 即使委派未变也承受了更多查询。

合并还降低安全暴露。RFC 5452说明,多个等价在途查询会扩大生日攻击面,因为伪造响应只需命中其中任一事务。失败控制因此至少包含三项独立机制:合并相同需求;限制每条地址/传输路径的重试;缓存已经穷尽的失败。任何一项都不能替代另外两项。

它们的生效时点也不同。合并决定同时存在多少事务;重试上限决定每条路径承受多少工作;失败缓存决定未来相同需求在本地停留多久。一份可信的实现说明必须分别展示三者,而不是用“启用了负缓存”概括全部行为。

一秒下限、五分钟上限,以及恢复债务

RFC 9520 要求每个解析失败至少缓存一秒,最长不得超过五分钟。最小值应允许运营者配置;持续失败可采用线性或指数退避,但仍不得突破五分钟上限。

这些数字构成安全边界,不是统一最优值。时间越长,重复上游工作越少,但权威服务修复后,解析器再次尝试的时点也越晚。时间越短,瞬态故障恢复更快,却会在长期故障中制造更多负载。退避可以保护仍在生病的权威服务,也可能让最后一次失败在服务恢复后继续支配客户体验。

这就是“恢复债务”。每个条目都应保存缓存键、作用域、原因集合、插入时间、实际时长、退避级别、过期时间和配置版本。过期后,应只释放一次受控恢复探测,而不是把积压请求同时放行。第一条有用响应出现后,继续抑制就失去了本地依据。运营者要测量的是:服务具备恢复条件之后,客户又等待了多久。

缓存键如何设计同样重要。DNSSEC 验证失败可能按名称、类型、类别保存;地址不可达可能按服务器 IP 保存;一台服务器返回 SERVFAIL,未必足以封锁同一区所有服务器。RFC 2308在失败缓存仍属可选时,给出了具体的查询与服务器作用域。RFC 9520 将缓存改为强制,却允许实现根据失败类别设计结构。自由意味着实现者必须让运营者知道“一次失败究竟覆盖什么”。

父区不是重置按钮

当一个区的全部权威服务器都没有响应,历史上一些解析器会再次向父区索取委派信息。RFC 4697已经禁止在这种情况下激进重问父区 NS。RFC 9520 又把边界扩展到所有查询类型和所有祖先区:对失败区限制多少,也应相应限制向上层发出的重复查询。

这里体现的是清晰的权威分离。父区可以准确重复原有委派,却仍然无法修复子区的路由或服务器。父区的回答不是“继续重试”的命令。阻止祖先查询也不表示委派错误、过期或已经改变;它只表示在当前短暂失败窗口内,向上重复同一问题不会增加有用信息。

因此,解析器应统计被抑制的父区和祖先查询。这项指标既说明故障原本会向哪里转嫁,也证明背压是否覆盖了健康上层。过期后的受控探测仍能发现真正的委派变更或新地址。有界抑制保护父区;永久抑制才会遮蔽变化。

DNSSEC 失败必须缓存,因为失败数据的 TTL 不可信

RFC 4035原先允许安全感知解析器建立 BAD cache,记住重复验证失败。理由很现实:持续签名错误会让重复查询浪费大量资源,但未通过验证的数据不能提供可信 TTL,所以实现必须自行分配较短时长,并防止 BAD cache 成为拒绝服务放大器。

RFC 9520 把“可以缓存”加强为“必须缓存”。这并不允许解析器把无效 RRset 当成正确数据返回;它允许解析器记住“反复解析没有得到可接受数据”。

EDE 可以在此提供原因说明,例如告诉客户端这是缓存错误或验证结果为 bogus。但运营证据仍需包含信任链、失败时点、本地作用域和下次探测。一个错误名称不能替代具体签名、密钥或不存在性证明的记录。

Serve Stale 处理的是另一个问题

RFC 8767允许解析器在刷新失败时,依据明确策略返回已经过期的数据;它还建议对失败权威服务限制刷新频率,常用的失败复查间隔为三十秒。这一机制在缓存中仍有旧的可用数据时维持连续性。

RFC 9520 更普遍,也不同。解析器可能在查询一个从未见过的名称或类型,缓存中没有陈旧数据;运营者也可能关闭 Serve Stale。即便如此,它仍必须防止无限制造失败工作。陈旧答案表达的是:“这份数据过去有效,当前政策允许暂时复用。”失败缓存表达的是:“所有路径都没有给出有用信息,在本地时限结束前不重复同一工作。”

证据行必须同时标明两种状态。否则,一个收到旧答案的客户与一个收到错误的客户,会被统计成同一种“成功控制”。连续性与负载控制可以协同,却没有相同的事实含义。

用来遏制风暴的缓存,也可能遭到攻击

攻击者可以制造大量不同名称或类型,让它们全部失败,以消耗失败缓存的内存和处理时间。RFC 9520 因此要求实现限制资源。运营者应监测条目数、字节数、插入与逐出速率、按来源或区的集中度、随机标签增长,以及分类失败所耗 CPU。

还有第二种风险。失败消息不像已签名 DNS 数据那样可验证。若攻击者能伪造匹配的失败响应,解析器可能在退避窗口内停止查询真实权威服务器。五分钟上限使单次影响保持短暂,但重复伪造可以持续造成抑制。正确防线不是假装缓存中存有权威事实,而是严格限制时间,保留响应与传输证据,减少多个在途事务,并检查不同服务器和观测点之间是否出现不一致。

因此,失败缓存本质上是带有证据义务的断路器。没有上限,重试可能变成对权威与父区的分布式攻击;没有明确作用域与过期,抑制本身又可能成为拒绝服务。

一次可辩护失败需要的最小记录

逐项保存 QNAME、QTYPE、QCLASS、首次出现时间与合并请求组。列出区切、NS 集合、服务器地址、解析器观测点和每种传输。每次尝试都要有开始、结束、重试序号与原始结果,并明确是否有任何响应通过“有用响应”测试。

若全部路径失败,分别存储失败类别、RCODE、传输错误、DNSSEC 结果与 EDE;再记录缓存键、作用域、插入时点、配置时长、退避级别、过期和逐出优先级。还要记录阻止了哪些父区/祖先查询,是否存在陈旧数据,以及 Serve Stale 是否实际回答客户。

在全网层面,把客户进入量与合并组、权威出站量、祖先出站量放在同一张表中,计算放大比、时延、CPU、内存与缓存基数。条目过期后,标记恢复探测目标、首条有用响应和客户可见恢复延迟。不要把“缓存已过期”等同于“服务已经恢复”。

这套记录让决定保持本地、可逆。它不要求登记机构、标准组织或父区替一个解析器认证计时器;它要求解析器运营者证明为何停止工作,以及何时重新开始。

资料来源