摘要

  • 向同一个 DNS 地址连续发出两次查询,不保证由同一个服务实例回答。NSID 把诊断标识附在当次回答中,减少事后身份查询与原始故障之间的错配。
  • NSID 只询问当前 DNS 对端。递归解析器可以另行向上游索取标识,但不会因此替客户端返回一条权威服务器来源链。
  • 标识内容由运营者决定,协议只要求把不透明字节可靠地传回来。它可以帮助查找实例,却不自动证明身份、位置、数据真实性或完整的递归过程。

那台正常的服务器,未必发过异常回答

设想一次域名查询返回了不合预期的结果。排查者立即向同一个 IP 地址再发一问,要求服务器报出自己的名字。第二份回答看起来很有帮助:名字清楚,机器运行正常,日志里却找不到刚才的问题。

这不一定是日志丢失,也不必先假定有人隐瞒。第一次查询与第二次查询,可能根本没有到达同一个实例。路由发生变化,或者负载均衡器为两份报文选择了不同后端,便足以让“刚才是谁回答的”变成“现在是谁回答我的”。这是一个可能发生的诊断情形,不是一宗被本篇实测确认的事故。

2007 年 6 月的 RFC 4892 把这种相关性缺口写进了需求。多个 DNS 查询即便发往相同地址,也可能落在不同服务器上。换用 ICMP、TCP 或其他 UDP 探测,更不能保证碰到处理原查询的那台设备。稳定的服务地址,已经不足以独自承担实例身份的工作。

地址负责把请求送到服务,不负责记住谁接待

2006 年 12 月的 RFC 4786 讨论任播服务时,描述的是一组稳定服务地址,由多个独立节点向路由系统宣告可达性。客户端不必先取得每个实例的私有地址,就能访问共同服务;实际到达哪个节点,则受路由选择影响。

不能把这个选择简单说成总会到地理上最近的机器,也不能说任播必然逐包切换。关键只在于:共同地址不等于唯一实例。即使大多数时候路径稳定,诊断也不能把这份稳定当作永远成立的保证。

RFC 4786 的节点识别章节指出,traceroute 有时能帮助判断,但等工具运行时,网络条件可能已经不同。它建议把识别能力放进服务协议,并在监测中将实例标识与性能、可用性一起记录。该文提到的是当时正在形成的 NSID 工作;不能把 2007 年正式规范的发布日期提前到 2006 年。

原来的“报名字”办法并非毫无价值

早期惯例使用 CHAOS 类的 TXT 查询。向 HOSTNAME.BIND. 发问,可以得到管理员配置的服务器标识;ID.SERVER. 则去掉了前一种写法中的实现名称。它们仍然在 DNS 协议里,容易配置,也让运营者控制披露什么信息。

这些优点不该因新机制出现而被抹去。用 DNS 查询识别 DNS 服务,通常比要求额外开放另一种探测通道更贴近实际请求。但这种惯例需要额外一问,而额外一问无法可靠地绑定到之前那份回答。它回答了一个有用的问题,却不一定是排查者真正想问的问题。

RFC 4892 因而要求一种实现中立的方式:标识请求能放入普通业务查询,不需要为了诊断另建专用命名空间或支持另一个类别;管理员能启用、关闭或限制披露,也不必交出维护用主机名和单播地址。需求是在保留可诊断性的同时,缩短证据与事件之间的距离。

空请求与随答返回的字节

R. Austein 在 2007 年 8 月的 RFC 5001 中定义 DNS Name Server Identifier,简称 NSID。请求者把一个空的 NSID 选项放进查询的 EDNS OPT 伪资源记录。选项编号是 3;请求里不能携带 NSID 数据。即使请求者放了数据,服务器也必须忽略它。

这个细节说明 NSID 不是回显挑战。客户端并未提交一个标识,要求服务器照抄;也没有指定“请让某台实例回答我”。它只是向实际收到查询的对端提出请求:如果你支持并愿意披露,请在这次回答中附上你的标识。

响应仍是可选的。支持 NSID 的服务器可以不提供;客户端没有请求时,服务器不得擅自附带。缺少 NSID 既不证明没有服务器实例,也不证明没有任播,更不必然使普通 DNS 回答失效。支持、配置、请求与返回,是四个需要分别核对的状态。

今天的 IANA DNS 参数登记表 仍把 EDNS 选项 3 登记为 NSID。统一的是这个共同入口,而不是每家运营者填进去的名字。注册表没有为全网逐台服务器签发身份号码,也没有规定标识必须包含机房、城市或维护地址。

