NameSilo NS 列表别把名字当成顺序

本文目录

给自己的域名工具加「改 NS」命令,写解析的时候差点凭记忆猜返回形状。忍住了, 先打了一次真实 API 把响应打出来看——还好看了。

2026-09-06 修订:原文的示例已经按 position 排好,不能证明数组乱序; 「主从关系反了」的说法也不准确。下面保留原示例,并补上明确标注的测试输入。

原文用来说明 getDomainInfonameservers 的片段是:

[
  { "nameserver": "ns3.dnsowl.com", "position": 1 },
  { "nameserver": "ns2.dnsowl.com", "position": 2 },
  { "nameserver": "ns1.dnsowl.com", "position": 3 }
]

这里能看出来的是:数组第一个元素叫 ns3,它的 position 是 1。

名字里的 3 不等于数组里的第三项,也不能据此判断 DNS 的主从关系。 这段数组的 position 恰好就是 1、2、3,直接遍历和按 position 排序, 得到的都是 ns3, ns2, ns1。原文说它「顺序完全对不上」,是把两件事混在了一起。

DNS 的主从区别与区域数据的来源有关,不能从 NS 名称的数字后缀推断; 对于外部解析器,它们都是提供该区域答案的服务器。 这点可参见 RFC 2182 第 2、3 节

为什么这个错误特别难发现

难发现的是比较逻辑里的假设。单看「读 NS」,读到了三个名字,看起来就像成功了。

这个假设一旦进入回读验证,就会影响成败判断。改 NS 这种操作,我的规矩是 「成功以回读为准」:调完写接口,再读一次,比对是不是目标值。可要是验证逻辑 把列表顺序也算进相等条件,只是换了排列,就可能报「改失败了」——即使服务器 集合已经符合目标。

所以先说明白自己在验证什么。如果只是确认域名使用了哪几台 NS,就应该在 校验名称、统一大小写和末尾点之后比较集合。注册商 API 的回读也只说明其记录状态, 不能单凭这一项断言公网 DNS 已经全部更新。

一个用来判断成败的函数自己是错的,比功能本身有 bug 危险得多。

如果工具另有需求,要按 position 展示条目,则应明确使用这个字段。 下面是专门构造的乱序测试输入,不是一次真实 API 响应

raw = [
    {"nameserver": "ns1.example.com", "position": 3},
    {"nameserver": "ns3.example.com", "position": 1},
    {"nameserver": "ns2.example.com", "position": 2},
]

字段通过解析层校验后,排序这一步可以写成:

ordered = tuple(
    item["nameserver"]
    for item in sorted(raw, key=lambda item: item["position"])
)
# ("ns3.example.com", "ns2.example.com", "ns1.example.com")

它验证的是「是否按字段排序」。真实接口接受哪些形状、是否保证数组顺序, 仍要结合接口文档和保存的响应核对,不能从这个测试输入反推。

顺带一条:解析不出来的时候别返回空

同一个函数里还有个更隐蔽的决定:形状不认识时该怎么办。

返回空元组是最省事的写法,也是最危险的。因为回读验证的逻辑是 「读出来的 == 想要的?」,期望非空时,读到空就会误报不一致;如果期望值也 意外变成空,还可能误报成功。于是,没生效和读不懂,被压成了同一个结果

而这一步正是判断成败的唯一依据。所以解析不出来就抛错:

raise NameSiloError("解析不了 %s 的 nameservers 段:%.100r" % (where, raw))

宁可整条命令红着停下,也不要给出一个看起来能用、实则失去意义的答案。

真正的教训

不是「NameSilo 的 API 设计得怪」——第三方 API 本来就会怪。

别凭记忆猜响应形状。猜错的代价不是报错,是一个安静跑着、结论全错的 解析器。打一次真实请求,把返回打印出来,成本三十秒。

对这篇文章有想法?

来信聊聊 RSS 订阅