资源记录与 zone 里的四类数据
zone 里的数据全部是同一种东西:资源记录(resource record,简称 RR)。一棵树,每个节点上挂着若干条带类型的记录,就是 DNS 的全部数据模型。
1 · 一条记录的五段
mail.vega.dev. 3600 IN A 203.0.113.40
└─ owner ────┘ └TTL┘ └class┘ └type┘ └─ RDATA ──┘
owner 是这条记录挂在哪个名字上。TTL 是缓存方可以留存它多少秒。class 几乎总是 IN(Internet)——CH 与 HS 是历史遗留,CH 至今仍有一处实用价值:许多权威服务器实现用 version.bind CH TXT 报告自己的版本号。type 决定 RDATA 怎么解读。
写在 zone file 里时,这五段有一套省略规则:owner 省略即继承上一条(缩进在此有语义)、TTL 省略即取 $TTL、class 与 TTL 的先后可以互换、相对名会补上 $ORIGIN、@ 代表
$ORIGIN 自身、括号之间可以跨行。这些省略叠起来,使得同一份数据的两种写法在字面上可以毫无共同点。
2 · 缓存与签名的单位是 RRset
owner、class、type 三者相同的一组 RR 合称一个 RRset。它不是一个方便的说法,而是协议里的实际单位:缓存以 RRset 为单位存取,DNSSEC 以 RRset 为单位签名,应答里也不允许只给一个 RRset 的一部分。
由此推出一条硬约束:
定理 2.1 同一个 RRset 内所有 RR 的 TTL 必须相同(RFC 2181 §5.2)。
证明 反设 RRset 内有两条 TTL 不同的 RR。缓存方按 RRset 整体存取,到期时也整体丢弃,故必须为整个集合选定一个寿命;若取较大者,较短那条的作者意图被违反;若取较小者,则应答里出现过的一条记录会在其自身 TTL 未尽时消失。两种选择都使 TTL 失去「这条记录可以缓存多久」的含义。∎
现实中这条约束经常被违反(多人分别编辑 zone file 时尤甚),而各实现的收敛行为不一致——有的取最小值,有的取第一条,有的报错拒绝加载。本页的解析器取最小值并报出冲突。
3 · zone file 里的四类数据
一份 zone file 里的行,权威性并不一致。RFC 1034 §4.2.1 把它们分作四类:
第一类是本 zone 的权威数据——顶点以下、未被委任出去的一切。它们进应答的 ANSWER 段,带 AA 标志,会被 DNSSEC 签名。
第二类是顶点自身的 SOA 与 NS,表明「本服务器对此 zone 有权威」。
第三类是委任:指向下级 zone 的 NS 记录。它们写在本 zone 的文件里,却不带权威——同一组 NS 在子 zone 的顶点也有一份,那一份才是权威的。委任应答里它们进 AUTHORITY 段,AA=0。
第四类是 glue:委任目标主机的地址记录,同样不带权威,进 ADDITIONAL 段。
glue 的必要性来自一个循环。设 vega.dev 的 NS 是 ns1.vega.dev——要问 ns1.vega.dev 的地址,得先找到 vega.dev 的权威服务器,而那正是 ns1.vega.dev 自己。父 zone 提供 glue 就是为了打破这个循环,所以 glue 只在 NS 目标落在被委任子树内(术语叫
in-domain)时才是必需的。
注 · NS 完全可以指向别处:图 3-1 的根 zone 里,dev. 的 NS 就落在另一个 TLD 之下。这类地址记录叫 sibling glue,父 zone 没有提供的义务,因为它不构成循环。但实践中根 zone 提供了,各家权威实现也都会在 ADDITIONAL
段附上手头有的地址——省一轮往返是实打实的。写本系列的解析模拟器时,起初只按「严格 glue」填 ADDITIONAL 段,结果模拟出的根不给 TLD 服务器的地址,与真机 dig 的输出对不上;改成「手上有就附上」才吻合。
4 · 被切断遮蔽的记录
委任点之下唯一还能留在父 zone 里的数据是 glue 与 DS/NSEC。其余全部被遮蔽(occluded):不会被应答,也不会被 AXFR 传送(RFC 5936 §3.2)。
这是 zone file 里最隐蔽的一类错误,因为它完全沉默:语法无误、named-checkzone 通过、记录明明写在文件里,就是永远查不到。图 3-1 的「故意写坏的」示例里那条 hidden.lab.vega.dev. TXT 正是如此——lab.vega.dev 已经委任出去,它以下的整棵子树就不再属于 vega.dev 这个
zone 了,哪怕文件里还写着。
5 · 通配与空节点
*.cdn.vega.dev 这样的 owner 是通配(wildcard)。它的展开规则比看起来窄,三条都容易记错:
通配只在 closest encloser 之下生效——先找 qname 最长的、在 zone 里确实存在的祖先,再看它下面有没有 *。所以更近的实名会挡住更远的通配。
合成出来的记录,owner 是被问的那个名字,不是 *.…。通配是合成,不是别名,应答里不出现星号。
通配只能给它自己有的类型。问 img.cdn.vega.dev 的 AAAA 而通配只有 A,得到的是 NODATA,不是 NXDOMAIN。
与通配相关的是空节点(empty non-terminal):cdn.vega.dev 自己没有任何记录,但因为 *.cdn.vega.dev 存在,它是一个存在但无数据的节点。问它得到 NODATA 而非 NXDOMAIN。这一条是实现里最容易写错的地方,而错的后果会在 DNSSEC 与 QNAME minimisation 两处放大(见第 7、8 讲)。
6 · CNAME 的两条限制
CNAME 把一个名字指向另一个名字,解析器拿到它之后换名字继续查。两条限制:
同一个名字上只能有一条 CNAME,且它不得与其他类型共存(RFC 1034 §3.6.2)。原因是解析语义会矛盾:CNAME 的含义是「这个名字的数据在别处」,而并存的记录说数据就在本名字上。
因此 zone 顶点不能是 CNAME——顶点必须有 SOA 与 NS。这正是「根域名(example.com 而非 www.example.com)不能 CNAME 到 CDN」这个常见困境的来源,各家 DNS 服务商用 ALIAS / ANAME /
CNAME flattening 之类的非标准记录绕开它:在权威服务器内部解析目标名,然后以 A/AAAA 的形式应答。RFC 9460 之后更规范的出路是 HTTPS 记录,它允许在顶点做服务绑定而不违反上述限制。
7 · 参考文献
- Mockapetris, P. (1987). Domain Names — Concepts and Facilities (RFC 1034). IETF. §3.6 是 RR 的构成与 CNAME 限制,§4.2.1 是 zone 内四类数据的划分。
- Elz, R., & Bush, R. (1997). Clarifications to the DNS Specification (RFC 2181). IETF. §5 定义 RRset 与 TTL 一致性要求。
- Lewis, E. (2006). The Role of Wildcards in the Domain Name System (RFC 4592). IETF.
- Gustafsson, A. (2003). Handling of Unknown DNS Resource Record (RR) Types (RFC 3597). IETF.
- Lewis, E., & Hoenes, A. (Eds.). (2010). DNS Zone Transfer Protocol (AXFR) (RFC 5936). IETF. §3.2 说明委任点之下的数据为何不被传送。