使用 Cloudflare 隧道 IP 更新 DDNS

年前在家里闲不住,决定重新捡起来写代码开源的爱好。正好办公室的公网访问不是特别稳定,正好解决这个问题。

办公室宽带有动态公网 IPv4 和 IPv6,常见的路由器一般都支持 DDNS,但是办公室有双路接入,它不支持同时更新多个 IP。

之前的解决方案是服务器通过 Cloudflare Zero Trust Tunnel 进行内网穿透,绕一圈以后向 Internet 提供服务已经没有问题。Intranet 和 SSH、RDP、SQL 等非网页访问此前一直使用 Tailscale VPN,但是既然有公网 IP,当然是尽可能少一层重复加密了。

鉴于所有服务都在 Cloudflare 上,使用 Cloudflare Worker 解决这个问题是最合适的方法。于是我写了一个简单的项目:

  1. 轮询 Cloudflare Zero Trust Tunnel 的状态,获得当前服务器的源 IP(可能有多个);
  2. 和 DNS A 记录匹配,更新 DNS 记录。

但是还有一个问题。Cloudflare 对于每一条记录有一个 DNS_ID,如果简单删除再重建,ID 会重置。因此,匹配时不能简单的先删再建,而是应该尽可能用修改代替。为此有必要采取以下的规则:

const records: Map<string, string> = new Map() // 记录。key 为 IP 地址,value 为 Record ID。
const ips: Set<string> = new Set() // 源 IP 地址。

const toCreate: Set<string> = new Set([...ips].filter((ip: string): boolean => !records.has(ip)))
// 剔除已经在当前记录中的源 IP,剩下的就是需要新建记录的 IP。

const toDelete: Set<string> = [...records].filter(([ip, id]: [string, string]): boolean => !ips.has(ip)).map(([ip, id]: [string, string]): string => id)
// 剔除已经在当前源 IP 中的记录,剩下的就是需要删掉的记录。

// 为了减少重复的删除和新建,尽可能将删除和新建变为变更
const toPatch: Map<string, string> = new Map(); // 记录。key 为 IP 地址,value 为 Record ID。

for (let i: number = 0; i < Math.min(toCreate.size, toDelete.size); i++) {
	const [content] = toCreate;
	const [id] = toDelete;
	toPatch.set(content, id);
	toCreate.delete(content);
	toDelete.delete(id);
}

项目开源在 Github,希望能帮到有类似需求,不想折腾路由器上的 DDNS(路由器上的 DDNS 支持 Cloudflare 的少)的朋友。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注