系统设计 / DNS · 层级、委任、缓存,以及不存在的「浸透」 / 名字空间与委任:规模问题的解法 待审核 1 / 11
HOSTS.TXT · 层级 · zone cut

名字空间与委任:规模问题的解法

ARPANET 早期把主机名与地址的对应关系放在一份叫 HOSTS.TXT 的文本文件里,由 SRI-NIC 集中维护,各主机定期取回本地(RFC 952 规定了它的格式与向 NIC 报送的方式,RFC 953 是配套的查询服务)。这套办法在主机数上千之后同时撞上三面墙:文件本身的分发消耗着当时稀缺的带宽;每一次改名都要经过一个集中机构的人工登记;而应用越来越需要一个通用的名字服务,而非各自解析一份静态文件。(「某个名字归谁」的裁决权集中在一个机构,是这三条之外的引申。)

1987 年的 RFC 1034 / 1035 换掉的正是这三件事,用的是同一个手段:把名字空间做成层级,再把每一层的管理权委任下去。

1 · 名字的层级与它的书写顺序相反

一个域名由若干 label(标签)拼成,书写时自左向右、以点分隔,而层级自右向左升高:mail.vega.dev. 最右那个空 label 是根,dev 在根之下一级,vega 再下一级。末尾那个点常被省略,但它不是可选的装饰——带点的是绝对名,不带点的在很多上下文里会被补上一个搜索域后缀。

图 1-1 · 域名的三种形态:presentation 文本、label 序列、wire format 字节数。可改输入观察层级标注与合法性判定,示例里含 a\\.b.dev. 这种转义情形。

这个反向关系是 DNS 的第一处认知门槛,但它是必然的:委任沿层级自上而下发生,而人读名字习惯从具体到笼统。两者方向相反,于是「dev 决定 vega.dev 归谁」这句话在书写形式上看起来是从右边管左边。

label 是字节串,不是字符串。协议层对内容几乎不作限制——\. 可以写进 label 内部、不可打印字节可以用 \DDD 转义——只有三条长度约束是硬的:单个 label 至多 63 字节,整个名字在 wire format 下至多 255 字节,中间不得出现零长 label。

注 · 常说的「域名只能用字母数字和连字符」是 RFC 952 给主机名定的规则(所谓 LDH,letter-digit-hyphen),不是 DNS 对 label 的限制。DNS 自己只搬字节,所以 _dmarc 这类下划线开头的名字合法且大量使用(SRV 与各种验证记录都靠它)。而非 ASCII 的名字要在全世界一致地解析,得先经 IDNA 转成 xn-- 形式的纯 ASCII——那一层在 DNS 之上,DNS 看到的仍然只是 ASCII 字节。

比较名字时不区分大小写,但这条规则也只定义在字节上:只有 ASCII 的 A–Z 与 a–z 互等(RFC 4343),不认任何 Unicode 大小写映射。实现里用 toLowerCase() 折算是个隐蔽的错误——土耳其语的 İ 经它会变成两个码位,名字的字节数随之改变。

2 · 委任与 zone

委任(delegation)是一次管理权的移交:父节点在自己的数据里放一组 NS 记录,声明某个子名字下面的一切由另一批服务器负责。被切出来的那个管理单元叫 zone。

定义 2.1(zone) zone 是名字空间中一棵连续的子树,其顶点由一条 SOA 记录标记,其边界由委任切出——即:从顶点向下的所有名字都属于本 zone,直到遇到一个带 NS 记录的名字为止,那个名字及其子树属于另一个 zone。这条边界称为 zone cut(切断点)。

「domain」与「zone」常被混用,但它们不是一回事:vega.dev 这个 domain 指它以下的整棵子树,而 vega.dev 这个 zone 只包含那棵子树里没被再次委任出去的部分。两者在没有下级委任时才相等。

图 2-1 · 四级 zone 与一次解析路径上经过的委任。可切换查询名观察权威落在哪个 zone,以及 lab.vega.dev 如何把子树从 vega.dev 手里切走。

