上一篇文章无意中发现了我的公网 IPv6 地址是能够联通的,并且实际联机体验较为不错。但由于没钱买商宽,所以没有静态 IP,而每次 IP 变动都会带来麻烦,因此本篇将探索如何使用 Cloudflare API 来更新域名 DNS,四舍五入成为一个 dDNS。
IPv6 公网地址虽然好,但毕竟是动态的。虽然 Minecraft 本身对于服务器的地址没什么要求,但一些 mod,例如小地图,它使用服务器的地址来区分数据。也就是说,当 IP 变动时,即便服务器还是原来的服务器,但对于小地图 mod 来说,这就是一个新的服务器,因此之前加载的地图缓存和路经点也就全没了。
前些日子刚好想起来我还有个域名托管在 Cloudflare,主要是利用它的 tunnel 服务来将内网或一些临时性的服务分享到公网上(一方面是懒得更新 DNS,另一方面是懒得搞 SSL 证书)。我原本也想用 tunnel 服务将 Minecraft 服务器映射出去,但简单测试后发现延迟波动比较大,而且延迟普遍有点高,不适合联机。这时我突然意识到,Cloudflare 提供了完备的 API,即便官方没有提供 ddns 客户端,我也可以通过调用 API 的方式更新域名的 DNS 记录。
想来想去,由于我用的是 Linux 系统,因此实现的最佳方式便是 bash,恰好 Cloudflare 的 API 文档也提供了多种多样的调用例子,其中就包含了 Shell/curl:https://developers.cloudflare.com/api/operations/zones-get
要访问 Cloudflare API,第一步就是要获取访问密钥,也就是所谓的 API Token,这一步在账户设置里就可以找到。关键点在于创建的 Token 不光要能 Edit Zone 设置,还要能 Read,不然后面第一步获取 id 就失败了。我一开始以为 Edit 蕴含了 Read 权限,所以是我调用的方式不对,结果发现是我 token 的权限没给。
- api_email="your login email address"
- api_key="your api token"
在 Cloudflare 中,网页中显示的 website,实际上是 DNS Zone。要操作一个 DNS Zone,我们不能直接用名字,我们要用它的 ID。根据文档,我们可以直接按照 Zone 的名字(也就是域名)进行筛选:
- zone_name="example.com"
- zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" | jq -r .result[0].id )
- echo Zone $zone_name id is: $zone_id
这里我们用 jq
这个工具从返回的 Json 中获取我们想要的结果。
拿到 Zone ID 之后,若要修改一个记录(Record),我们同样需要 ID。根据文档,我们使用记录的完整域名和 zone id 进行查询:
- record_name="ddns.example.com"
- record_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$record_name" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" | jq -r .result[0].id )
- echo Record $record_name id is: $record_id
同样地,使用 jq
来解析 Json。
拿到了这两个 ID 我们就可以更新 DNS 记录了。这里要先在网页面板上创建好一个 AAAA 记录(用于 IPv6),至于实际地址可以随便写,比如::1
。关于 TTL,因为 IP 可能会随时变动,因此直接设置为最小值,以便 IP 更新后朋友那边可以直接获取到最新的记录。那么,IPv6 怎么获取呢?经过一番 awk
的现学现卖,我得到了如下命令:
- v6Addr=$(ip -6 addr show scope global | grep /128 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^fd7a)
在上一篇文章中可以得知,我的 NUC 是能够获得一个 / 128 的公网 IP。我先通过 ip addr
命令的变体找出所有公网 IPv6 地址,然后寻找后缀是 / 128 的地址,使用 awk 命令提取出 IP,随后排除掉来自 tailscale 的 fd7a
开头的地址。最后将字符串形式的 IP 存入到 v6Addr
这个变量中。
根据文档,我们可以使用 PATCH 动作来只更新我们需要的字段:
- result_json=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" \
- --data "{\"type\":\"AAAA\",\"content\":\"$v6Addr\"}")
- # check the result and print
- if [[ "$(echo $result_json | jq .success)" == "true" ]]; then
- echo Done!
- else
- echo -e "Failed to update DDNS. CF API result: $result_json"
- exit 1
- fi
这里我们确保更新的是 AAAA 记录,并且记录的内容是我们获取到的公网 IP。最后同样使用 jq 判断一下返回的数据是否包含成功。成功则直接退出,若失败则将结果打印到控制台中。
至此,我的朋友和我联机时就再也不用把小地图和路经点复制来复制去了。
完整版脚本
这个脚本非常原始,中间命令没有错误检测,并且只能设置 IPv6,并且 IP 只能从本地网卡获得。使用前请务必确认你理解这个脚本的全部内容,使用此脚本的风险和后果由你自行承担。
- #!/bin/bash
- # basic info
- api_email="your login email address"
- api_key="your api token"
- zone_name="example.com"
- record_name="ddns.example.com"
- # the command to pull ipv6 address
- v6Addr=$(ip -6 addr show scope global | grep /128 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^fd7a)
- echo Using IPv6 address: $v6Addr
- # Call CF API to get zone id and record id
- zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" | jq -r .result[0].id )
- echo Zone $zone_name id is: $zone_id
- # record id
- record_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$record_name" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" | jq -r .result[0].id )
- echo Record $record_name id is: $record_id
- # Update the record
- result_json=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \
- -H "X-Auth-Email: $api_email" \
- -H "Authorization: Bearer $api_key" \
- -H "Content-Type: application/json" \
- --data "{\"type\":\"AAAA\",\"content\":\"$v6Addr\"}")
- # check the result and print
- if [[ "$(echo $result_json | jq .success)" == "true" ]]; then
- echo Done!
- else
- echo -e "Failed to update DDNS. CF API result: $result_json"
- exit 1
- fi
- 全文完 -

【歪门邪道】使用 Cloudflare 作为 ddns 更新 IPv6 域名 DNS 由 天空 Blond 采用 知识共享 署名 - 非商业性使用 - 相同方式共享 4.0 国际 许可协议进行许可。
本许可协议授权之外的使用权限可以从 https://skyblond.info/about.html 处获得。
如果有其他脚本的 ipv4 记录会被覆盖掉吗?我用了好几个不同脚本,ipv4 和 ipv6 之间记录会互相覆盖
照理说应该不会吧,DNS 解析的话,IPv4 是 A 记录,而 IPv6 是 AAAA,这两个互不干扰的
我家 IPv6 一直都是吃灰状态 @(笑尿)
我这也差不多,平时还是以 v4 为主,只有需要虚拟组网或者挂 bt 下载的时候才有 v6 的用途