DNS 名字压缩用高两位为 11 的两个字节,指向本报文里更早出现的同一段名字。偏移占 14 位,从 ID 的第一个字节算起。普通标签的高两位是 0,单段因此不超过 63 字节。发送方可以不用指针。规则在 1987 年 11 月的 RFC 1035 第 4.1.4 节。

先看高两位

一个可独立引用的定义句:DNS 压缩指针是两个字节。高两位是 11,其余 14 位是偏移,从报文开头算起,也就是从标识字段的第一个字节算起。偏移 0 指向这个字节(RFC 1035 第 4.1.4 节)。

普通标签不是这一种。第 3.1 节写:每个长度字节的高两位必须是 0,剩下 6 位把标签限制在 63 个八位组以内。长度 0 是根的空标签,名字到此结束。第 2.3.4 节把同一条上限列成「标签 63 个八位组或更少」。第 4.1.4 节把高两位的另外两种组合 01 和 10 留给以后。2026 年 10 月 22 日没有重读后来有没有分配或废止这两类,因此不判断它们现在还能不能出现。

14 位能表示的最大偏移是 16383。原文没有印出这个数。第 4.1.4 节也没有另写「偏移落到报文末尾之外时必须丢掉」。本文不补一条原文没有的处理。

1987 年的报文头图是六行,每行 16 位。按图数是 12 个八位组,原文没有印出 12。问题段排在头后面。例子里的第一个名字放在偏移 20,不是 12。不要把 12 当成每个问题名的固定位置。后来有没有改标志位、有没有把头加长,本文没有重读那些更新,见文末。

第 4.1.2 节写,问题名可以是奇数个八位组,不补齐。指针虽是两个字节,名字本身并不因此对齐到偶数。

高两位1987 年文本里的含义
00长度字节。低 6 位是本段长度,不超过 63。0 表示根,名字结束
11指针。低 14 位是从 ID 第一个字节起算的偏移
01 或 10留给以后。本文没有重读后来的分配

RFC 1035 没有引用 RFC 2119。下文的「必须」只对应原文已经写出的小写 must 或 required:长度字节的高两位必须为 0;所有程序都必须能读到达的、带指针的报文。

指针结束名字,并指向更早的字节

一个可独立引用的定义句:压缩后的域名只能是三种之一:以一个 0 字节结束的标签序列、单独一个指针,或者若干标签再跟上一个指针。指针替换的是先前已经出现过的整个名字,或名字末尾的一串标签(RFC 1035 第 4.1.4 节)。

指针放在末尾,后面不再跟标签。原文用的词是先前出现(prior occurrence)。偏移若不早于指针自己,就落在这句的外面。这一节没有另写一条检测环的必须。本文不提供构造这种环的步骤。

第 4.1.4 节的例子用下面这些偏移。这是 1987 年文本里的示意,不是 2026 年 10 月的抓包。

偏移例子里写的名字
20标签 F、ISI、ARPA,以 0 结束
40标签 FOO,再加一个指向 20 的指针,合成 FOO.F.ISI.ARPA
64指向 26 的指针。26 是第一串里 ARPA 的长度字节,名字就是 ARPA
92单独一个 0,表示根,没有标签

原文写明,指向 26 的那个指针依赖于 ARPA 是偏移 20 那一串的最后一个标签。第一串若在 ARPA 后面还有别的标签,同一个偏移就不再只代表 ARPA。

名字若放在带长度的部分里,例如资源记录的 RDATA,长度按压缩后的字节数计算,不按展开后的名字。一个两字节的指针在这个长度里计 2,不计它指到的那一串有多长。

指针只能用在格式不随类而变的域名出现处。原文的理由是:否则服务器或解析器就得认识它处理的每一种资源记录的格式。1987 年的文本接着写,当时还没有这种例外,以后的 RDATA 格式里可能会有。本文没有重读后来的记录类型,因此不判断哪一种 RDATA 后来禁止压缩。

DNSSEC 讨论的是签名校验为何失败。签名用的规范形式要不要先把指针展开,在 RFC 4034 里。本文没有重读 RFC 4034,不把那份规则并进第 4.1.4 节。

发送方可以不用指针。原文写,这会降低数据报能装下的量,并可能造成截断;但所有程序都必须能理解到达的、含有指针的报文。不用指针,并不使截断位必须为 0。截断位 一文写 TC 何时表示没装完。第 4.2.1 节把 UDP 上的报文限制写成 512 字节,不计 IP 头和 UDP 头。超过这个限制时如何置位、后来的文件如何放宽,都在那一篇。本文没有重读那些更新。指针的两位格式不靠这个 512。

第 3.1 节把域名总长限制在 255 个八位组以内,计的是标签字节和长度字节。第 2.3.4 节把同一条写成「名字 255 个八位组或更少」。压缩后的线上字节可以更短。这 255 不是 14 位偏移的上限。

偏移不从 IP 头起算

隧道把整份 IP 包再装进外层时,DNS 报文仍从自己的 ID 起算。外层头变长,不改报文内部的偏移。原样携带时,指针不用因为多了一层封装而重算。

若某个程序重写了这份 DNS 报文,新的偏移仍从新报文的 ID 起算。在带长度的字段里,仍按压缩后的长度填写。假 IP 一文写的是用保留地址代替真实解析结果。名字压缩不代替那个地址选择。某一客户端重写报文时是否还保留指针,本文没有测量。本文不提供改写指针的步骤。

查询名最小化 关心的是向某一台权威服务器送出名字的哪一段。压缩关心的是,在已经放进同一份报文之后,重复的后缀怎样用偏移代替。两篇不互相决定。

DNS Cookie 写的是选项 10 的两段长度。名字压缩不改变那个选项号,也不把 Cookie 的字节算进标签长度。本文没有把选项 10 的布局再数一遍。

已验证勘误 6464 只把第 4.1.4 节的 occurances 改成 occurrences。Held for Document Update 的 2691 改的是同一节另一处 occurance 的拼写。两处都不改指针的两位格式。勘误页标着 Verified (12)。其余已验证条目没有逐条读完。

2026 年 10 月 22 日的元数据把 RFC 1035 标成 Internet Standard,没有把它标成已被取代。更新者一长串,其中包括 RFC 2181、4034、4035 和 7766。本文没有重读这些正文,因此不判断它们有没有改写第 4.1.4 节。

现象对照

现象更可能先看什么
长度字节的高两位是 11这是指针。若把整个字节当成长度,会误看成 192 到 255
偏移是 0指向 ID 的第一个字节,不是 IP 头
指针后面还有标签不在第 4.1.4 节列出的三种写法里
RDATA 的长度按展开后的名字填写与「按压缩后的长度计算」不符
报文里没有任何指针允许。发送方可以不用。接收方仍必须能读别人发来的指针
只有外层隧道头变了不因此改 DNS 偏移。偏移不从 IP 头起算

核对清单

  1. 先看高两位:00 是标签,11 是指针。01 和 10 在 1987 年文本里留给以后,后来的分配本文没有重读;
  2. 偏移从 ID 起算:0 是标识的第一个字节。14 位最大是 16383,原文没有印出。例子里的第一个名字在偏移 20;
  3. 指针结束名字:它指向先前出现的后缀。带长度的字段按压缩后的字节数计算,两字节指针计 2;
  4. 可以不发指针:收到带指针的报文则必须能读。不用指针可能截断。截断位和 512 字节的后来变化见另一篇;
  5. 外层封装不改这个偏移。更新 RFC 1035 的那一长串正文没有重读。勘误 6464 只改拼写,其余已验证条目没有读完。