DNS Cookie 是 EDNS 选项 10。只有客户端饼干时,长度是 8;带上服务器饼干后,RFC 7873 允许 16 到 40。版本 1 把这个长度收成 24。它不是 DNSSEC。基础文本是 2016 年 5 月的 RFC 7873,算法由 RFC 9018 更新。
长度先区分有没有后半段
一个可独立引用的定义句:COOKIE 选项使用选项码 10。客户端还不知道服务器饼干时,选项长度是 8,内容只有 8 字节的客户端饼干。已经知道时,选项长度是 16 到 40,前 8 字节仍是客户端饼干,后面是 8 到 32 字节的服务器饼干(RFC 7873 第 4 节)。
合法长度只有两种:正好 8,或者 16 到 40 含两端。短于 8、落在 9 到 15、或长于 40,服务器生成 FORMERR。一份请求里若有多个 COOKIE 选项,只看离 DNS 头最近的第一个,其余忽略。
| 选项长度 | 里面有什么 | 第 5.2.2 节 |
|---|---|---|
| 8 | 只有客户端饼干 | 合法 |
| 16 到 40 | 8 字节客户端饼干,加 8 到 32 字节服务器饼干 | 合法 |
| 其他 | 畸形 | FORMERR |
2026 年 10 月 16 日更新的 IANA DNS 参数 里,EDNS 选项 10 的名称是 COOKIE,状态是 Standard,参考文献是 RFC 7873。扩展 RCODE 23 的名称是 BADCOOKIE,描述是 Bad/missing Server Cookie,同一份参考文献。2026 年 10 月 21 日检索 RFC 7873 与 RFC 9018 的勘误页,没有列出条目。
它不是浏览器保存的 HTTP Cookie。浏览器指纹 说的是网页脚本读到的特征。这里的饼干只出现在 DNS 报文的 OPT 记录里。另一项 EDNS 选项才会带上客户端地址的一段前缀,见 EDNS 客户端子网。2026 年 10 月 21 日没有重读那一篇的字段表,因此不把选项 8 和选项 10 的长度并成一张表。
版本 1 的服务器饼干是 16 字节
一个可独立引用的定义句:RFC 9018 定义的 128 位服务器饼干分成四段:1 字节版本、3 字节保留、4 字节时间戳、8 字节散列。版本 1 的散列是 SipHash-2-4。校验这种饼干时,COOKIE 选项的长度必须正好是 24 字节(RFC 9018 第 4 节)。
RFC 9018 更新 RFC 7873。它写明附录里的简单算法不得再用作服务器或客户端饼干,因为按现行标准看太弱;第 4 节的互操作算法必须被服务器饼干的实现使用。第 6 节又写,以后可以按版本字节和长度登记别的算法。两句同时成立:版本 1、长度 16 这一格必须用第 4 节;登记表允许别的格子以后再填。
2026 年 10 月 16 日的登记「DNS Server Cookie Methods」里,版本 1、长度 16 的方法是 SipHash-2-4,参考文献是 RFC 9018。版本 0 保留。版本 1 的其他长度、以及版本 2 到 239,当日是未分配。版本 240 到 254 保留给私用。看见一份长度不是 24 的选项,只说明它不是这个版本 1 的长度,不说明它违反了 RFC 7873 的 16 到 40。
| 子字段 | 宽度 | 第 4 节 |
|---|---|---|
| Version | 8 位 | 本文把版本 1 定义成这一节的结构和算法 |
| Reserved | 24 位 | 构造时必须是 0。校验时不得强制它为 0,散列用收到的值 |
| Timestamp | 32 位 | 从 1970-01-01 00:00:00 UTC 起的无符号秒,忽略闰秒,网络字节序 |
| Hash | 64 位 | SipHash-2-4,密钥是只有服务器知道的秘密 |
散列的输入是:客户端饼干、版本、保留、时间戳、客户端地址,密钥是服务器秘密。客户端地址不写进饼干本身。IPv4 地址 4 字节,拼接后的输入必须正好 20 字节;IPv6 是 16 字节,输入必须正好 32 字节。版本 1 的选项长度必须是 24,使这份输入只有这两种长度。原文举的例子是:避免把来自 IPv4、选项长 36 字节的情况,当成来自 IPv6 去校验。
时间戳的比较必须使用 RFC 1982 的序号算术。2026 年 10 月 21 日没有重读 RFC 1982,因此不复述那份算法,只保留 RFC 9018 自己的窗口:服务器应当接受过去 1 小时以内、以及未来 5 分钟以内的饼干。收到的服务器饼干若已超过半小时,服务器应当至少为此生成一枚新的,也可以更频繁。
客户端饼干在 RFC 9018 第 3 节改成 64 位熵。同一服务器地址必须使用不同的客户端饼干。本机地址改变之后,不得继续使用原来的客户端饼干或服务器饼干。第 3 节说明,先前把客户端地址算进客户端饼干的要求被拿掉了,因为实现通常在知道本机地址之前就要算出它。RFC 7873 第 4.1 节仍印着「应当是客户端地址、服务器地址和秘密的伪随机函数」。读现行构造时以 RFC 9018 第 3 节为准,不要把两句收成「地址既要算进客户端饼干、又不要算进去」。
本文没有运行 RFC 9018 附录 A 的测试向量,也不提供构造散列的步骤。
源地址一变,旧散列对不上
一个可独立引用的结论句:版本 1 的服务器饼干向服务器弱保证:请求来自这个源地址,并且用的是选项里的这枚客户端饼干。保证来自早先应答里发出的那枚服务器饼干。它不保护能看见明文的在路径上的对手(RFC 9018 第 4 节、第 8 节)。
在路径上的人若看见了这枚服务器饼干,可以在它仍然有效的时间里,对那个客户端伪造请求。RFC 7873 第 1 节把这种保护和用 TCP 做 DNS 相比:路径外的人要猜中 64 位的伪随机量,而不是 32 位的 TCP 序号。这是比较,不是「猜不中」。
同一节还写:饼干只回到当初收到它的那个地址,因此不能用来普遍跟踪互联网用户。RFC 9018 第 3 节补的是另一句:地址变了还继续用同一枚饼干,就可以跨链路跟踪,也会绕过 IPv6 隐私扩展。它把这句指向 RFC 8981。两句不要合成「所以饼干从不构成标识」。临时地址的寿命见 IPv6 临时地址。2026 年 10 月 21 日没有重读 RFC 8981。
NAT 把许多内部主机映成同一个外部地址时,服务器若只按源地址计算饼干,其中一个客户端拿到的服务器饼干就能被同一个外部地址后面的其他主机拿去用。RFC 7873 第 6 节因此把客户端饼干也算进服务器饼干。它同时写明,这枚服务器饼干帮不上伪造应答,因为应答仍要带上那个客户端自己的客户端饼干。运营商级 NAT 讨论的是这种多对一的地址共享。本文没有重读那一篇所引的映射定时器。
查询从隧道的哪一个地址离开,决定服务器看见的客户端地址是哪一个。见 DNS 泄漏。版本 1 把这个地址算进散列,却不把它放进选项值。地址一变,旧的服务器饼干不能靠同一份输入通过校验。客户端这一侧,RFC 9018 要求地址改变后不得复用旧饼干。某一解析器把虚拟接口上的地址、物理网卡上的地址,还是出口地址当成「客户端地址」,本文没有测量。
任播 会把同一地址分到多台服务器。RFC 7873 第 6 节建议任播集合使用同一个服务器秘密。RFC 9018 第 5 节把更换秘密分成三个阶段:先在所有服务器上装上新秘密,但仍用旧秘密生成,两枚都算有效;再改用新秘密生成,旧秘密仍可校验;最后才停止用旧秘密校验。阶段 3 里有两句等待,不要收成同一个数字。一句建议至少是所服务区里最长的 TTL 再加 1 小时。另一句应当至少长于客户端被允许使用同一枚服务器饼干的时间,而那个时间应当是 1 小时。
只有客户端饼干时,服务器可以按策略丢掉、回 BADCOOKIE,或正常处理。若应答,必须把请求里的客户端饼干抄回,并附上自己生成的服务器饼干。服务器必须至少偶尔应答,好让客户端学到正确的服务器饼干。经 TCP 收到时,应当把 TCP 本身的认证考虑进去,并应当选择正常处理。
客户端收到 BADCOOKIE、且客户端饼干与发出的一致时,应当用应答里的新服务器饼干重试。用这枚新饼干重试后仍然是 BADCOOKIE,应当再改用 TCP 重试。这是第 5.3 节的客户行为,不是打开一条新隧道的步骤。本文不提供伪造饼干、猜测秘密或迫使服务器改走 TCP 的做法。
DNSSEC 校验的是资源记录上的签名。COOKIE 选项不代替那个签名。服务器没有实现这个选项时,按没有这个选项来应答;客户端和服务器不必同时部署。加密传输把整份 DNS 报文包起来。某一 DoH 或 DoT 实现发不发选项 10,本文没有测量。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
| OPT 里选项码 10,长度 8 | 只有客户端饼干。服务器还没有被这名客户端记住 |
| 长度 24,第一字节是 1 | 符合版本 1 的长度。是否真用 SipHash-2-4,本文没有测量 |
| 长度是 9 到 15,或大于 40 | 第 5.2.2 节的畸形,生成 FORMERR |
| RCODE 23,客户端饼干与发出的一致 | BADCOOKIE。应当用应答里的新服务器饼干重试 |
| 出口地址变了,旧请求开始得到 BADCOOKIE | 版本 1 的散列材料变了。先分清解析器看见的是哪一个源地址 |
| 应答的资源记录通过了 DNSSEC | 签名是另一项。它不证明选项 10 存在 |
核对清单
- 先看长度:8 是只有客户端饼干。16 到 40 才可能带服务器饼干。版本 1 的校验要求正好 24;
- 地址不在选项值里:版本 1 的散列包含客户端地址。保留字段构造时必须为 0,校验时不得强制为 0;
- 两份构造不要并成一句:RFC 7873 第 4.1 节把客户端地址算进客户端饼干。RFC 9018 第 3 节拿掉了这个输入,并要求地址一变就不得复用;
- 不是 DNSSEC,也不是 HTTP Cookie:它只弱校验这一次事务,并且不防在路径上能看见明文的人;
- 登记日是 2026-10-16:当日只有版本 1、长度 16 被指定为 SipHash-2-4。本文没有运行附录里的测试向量。