NameSilo 的 NS 列表:别把名字当成顺序
本文目录
给自己的域名工具加「改 NS」命令,写解析的时候差点凭记忆猜返回形状。忍住了, 先打了一次真实 API 把响应打出来看——还好看了。
2026-09-06 修订:原文的示例已经按
position排好,不能证明数组乱序; 「主从关系反了」的说法也不准确。下面保留原示例,并补上明确标注的测试输入。
原文用来说明 getDomainInfo 的 nameservers 的片段是:
[
{ "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 本来就会怪。
是别凭记忆猜响应形状。猜错的代价不是报错,是一个安静跑着、结论全错的 解析器。打一次真实请求,把返回打印出来,成本三十秒。