递归解析器报的是自己,不是上游的履历

NSID 的“逐跳”容易被误读成沿路每个 IP 路由器都留下记录。这里的跳,是一次 DNS 交换的两个端点。客户端向递归解析器发出 NSID 请求,问的是该解析器自己的实例标识。

递归解析器作为请求者访问权威服务器时,可以独立选择索取上游 NSID;那是另一份查询与另一份回答。上游返回了标识,不会自动使原客户端得到它,也不会把递归过程变成一串可转交的来源证明。RFC 5001 明确将这种非传递性写入双方行为。

2013 年的 RFC 6891 对 EDNS 作了后续整理:OPT 描述特定问答事务的控制信息,不是 DNS 区域数据,不能作为普通记录缓存、转发或写入主文件。结合 NSID 的范围,可以得出一个实用结论:解析器可能从缓存取出域名记录,但若本次返回自己的 NSID,它标识的是眼前作答的解析器,不是当初获取那批缓存内容的权威实例。

这项限制并不是缺少一段方便功能。若把上游标识沿递归链照搬,读者就很难判断标识属于哪个时间、哪份回答、哪个缓存状态。协议选择回答一个小而明确的问题,而不把诊断字段扩大为它没有定义的历史档案。

看不懂标识,也必须能原样交回

NSID 的内容是一串不透明字节。它可以恰好像文字,也可以只对运营者的内部映射有意义。标准不要求用户先理解这串内容,才能把它交给有能力定位实例的人。

因此,RFC 5001 对显示与复制反而十分具体:以十六进制读写,每个字节对应两个数字;比较应当比较原始二进制内容;复制不能假定数据以零字节结尾。开头的零、内部的零以及任何看起来不像普通文本的字节,都不能因为界面追求整洁而消失。

这与把标识解释成“服务器名称”有很大差别。工具不能擅自将载荷当作 DNS 名称,不应对解码后的内容做大小写或 Unicode 规范化。十六进制字母的显示大小写可以不同,只要解码后仍是同一组字节;可读文本预览却不能取代精确证据。

这里有一种朴素的分工:用户负责保存并转交,运营者负责解释。让不理解内部编码的人也能把完整标识贴进工单,比强迫所有运营者公开一套可读的全球命名规则更有用。美观不是无损传递的替代品。

不透明不是自动保密,更不是自动认证

运营者可以选择真实主机名、维护地址、随机标识或其他编码。各有代价:暴露维护单播地址,可能抵销任播隐藏个别节点的部分好处;简单散列一个 IPv4 地址,输入空间仍只有 32 位;使用随机标识,又需要保存映射和连续性。

如果所有节点都配置成同一个值,格式完全正确,诊断区分能力却消失了。若标识会轮换,两个不同值也未必代表机器迁移或路由变化。相等或不同应当放在运营者已知的分配规则下解释,不能直接升级为物理设备的永久身份证明。

RFC 5001 还讨论签名或加密载荷,但没有为它们提供完整安全方案。静态数据可能被重放;加密后的内容对没有密钥的人也可能只是另一个无法解释的常量。该文把 NSID 归为通道信令,指出它不在 DNSSEC 本身的保护范围内,需要完整性保护时应使用相应的通道机制,例如文中提及的 TSIG。这是边界说明,不是说每次 NSID 回答已经得到认证。

实现把两种选择分开了

本次核对的 BIND 配置文档 标注版本为 9.20.27。它把对外报告的 server-id 与向上游索取标识的 request-nsid 分别配置。前者默认是 none,可以选择使用主机名;后者默认关闭,启用后在迭代查询中发送空 NSID 请求,并把上游返回值记入对应日志类别。

这不是一个开关控制整个递归链。自己愿不愿意披露,与自己是否向对端收集诊断材料,是两个本地决定。BIND 工具手册 也记录了 dig +nsid,其作用是随查询加入请求,而不是迫使服务器回答,更不是自动验证载荷真实性。文档说明存在实现路径,不能证明某个生产站点已经启用它。

额外字节还可能让回答触及截断边界。NSID 不改变 DNS 截断规则,也不要求为了可选标识而必须截断正常回答。诊断材料需要与实际响应大小一起考虑,却不应夺走正常业务结果的优先位置。

NSID 让一个共同地址背后的实例更容易被找到,靠的不是宣布谁拥有全局解释权,而是把应当一起出现的证据放在一起。一份回答,可以带上解释这份回答所需的线索;这并不要求它同时证明整个网络的来历。