利用 Cloudflare Worker 控制 Cloudflare API 实现 ANAME

利用 Cloudflare Worker 控制 Cloudflare API 实现 ANAME

ANAME 是什么?

ANAME 记录(也称为ALIAS记录)是一个自定义的 DNS 记录类型,并不是所有的 DNS 提供商都支持。ANAME 记录允许一个根域名(例如example.com而不是www.example.com)映射到另一个域名,类似于CNAME记录,但同时允许像A记录那样映射到IP地址。

ANAME 和 CNAME 主要的区别在于:

这使得ANAME记录特别适用于需要根域名指向某个动态IP地址的服务,比如某些托管在CDN上的网站。在背后,DNS提供商会实时解析配置的目标域名,并将解析结果作为A记录返回给发起请求的DNS客户端。

ANAME 记录的缺点是它通常依赖于DNS提供商提供的特定服务和解析器逻辑,而不是标准化的DNS记录类型。这意味着,如果你把你的DNS托管服务从支持ANAME记录的提供商迁移到不支持的提供商,你可能需要寻找替代方法来实现相同的功能。

Cloudflare ANAME 支持程度

目前 Cloudflare 并不支持 ANAME 功能,但是免费版根域名的 CNAME 拉平功能可以实现类似 ANAME 的效果,子域名无法使用 CNAME 拉平功能。

Cloudflare CNAME 拉平

利用 Cloudflare Worker 实现 ANAME 类似效果

原理

  1. 利用 DNS Over HTTPS 查询目标 CNAME 域名 IP
  2. 通过 Cloudflare API 设置子域名的 IP

Worker 代码

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run "npm run dev" in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run "npm run deploy" to publish your worker
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

export default {
  async scheduled(event, env, ctx) {
    const target = 'cname.exampledns.com';
    const subdomain  = 'sub.example.com';

    const ip = await getIP(target);
    console.log(await dns(subdomain, ip));
  }
};


async function getIP(domain) {
  let dnsQueryURL = `https://cloudflare-dns.com/dns-query?name=${domain}&type=A`

  let dnsResponse = await fetch(dnsQueryURL, {
    method: 'GET',
    headers: {
      'accept': 'application/dns-json',
    },
  })

  let dnsData = await dnsResponse.json()
  let ip = dnsData.Answer ? dnsData.Answer[0].data : null

  return ip;
}


async function dns(subdomain, newIp) {
  const zoneId = 'your zone id'; // Cloudflare Zone ID
  const apiToken = 'your api token'; // Cloudflare API Token

  const baseUrl = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;

  const headers = {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json',
  };

  // Step 1: List DNS records to check if the A record already exists
  let existingRecord = null;
  let response = await fetch(`${baseUrl}?name=${subdomain}&type=A`, { headers });
  const records = await response.json();
  if (records && records.result && records.result.length > 0) {
    existingRecord = records.result[0];
  }

  // Step 2: Decide whether to update an existing record or create a new one
  let method = 'POST';
  let url = baseUrl;
  if (existingRecord) { // Record exists, prepare to update
    method = 'PUT';
    url = `${baseUrl}/${existingRecord.id}`;
  }

  // Step 3: Perform the actual update or create operation
  response = await fetch(url, {
    method: method,
    headers: headers,
    body: JSON.stringify({
      type: 'A',
      name: `${subdomain}`,
      content: newIp,
      ttl: 1, // can be set to 1 for automatic
      proxied: false, // can be set as required
    }),
  });

  const result = await response.json();

  // The response for debugging purposes
  return result;
}

设置定时触发器

每5分钟执行查询 CNAME 目前域名 IP 并更新。

设置定时触发器