Why
有个域名托管在 Namesilo 上,对应的主机则是通过 DDNS 穿透到内网的虚拟机上,是没有公网 IP 的,解析则是靠 DDNS 提供的域名作为 CNAME
记录。而大部分域名托管商是不支持给裸域(Apex Domain) 添加 CNAME
记录的。首先,RFC 1034 声明了 CNAME
记录应该是排他的:
If a CNAME RR is present at a node, no other data should be present; this ensures that the data for a canonical name and its aliases cannot be different.
RFC 2181 中进一步说明:
...That is, for any label in the DNS (any domain name) exactly one of the following is true: + one CNAME record exists, optionally accompanied by SIG, NXT, and KEY RRs, + one or more records exist, none being CNAME records, + the name exists, but has no associated RRs of any type, + the name does not exist at all.
其次在 RFC 1034 阐述了 NS
记录和 SOA
记录对于裸域的必要性:
...Though logically part of the authoritative data, the RRs that describe the top node of the zone are especially important to the zone's management. These RRs are of two types: name server RRs that list, one per RR, all of the servers for the zone, and a single SOA RR that describes zone management parameters.
裸域存在 NS
记录和 SOA
记录是最佳实践。意味着我没办法以指定 CNAME
的方式,通过裸域来访问这个网站,很长一段时间里,我只能通过加 www 来访问。而是否使用 www,知乎上 为什么越来越多的网站域名不加「www」前缀? - Rio的回答 给出的结论是比较中肯且全面的。当然,对于仅仅是用于个人网站的我,可以全凭个人好恶,无须考虑太多。
解决方案
在没有独立公网 IP 的情况下通过裸域正常访问 www 网站, 主要有加钱、 Domain Forwarding 和 CNAME Flattening 三种解决方案。
加钱
买一个带公网 IP 的云主机,把服务部署在上面,或者用 FRP 做穿透,然后将裸域的 A 记录指向该 IP。我太穷了,暂不考虑这条路。
Domain Forwarding
Namesilo 和大部分域名托管方给出了 Domain Forwarding 的解决方案,但实际上使用是有限制的,考虑以下几种可能的场景:
输入 | 试图跳转至 | 可行性 |
---|---|---|
http://foo.com | http://www.foo.com | ✅ |
http://foo.com | https://www.foo.com | ✅ |
https://foo.com | https://www.foo.com | ❌ |
Domain Forwarding 的原理,是让你的裸域指向了域名托管商的一个 web 服务器(指向的方式当然也是添加一条 A
记录啦),访问的时候服务器返回 301 跳转到你指定的地址,那自然通过 HTTP 访问托管商的 web 服务器,再通过 301 跳转到 HTTP/HTTPS 的 www 都没问题。
但是要访问 HTTPS 的裸域时,由于域名托管商并没有你的裸域的 SSL/TLS 证书,自然不能为你的 HTTPS 裸域跳转。所以这种方式并不完全支持所有场景。
CNAME Flattening
Cloudflare 提供了名为 CNAME Flattening 的解决方案,表面上允许域名所有者给裸域添加 CNAME
记录,实际背地里把所有者提供的 CNAME
值转换为 A
记录,就实现了(好阴险的偷梁换柱!)
实际上我也可以 nslookup 我的 DDNS 地址的 A
记录,相应地给我的裸域填上 A
记录,但也如上面知乎回答内所言:
特别是使用第三方托管服务的时候。如果第三方迁移服务器导致 IP 地址变更,你必须自己去更改 DNS 的 A 记录。
所以自定义 CNAME Flatening 是可行的,只是麻烦在“自己去更改”。如果我能在每次 DDNS 地址的 A
记录变更时,自动将我裸域的 A
记录一并变更,问题就解决了。
自定义 CNAME Flattening
Namesilo 允许通过 API 来增删查改 DNS 记录,我要做的便是定期查询 DDNS 的 A
记录,以及我的裸域的 DNS 记录情况,结合两者的情况决定对域名 DNS 记录进行何种操作:
- 裸域不存在任何记录,直接新增
A
记录为 DDNSA
记录 - 裸域存在
A
记录,判断是否与 DDNSA
记录相同- 相同,无需更新
- 不同,更新为 DDNS
A
记录
- 裸域存在 CNAME 记录,删除之,新增
A
记录为 DDNSA
记录
将其写在循环中,以 DDNS A
记录的 TTL 来作为线程 Delay 的时间。考虑到可能不止有一个裸域需要同步,写的时候可以做配置文件,开多个线程分别跑。
事实上不只是 Namesilo,只要支持 API 的,或者可以通过爬虫写自动化工具的,皆可以采用此种方法。当然上面只是粗略流程,因为 A
记录是可以有多条的,至于实际使用过程中要保留哪条,以及当 DDNS A
记录也有多条的时候如何应对,需自行斟酌。
裸域不支持cname确实比较恶心,所以我直接用dnspod做解析了。
确实如果 DNS 服务商支持 AName 之类的别名是最好的。