Total Pageviews

Sunday, 8 December 2019

谈谈关于 CNAME 和 MX 冲突的一些事

这本来是一个颇老生常谈、甚至是一个本应该盖棺定论的问题了。虽然 RFC 1034- https://tools.ietf.org/pdf/rfc1034.pdf 早就给出了规范,但是 CNAME 和 MX 不能同时添加的问题和说法依然时不时就会出现,比如在 https://www.v2ex.com/t/479384。所以打算也来谈一谈这个问题。 
缘由 
CDN 服务商的众多节点大多都是使用 GSLB 进行调控的,大部分 CDN 服务的 GSLB 又都是通过 CDN 服务商的权威 DNS 设施上部署的 GeoDNS 实现的(少数如 Cloudflare、Fastly、Stackpath、MaxCDN、Google Global Load Balancer 等 CDN 服务商使用了 Anycast、通过调整 BGP Route 实现 GSLB)。所以用户的网站如果需要接入 CDN 一般有两种方法,将用户的域名接入到 CDN 服务商的权威 DNS(360 网站卫士和百度云加速的 NS 接入),或者用户通过添加 CNAME 记录从而实现 CDN 服务商的权威 DNS 接管 GeoDNS。 对于一些需要在根域(@)接入 CDN、同时需要添加域名邮箱所需的 MX 记录时,就会导致冲突问题。 

剖析问题 
毫无疑问,只要了解一些 DNS 的基础知识的都知道,CNAME 的意思是当前域名的解析结果应该全部、毫无保留的采用返回 CNAME 结果中的域名的解析记录,递归 DNS 会直接向下追踪 CNAME 域名的解析记录、会直接无视其它解析结果。而且,因为 RFC 的规范的规定,权威 DNS 和递归 DNS 都不会允许 CNAME 和其它类型的解析结果同时、一起返回。

常见的解决方案 
使用 CNAME,无非是因为 SLB、GSLB 由其它服务商提供,而且服务商在使用自己的权威 DNS 设施实现 SLB 和 GSLB。解决的方法也很简单,不返回 CNAME 而是返回 A/AAAA 就可以和 MX 共存了。 所以,最完美的解决方案,应该就是用户直接使用 NS 接入 CDN 服务商、直接使用 CDN 服务商的权威 DNS(如本文一开始就提到的 360 网站卫士、百度云加速,以及 Fatstly 和 Akamai 的定制版业务),不仅 GeoDNS 和使用 CNAME 一样精准,而且 CDN 服务商的权威 DNS 可以直接给 @ 返回 A/AAAA 记录,和 MX 记录并不冲突。 另一个相对最完美的解决方案是在 CNAME 域名中返回 MX 记录。Cloudflare 的 CNAME Setup 就是采用这种解决方案。
以使用 CNAME Setup 的 globalsign.com 为例,globalsign.com 对应的 CNAME 是 globalsign.com.cdn.cloudflare.net ,你们可以在终端里 dig 一下这个 CNAME 域名:

$ dig globalsign.com.cdn.cloudflare.net
;; ANSWER SECTION:
globalsign.com.cdn.cloudflare.net. 300 IN A 198.41.214.154 globalsign.com.cdn.cloudflare.net. 300 IN A 198.41.215.154

$ dig globalsign.com.cdn.cloudflare.net MX
;; ANSWER SECTION:
globalsign.com.cdn.cloudflare.net. 300 IN MX 10 globalsign-com.mail.protection.outlook.com.

这样如果 globalsign.com 的根域名添加了 globalsign.com.cdn.cloudflare.net 的 CNAME 记录,就不会导致找不到 MX 记录而丢失信件。 
然而,globalsign.com 是直接在 @ 添加了两条 A 记录(Cloudflare Anycast IP),并不是直接添加 CNAME 而且,大部分 CDN 服务商内部的 GSLB 系统会导致多个 CNAME 递归,七牛这种卖二手 CDN 更会导致 CNAME 之间的递归。所以这种方案并不现实。 

还有相对不完美的解决方案。部分权威 DNS 服务商提供这样一种服务:由权威 DNS 的节点解析 CNAME 的 A/AAAA 的结果,然后权威 DNS 直接返回 A/AAAA 记录。这样的服务通常会被命名为 ALIAS ANAME Flatten CNAME 等。使用这类服务将 CNAME 变成 A/AAAA 的方案的确可以解决冲突的问题,但是 Flatten CNAME 又要保留 GeoDNS,就会高度依赖于权威 DNS 服务商的节点分布,所以最终得到的 GeoDNS 结果一定会非常不精确。

DNSPod 的共存和 CloudXNS 的 LINK 记录
接下来就是本文的重点了。之前 DNSPod 支持同时添加 CNAME 和 MX 记录(面板会提示可能导致问题)、CloudXNS 的面板不允许 CNAME 记录和 MX 记录共存,但是 CloudXNS 官方在他们的用户社区中发过使用 LINK 记录绕过这个限制的方法。
前面说过,规范已经规定了 CNAME 和 MX 记录不能同时返回,那么 DNSPod 是怎么做的?DNSPod 自称他们是自研的架构而不是使用 BIND 这类成熟的程序。这并不是重点,重点在于 DNSPod 可以根据向他们的权威 DNS 节点的请求的不同 Type 返回不同的结果,说人话就是你向 DNSPod 请求 MX 记录他们就会返回 MX 记录,你向 DNSPod 请求 A/AAAA 这些就返回 CNAME。 但是,用户并不会直接请求 DNSPod 的权威 DNS 的——用户都是请求的递归 DNS,你不能保证所有用户都使用你 DNSPod 提供的公共 DNS 嘛。一旦递归 DNS 缓存了 CNAME 记录,就会沿着 CNAME 域名继续向下请求解析,就会导致邮件丢件。
CloudXNS 的 LINK 也不是 Flatten CNAME 这类服务,而是一个便捷的管理多个 CNAME、减少 CNAME 递归次数的一个工具;如果你为 CDN CNAME 域名添加的 LINK 记录,最终依然会返回 CNAME 记录。使用 LINK 绕过 CNAME 和 MX 不能共存的限制,和 DNSPod 的允许在控制台中同时为 @ 添加 CNAME 和 MX 本质上并没有什么区别。
如果权威 DNS 支持 CNAME 和 MX 共存,那么为了防止邮件丢件,就应该尽可能少的让 CNAME 被缓存,这样 MX 依然有机会会被返回,也就是把 TTL 调整地尽可能短。但是,权威 DNS 服务商为了降低自己的服务的负载,会限制最短 TTL;即使你成功缩短了权威 DNS 返回的 TTL,递归 DNS 也会为了降低自己的服务负载而篡改 TTL。所以这并不是一个可行的解决方案。
最近 DNSPod 也开始禁止 CNAME 和 MX 共存 了,已经共存的会把 MX 暂停掉,看来 DNSPod 应该放弃他们的自研架构了.