委任可以发生在任意 label 边界,不限于 TLD 之下。图 2-1 里 lab.vega.dev 就是从 vega.dev 手上切出去的独立 zone——这一点在企业内部很常用:把 ap-northeast.corp.example 整棵子树交给区域团队自管,而 corp.example 只保留一条委任。

层级化解决集中登记问题的方式是把全球唯一性拆成了局部唯一性:每一级只需保证自己的直接子节点之间不重名,全局唯一就自动成立。裁决权也随之下移——vega.dev 归谁,dev 这一级说了算,根不参与。

3 · DNS 的三个部件

RFC 1034 把整套系统拆成三块,本系列各页也按这个划分展开:

名字空间与资源记录是数据模型:一棵树,每个节点上挂着若干条带类型的记录。下一页专讲它。

权威服务器(name server)持有一到多个 zone 的完整数据,并对落在这些 zone 内的查询给出带 AA 标志的应答。它不为别人递归,也不缓存——它就是数据的出处。

解析器(resolver)代客户端把名字变成答案。实践中它又分成两层:应用与操作系统里的那一小段叫 stub resolver,本身不会走委任链,只是把查询丢给一台配置好的服务器;真正干活的是全服务解析器(full-service resolver,也常被叫作递归服务器或缓存服务器),它从根出发沿委任链反复检索,并把沿途结果缓存下来。

警示 ·「DNS 服务器」这个说法在不同语境下指的是完全不同的两种东西,而它们的职责几乎不重叠:注册商管理面板里那个叫「DNS 服务器」的字段填的是权威服务器;系统网络设置或 DHCP 下发的那个「DNS 服务器」填的是全服务解析器。把前者填进后者的位置,得到的是一台拒绝递归的服务器;反过来则是一台对该域名毫无权威的服务器。排查时先分清在说哪一个。

4 · 一处实测

本页的 label 模型在写测试时被推翻过一次。原先的实现把 label 当 JS 字符串处理,于是 escapeLabelİ(U+0130)产出了 \304——而 \DDD 是字节转义,十进制只到 255,304 这个数根本不合法。改成先按 UTF-8 展开成字节再处理之后,同一个名字得到 \196\176,两个 label 字节;checkName 也随之算得准了:21 个汉字(每个 3 字节)刚好用满 63 字节的 label 上限,22 个就超。

这不是一处纯粹的转义 bug。它说明「域名是字符串」这个默认心智模型本身有问题:parseName('例.dev.')[0].length 在字符模型下是 1,在字节模型下是 3,而协议的长度上限只认后者。

5 · 参考文献

  1. Mockapetris, P. (1987). Domain Names — Concepts and Facilities (RFC 1034). IETF. §2.1 记录了 HOSTS.TXT 的三项失效理由(分发带宽随主机数平方增长、改动要等 NIC 落地、 各组织想要自己那一段名字空间的结构),§4.2 定义 zone 与委任。
  2. Mockapetris, P. (1987). Domain Names — Implementation and Specification (RFC 1035). IETF. §2.3.1 是 label 的语法,§2.3.4 是 63 / 255 两条上限,§5.1 是转义规则。
  3. Harrenstien, K., Stahl, M., & Feinler, E. (1985). DoD Internet Host Table Specification (RFC 952). IETF.(LDH 规则的出处,管的是主机名而非 DNS label。)
  4. Eastlake, D. (2006). Domain Name System (DNS) Case Insensitivity Clarification (RFC 4343). IETF.(大小写无关性只定义在 ASCII 字节上。)
  5. Hoffman, P., & Fujiwara, K. (2024). DNS Terminology (RFC 9499, BCP 219). IETF. §2 逐条厘清 zone / domain / authoritative server / resolver 的用法。
  6. 大谷亘. DNS の基礎の基礎. 慶應義塾大学 SFC. 本地副本Why DNS? 一节(p6–13)。本页的骨架取自该节:HOSTS.TXT 与 SRI-NIC 的集中分发(p7,并指向 RFC 952 / 953)、名字的层级书写(p8)、委任切出 zone(p9)、以及 RFC 1034 的三部件划分 (p10)。三条失效理由的具体内容出自条目 1 的 RFC 1034 §2.1,不在该节内。