7. Resolver Implementation (解析器实现)
RFC-1034中讨论了推荐的解析器算法的顶层。本节讨论实现细节, 假设本备忘录的名称服务器实现部分中建议的数据库结构。
7.1. Transforming a User Request into a Query (将用户请求转换为查询)
解析器采取的第一步是将客户端的请求 (以适合本地操作系统的格式声明) 转换为在特定名称处匹配特定QTYPE和QCLASS的RR的搜索规范。
查询规范指南
单一类型和类偏好: 在可能的情况下, QTYPE和QCLASS应该对应于单一类型和单一类, 因为这使得缓存数据的使用变得更加简单。
原因: 缓存中存在一种类型的数据并不能确认其他类型的数据的存在或不存在。因此, 确定的唯一方法是咨询权威源。
QCLASS=* 限制: 如果使用 QCLASS=*, 则权威答案将不可用。
请求状态信息
由于解析器必须能够多路复用多个请求 (如果它要有效地执行其功能), 因此每个挂起的请求通常在某个状态信息块中表示。
此状态块通常包含:
1. 时间戳 (Timestamp)
目的: 指示请求开始的时间。
用法:
- 时间戳用于决定数据库中的RR是否可以使用或已过时
- 此时间戳使用先前讨论的用于RR在区域和缓存中存储的绝对时间格式
TTL解释:
- 当RR的TTL指示相对时间时, RR必须及时, 因为它是区域的一部分
- 当RR具有绝对时间时, 它是缓存的一部分, 并且RR的TTL与请求开始的时间戳进行比较
时间戳的优势: 使用时间戳优于使用当前时间, 因为它允许TTL为零的RR以通常的方式输入缓存, 但仍被当前请求使用, 即使由于系统负载、查询重传超时等导致间隔许多秒之后。
2. 工作限制参数 (Work Limitation Parameters)
目的: 某种参数来限制将为此请求执行的工作量。
基本原理: 解析器响应客户端请求将执行的工作量必须受到限制, 以防止:
- 数据库中的错误, 例如循环CNAME引用
- 操作问题, 例如网络分区阻止解析器访问它需要的名称服务器
实现:
- 虽然解析器将特定查询重传到特定名称服务器地址的次数的本地限制是必不可少的
- 解析器应该有一个全局的每个请求计数器来限制单个请求的工作
- 计数器应设置为某个初始值, 并在解析器执行任何操作 (重传超时、重传等) 时递减
- 如果计数器通过零, 则请求以临时错误终止
并行请求: 请注意, 如果解析器结构允许一个请求并行启动其他请求, 例如当访问一个请求的名称服务器的需要导致名称服务器地址的并行解析时, 生成的请求应该以较低的计数器启动。这可以防止数据库中的循环引用启动解析器活动的链式反应。
3. SLIST 数据结构
RFC-1034中讨论的SLIST数据结构。
目的: 此结构跟踪请求的状态 (如果它必须等待来自外部名称服务器的答案)。
7.2. Sending the Queries (发送查询)
如RFC-1034中所述, 解析器的基本任务是制定一个查询, 该查询将回答客户端的请求, 并将该查询定向到可以提供信息的名称服务器。
查询制定挑战
解析器通常只会以NS RR的形式对要询问哪些服务器有非常强的提示, 并且可能必须:
- 修订查询, 以响应CNAME
- 修订解析器正在询问的名称服务器集, 以响应将解析器指向更接近所需信息的名称服务器的委派响应
除了客户端请求的信息之外, 解析器可能必须调用其自己的服务来确定它希望联系的名称服务器的地址。
解析器模型
本备忘录中使用的模型假设:
- 解析器在多个请求之间多路复用注意力, 一些来自客户端, 一些内部生成
- 每个请求由一些状态信息表示
- 期望的行为是解析器以一种方式向名称服务器传输查询:
- 最大化请求被回答的概率
- 最小化请求所需的时间
- 避免过度传输
关键算法
关键算法使用请求的状态信息来:
- 选择要查询的下一个名称服务器地址
- 计算超时, 如果响应未到达, 该超时将导致下一个操作
下一个操作通常是传输到其他服务器, 但可能是对客户端的临时错误。
SLIST 初始化
起点: 解析器始终从要查询的服务器名称列表 (SLIST) 开始。
初始内容:
- 此列表将是对应于解析器知道的最近祖先区域的所有NS RR
- 为了避免启动问题, 解析器应该有一组默认服务器, 如果它没有合适的当前NS RR, 它将询问这些服务器
地址添加: 然后, 解析器将所有已知的名称服务器地址添加到SLIST中, 并且当解析器有名称但没有名称服务器的地址时, 可能会启动并行请求以获取服务器的地址。
历史信息
为了完成SLIST的初始化, 解析器将它拥有的任何历史信息附加到SLIST中的每个地址。
典型的历史数据:
- 地址响应时间的某种加权平均值
- 地址的命中率 (即, 地址对请求响应的频率)
重要说明:
- 此信息应该保持在每个地址基础上, 而不是在每个名称服务器基础上, 因为特定服务器的响应时间和命中率可能因地址而异
- 此信息实际上特定于解析器地址/服务器地址对, 因此具有多个地址的解析器可能希望为其每个地址保留单独的历史记录
- 对于没有这样历史记录的地址: 预期的往返时间应该是5-10秒的最坏情况, 对于同一本地网络等的估计较低
委派处理: 请注意, 每当遵循委派时, 解析器算法都会重新初始化SLIST。
服务器选择和超时
排名: 该信息建立了可用名称服务器地址的部分排名。
选择策略: 每次选择地址时, 应该更改状态以防止再次选择它, 直到尝试所有其他地址。
超时计算: 每次传输的超时应该比平均预测值大50-100%, 以允许响应的方差。
特殊情况
引导问题:
- 解析器可能遇到这样的情况: SLIST中命名的任何名称服务器都没有可用的地址, 并且列表中的服务器正是通常用于查找其自己地址的服务器
- 这种情况通常发生在粘合地址RR的TTL小于标记委派的NS RR时, 或者当解析器缓存NS搜索的结果时
- 解析器应该检测到这种情况并在下一个祖先区域重新启动搜索, 或者在根处重新启动
服务器错误:
- 如果解析器从名称服务器获得服务器错误或其他奇怪的响应, 它应该将其从SLIST中删除
- 解析器可能希望立即调度到下一个候选服务器地址的传输
7.3. Processing Responses (处理响应)
处理到达的响应数据报的第一步是解析响应。
响应解析过程
此过程应包括:
1. 标头检查:
- 检查标头的合理性
- 当期望响应时丢弃查询的数据报
2. 部分解析:
- 解析消息的各个部分
- 确保所有RR的格式正确
3. TTL检查 (可选):
- 作为可选步骤, 检查到达数据的TTL, 查找具有过长TTL的RR
- 如果RR具有过长的TTL, 例如大于1周:
- 要么丢弃整个响应
- 要么将响应中的所有TTL限制为1周
响应匹配
下一步是将响应与当前解析器请求匹配。
推荐策略:
- 使用域标头中的ID字段进行初步匹配
- 然后验证问题部分对应于当前期望的信息
实现要求: 这要求传输算法将域ID字段的几个位专用于某种请求标识符。
特殊注意事项
源地址变化:
- 一些名称服务器从与用于接收查询的地址不同的地址发送其响应
- 也就是说, 解析器不能依赖响应将来自它发送相应查询的相同地址
- 这种名称服务器错误通常在UNIX系统中遇到
重传处理:
- 如果解析器将特定请求重传到名称服务器, 它应该能够使用来自任何传输的响应
- 但是, 如果它使用响应来采样访问名称服务器的往返时间:
- 它必须能够确定哪个传输与响应匹配 (并为每个传出消息保留传输时间)
- 或者仅基于初始传输计算往返时间
缺少区域数据:
- 名称服务器偶尔不会有它应该根据某些NS RR拥有的区域的当前副本
- 解析器应该简单地从当前SLIST中删除名称服务器, 并继续
7.4. Using the Cache (使用缓存)
一般来说, 我们期望解析器缓存它在响应中接收到的所有数据, 因为它可能在回答未来的客户端请求时很有用。
但是, 有几种类型的数据不应该被缓存:
不应缓存的数据
1. 不完整的集合:
- 当特定所有者名称有多个相同类型的RR可用时, 解析器应该全部缓存或全部不缓存
- 当响应被截断, 并且解析器不知道它是否具有完整的RR集时, 它不应该缓存可能不完整的RR集
2. 非权威数据优先于权威数据:
- 缓存数据永远不应该优先于权威数据使用
- 如果缓存会导致这种情况发生, 则不应该缓存数据
3. 反向查询结果:
- 反向查询的结果不应该被缓存
4. 通配符查询结果:
- 如果数据可能用于构造通配符, 则QNAME包含
*标签的标准查询的结果 - 原因: 缓存不一定包含限制通配符RR应用所必需的现有RR或区域边界信息
5. 可疑可靠性数据:
- 可疑可靠性响应中的RR数据
- 当解析器接收到未经请求的响应或除请求之外的RR数据时, 它应该丢弃它而不缓存它
- 基本含义: 在缓存任何数据包之前, 应该对数据包执行所有健全性检查
缓存更新策略
当解析器在响应中有某个名称的一组RR, 并且想要缓存RR时:
- 它应该检查其缓存中是否已经存在RR
- 根据情况, 响应中的数据或缓存中的数据是首选的
- 两者永远不应该合并
- 如果响应中的数据来自答案部分中的权威数据, 它总是首选的
Best Practices Summary (最佳实践摘要)
对于高效的解析器实现
-
尽可能使用单一类型和类查询以获得更好的缓存利用率
-
维护每个请求状态包括时间戳和工作计数器
-
保持每个地址的历史记录以进行智能服务器选择
-
实施适当的超时策略 (比预测时间大50-100%)
-
处理特殊情况如引导问题和服务器错误
-
仔细解析响应进行标头检查和格式验证
-
选择性缓存 - 并非所有数据都应该被缓存
-
当两者都可用时优先使用权威数据而不是缓存数据
相关: 有关服务器端详细信息, 请参阅 6. Name Server Implementation (名称服务器实现)