摘要

  • CNAME 让一个 DNS owner name 成为只指向一个目标的别名。为了避免源名与目标名各给出一套互相矛盾的答案,别名节点不能再保存 A、MX、TXT、NS 等普通数据。
  • 解析器缓存 CNAME 后从目标名重新查询。源 zone 控制改道,目标 authority 控制最终数据;这既不是 delegation,也不证明身份、所有权或服务可用性。

一个名字想承担两种互不相容的职责

设想 portal.example 被发布为 service.example.net 的 CNAME。客户端询问地址,源服务器先返回别名,解析器再去目标名寻找 A 或 AAAA。若源 zone 同时给 portal.example 放上一条自己的 A 记录,缓存该信哪一个:源名直接携带的地址,还是沿别名抵达的地址?

1987 年 11 月发布的 RFC 1034 没有把这个冲突留给实现者随意选择。CNAME 的 owner 是 alias,RDATA 中的名字才是 canonical target;一旦节点存在 CNAME,就不应再有其他普通数据。

这不是 zone file 的整洁偏好。规范给出两个架构理由:canonical name 与 alias 的数据不能互相打架;缓存了 CNAME 的解析器也必须能够直接使用它,不必回头询问权威服务器这个 owner 是否还有另一类型的数据会推翻改道。

“排他”因此创造了一种很窄的权力。CNAME 不告诉解析器地址、邮件服务器或应用服务是什么,只决定下一次问题应从哪个名字开始。

答案会改写接下来要问的问题

CNAME 不是被动返回一个同义拼写。若 QTYPE 不是 CNAME,RFC 1034 的算法要求服务器把 CNAME RR 放入 Answer,随后把工作中的 QNAME 改成目标名,从头开始匹配。只有直接查询 CNAME 时不追随,因为此时客户端要看的正是改道记录本身。

RFC 9499 用三组术语呈现这次移动。Original QNAME 是请求报文真正携带的名字;effective QNAME 是沿 CNAME chain 实际解析过的任何名字;final QNAME 是链尾最终处理的名字。因此,响应 Question 区仍可回显原始名字,而真正回答请求的 RRset owner 已是另一个名字。

失败时,这一差别尤其重要。源别名可能确实存在,并得到源 authority 的权威回答;终点却可能不存在、暂时故障,或落在完全不同的 zone。final QNAME 的否定答案不能倒推成 original alias 从未存在。

改道不是委派

DNS alias 与 DNS delegation 都会让解析继续前进,却转移不同的东西。zone cut 上的 NS RRset 告诉解析器,哪个 name server 对下级 zone 掌握权威。CNAME 始终是源 authority 为自己的名字发布的数据:它启动另一次查询,却没有把源 zone 的权力交给目标运营者。

源 zone 管理员可以创建、修改或删除 portal.example,并选择 alias TTL。service.example.net 的 authority 则控制终点 A、AAAA 或其他 RRset 及其 TTL。源方指向目标,并不获得修改目标的权力;目标方也不能因此编辑源别名。

RFC 6604 后来澄清 CNAME/DNAME chain 的状态语义:一个响应可以对第一段 alias 给出权威答案,同时在后续阶段携带 referral 或失败。第一句话是谁说的,与整段旅程由谁保证,是两件事。

一个别名只有一个目标,不等于一台机器只能有一个名字

“canonical” 很容易诱发越界理解,仿佛 DNS 规定一台 host 或 interface 只能有一个官方名称。RFC 2181 明确否定这种推论。DNS 没有对机器身份设下一名制。

真正的规则更窄:一个 CNAME owner 只能有一个 canonical target。RFC 2181 还提醒,把 owner 口头称为“一个 CNAME”会掩盖方向。owner 是 alias,record value 才是该 alias 操作中的 canonical name。

多组域名可以合法地通向同一服务;不是 alias 的名字也可以同时拥有多种普通 RRset。被禁止的是同一个 owner 一边宣布“请去别处继续问”,一边又自称可以在这里独立回答。

zone apex 为什么不能走这条捷径

