现在 ZZ.AC 域名支持 DDNS 功能了,本文跟大家分享 DDNS 功能的设计理念和使用方法。
设计理念
先说之前为什么不支持 DDNS 🤦♂️
第一是因为系统设计的比较简单,每个用户使用单独的 zone 文件保存自已的记录,如果要支持通过 API 更新,整个系统需要重新设计,工作量很大。
第二是为了预防滥用。因为开启了 DDNS,大家就可以把自己的动态 IP 映射到 ZZ.AC 域名,不好跟踪发布的内容,从而产生滥用的风险。
那为什么现在又要支持了呢?
首先,整个 zz.nic 项目已经完成重构,前后端代码都整合到同一个项目1,这就为使用 LLM 奠定了基础。有了大模型加持,引入 DDNS 带来的工作量就不成问题了。
其次,我自己本身也强依赖 DDNS 功能。我家里的设备需要将 ISP 的动态公网 IP 地址映射到域名上。此外,家里的公网 IP 地址被禁用了 80 和 443 端口,无法使用 ACME HTTP-01 和 ACME ALPN-01 两种 Challenge 来申请 SSL 证书,只能使用 ACME DNS-01,这就要求域名需要支持 API 更新,也就是 DDNS 功能。
ZZ.AC 域名不支持 DDNS,所以之前只能临时使用另外一个域名配合 Cloudflare 实现。考虑到要坚持「吃自己的狗粮」,有必要给 ZZ.AC 域名引入 API 更新功能。
但如果开了 DDNS 功能,怎样防止滥用呢?我的办法是仿照工信部,引入「备案」设计。
简单来说,就是默认不开启 DDNS 能力。比如我的域名是 tao.zz.ac,我想把 NAS 设备绑定到 nas.tao.zz.ac 域名,我就得到 DDNS 后台 https://nic.zz.ac/ddns 给 nas 子域名创建 DDNS 令牌。目前支持 DDNS 和 ACME DNS-01 两种类型,创建时需要登记用途。创建成功之后会展示 Token 和使用方法。
管理员在后台可以看到所有登记的 DDNS 域名及其用途。同时会运行程序自动检查该域名是否有滥用行为。如有发现,直接封禁用户。
使用方法
有了 Token 就可以通过 curl 来更新对应的 A/AAAA 记录。
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"a":"1.2.3.4","aaaa":"2001:db8::1","ttl":300}' \
https://nic.zz.ac/api/ddns/recordsJSON 数据中的 a 和 aaaa 可以单独指定,也可以同时指定,都可以。多次调用会覆盖前面的值。但我建议有调用之前先检查当前值,有更新再调。
curl -H "Authorization: Bearer <token>" \
https://nic.zz.ac/api/ddns/records如果确认不需要了,请调用接口删除:
curl -X DELETE -H "Authorization: Bearer <token>" \
https://nic.zz.ac/api/ddns/records
ACME 证书
如果要自动申请 SSL 证书,则需要创建 ACME DNS-01 类型的 Token,使用方法跟 DDNS 型的类似。
ACME DNS-01 本质上是要更新域名的 TXT 记录,所以调用方法如下:
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"value":"dns-01-challenge-token","ttl":60}' \
https://nic.zz.ac/api/ddns/records注意 JSON 中的 value 字段。又因为同一域名下可能有多条 TXT 记录,所以删除的时候需要指定记录值,不然会误删其他记录:
curl -X DELETE -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"value":"dns-01-challenge-token"}' \
https://nic.zz.ac/api/ddns/records要想申请 ACME 证书,光有接口还不行,还得集成到 ACME 客户端上。最容易集成的就是 acme.sh 因为只需要写一个 shell 脚本就可以了。这个脚本的核心功能是提供dns_zz_add 和dns_zz_rm两个接口,分别对应创建和删除 TXT 记录。将代码保存一 .acme.sh/dnsapi/dns_zz.sh
然后在签发证书时指定用 dns_zz 就可以了:
acme.sh --issue --dns dns_zz -d example.zz.ac -d "*.example.zz.ac"
点击查看 dns_zz.sh 完整代码
Options:
ZZ_DDNS_TOKEN API Token
'
dns_zz_add() {
fulldomain="$1"
txtvalue="$2"
ZZ_TTL="${ZZ_TTL:-60}"
_info "Adding TXT record for $fulldomain"
_savedomainconf ZZ_DDNS_TOKEN "$ZZ_DDNS_TOKEN"
_zz_api PUT "{\"value\":\"$(_zz_json_escape "$txtvalue")\",\"ttl\":$ZZ_TTL}"
}
dns_zz_rm() {
fulldomain="$1"
txtvalue="$2"
_info "Removing TXT record for $fulldomain"
_zz_api DELETE "{\"value\":\"$(_zz_json_escape "$txtvalue")\"}"
}
_zz_api() {
method="$1"
data="$2"
ZZ_TTL="${ZZ_TTL:-60}"
ZZ_DDNS_TOKEN="${ZZ_DDNS_TOKEN:-}"
if [ -z "$ZZ_DDNS_TOKEN" ]; then
_err "ZZ_DDNS_TOKEN is not set"
return 1
fi
url="https://nic.zz.ac/api/ddns/records"
export _H1="Authorization: Bearer $ZZ_DDNS_TOKEN"
export _H2="Content-Type: application/json"
response="$(_post "$data" "$url" "" "$method")"
ret="$?"
_debug2 response "$response"
if [ "$ret" != "0" ]; then
_err "zz.ac API request failed"
return 1
fi
if _contains "$response" "\"ok\":true"; then
return 0
fi
_err "zz.ac API error: $response"
return 1
}
_zz_json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}关于 deSEC
ZZ.AC 域名支持使用 deSEC 来解析。而 deSEC 本身有完整的 API,可以实现 DDNS 功能。那为什么不直接用它呢?确实我还真用过,但它的最小 TTL 是一小时,对于 DDNS 功能来说就点太长了。想想我家里临时断电又恢复,ISP 下发了新的公网 IP 地址,但我的域名解析要到一个小时之后才能生效,完全不能接受😷
而 ZZ.AC 官方的 DDNS 基本可以做到两分钟之内生效,而且还有优化的空间。
有了 DDNS 功能,我就可以离摆脱 Cloudflare 又近了一步,离去中心化的互联网又近了一步。