排他规则带来一个直接后果。zone apex 必须携带 SOA 与 NS;CNAME owner 又不能同时携带这些普通记录,因此 apex 不能是普通的 CNAME。

RFC 1912 记录过这种配置的危险。它用当时的 BIND 举例:若 apex 的 NS 旁再放 CNAME,服务器可能接受 CNAME 而忽略其他资源,最终连该 zone 下的名字都不可见。具体行为属于那个时期的实现,数据模型的冲突却不会因此消失。

今天某些服务会在 apex 综合生成地址答案,或把这种产品行为称为 flattening。它们可能有实际用途,却不是 wire 上的 CNAME RR。诊断时若把所有便利功能都叫“apex CNAME”,反而会抹掉协议真正要求核对的 authority 与 cache 边界。

有些位置不能把下一跳藏在别名后面

RFC 2181 还规定,NS 的 target 与 MX 的 exchange 不能是 alias。查询 NS 或 MX 时,服务器会尝试在 Additional section 附上相关地址,减少预见得到的后续查询;这一处理不会先追 CNAME,再替 canonical target 补地址。

若把 alias 填进这些位置,就会增加查询与网络负担;在 delegation 等困难场景里,缺失地址甚至会让解析失败。写入 NS/MX 的管理员应先解析别名,再直接发布真正拥有地址 RRset 的名字。

这不是禁止所有“名字指向名字”。它限制的是那些依赖可预测地址发现的协议位置:隐含的额外跳转会破坏原本可一次随附的引导信息。

DNSSEC 增加证明,没有让别名重新成为目的地

随着 DNSSEC 演进,“不能有其他数据”获得了必要的限定。RFC 2181 允许当时的安全记录与 CNAME 共存;RFC 4034 后来要求 signed zone 在 CNAME owner 处放置 RRSIG 与 NSEC。

这些记录不与目标竞争。RRSIG 验证 CNAME RRset,NSEC 参与认证否定与类型证明。它们说明别名节点处于什么状态,不赋予源名一套独立地址、邮件路径或应用内容。

RFC 4033 也限制了安全结论。DNSSEC 在验证成功时提供 data-origin authentication、integrity 与 authenticated denial;它不会证明目标应用健康、某个人或企业的身份、合同关系或源名的法律所有权。

chain 可以存在,loop 必须被识别

一个 alias 可以再指向另一个 alias。RFC 1034 因效率原因建议避免多层 chain,却没有把有限 chain 本身定义成错误。解析器真正必须捕获的是 loop,以及最终指向不存在名字的情况,并把错误状态交给客户端。

所以运维对象是一张图,不是一次字符串替换。每条边都有 owner、authority、TTL,可能还有自己的 DNSSEC 状态;链尾数据另有一套 TTL。不同 cache 按不同时间淘汰旧边,同一迁移在各观察点会经历不同阶段。

这正是解耦的价格与价值。源运营者无需复制目标数据就能移动熟悉名称;目标运营者更换地址时也不必编辑所有别名 zone。但双方都能独立变化,缓存会把过去的陈述保存到各自到期为止。

最小共同规则靠拒绝混合真相而成立

CNAME 用一项刻意很薄的机制解决分布式维护问题。它不需要一个登记所有等价名字的全球 registry,也不需要中央机构裁定哪个地址才“真正属于”alias。它只需要一条任何解析器都能缓存、追随并独立验证方向的边。

排他规则是这项设计的制度中心。源名可以是绕路,也可以是目的地,但不能同时扮演两者。一旦选择绕路,它的权力真实而有限:发布一个目标,保留链上证据,把终点数据留给终点的控制者。

来源与证据边界

原始模型与解析算法来自 RFC 1034、RFC 1035;常见配置错误与命名澄清来自 RFC 1912、RFC 2181;DNSSEC 例外来自 RFC 4033、RFC 4034;chain 状态与现行术语来自 RFC 6604、RFC 9499。这些标准不能证明当代部署比例、厂商 flattening 行为、解析器 chain 上限、dangling alias 发生率、应用所有权或任何现有服务的可用性。