«

RouterOS v7 Cloudflare DDNS更新脚本

myluzh 发布于 阅读:6 RouterOS


0x01 获取 Cloudflare DNS API Token

域名先接入 Cloudflare,NS 生效后再配置。

打开 API Tokens:

  1. 点击 Create Token,选择 Edit zone DNS 模板。
  2. 权限选 Zone → DNS → Edit。
  3. Zone Resources 选 Include → Specific zone → 自己的域名。
  4. 点击 Continue to summary → Create Token,复制 Token,只显示一次。

家宽 IP 会变,Client IP Address Filtering 留空。

Zone ID 在域名的 Overview → API 区域复制。

0x02 参数

apiToken:上面创建的 Token。

zoneId:域名的 Zone ID。

fqdn:要更新的完整域名,比如 ddns.example.com。

wanInterface:PPPoE 接口名称,比如 pppoe-out1。

需要别名的话,添加灰云 CNAME:home.example.com → ddns.example.com,TTL 设 60 秒。

0x03 更新脚本

我这里是 RouterOS 7.21.2。脚本读取 PPPoE 公网 IPv4,自动创建或更新 A 记录,固定灰云、TTL 60 秒;多条同名 A 记录会报错。

# Cloudflare DDNS: IPv4 from the native PPPoE interface.
:global CfExampleDDNSBusy
:if ($CfExampleDDNSBusy = true) do={ :return 0 }
:set CfExampleDDNSBusy true
:local apiToken "YOUR_CLOUDFLARE_API_TOKEN"
:local zoneId "YOUR_ZONE_ID"
:local fqdn "ddns.example.com"
:local wanInterface "pppoe-out1"
:global CfExampleDDNSLastIP
:global CfExampleDDNSLastCheck
:do {
    :local wanState [/interface pppoe-client monitor $wanInterface once as-value]
    :local currentIP [:tostr ($wanState->"local-address")]
    :if ([:len $currentIP] = 0) do={ :error "No PPPoE address" }
    :local ip [:toip $currentIP]
    :if ([:typeof $ip] != "ip") do={ :error "Invalid IPv4 address" }
    :if (($ip in 0.0.0.0/8) || ($ip in 10.0.0.0/8) || ($ip in 100.64.0.0/10) || ($ip in 127.0.0.0/8) || ($ip in 169.254.0.0/16) || ($ip in 172.16.0.0/12) || ($ip in 192.168.0.0/16) || ($ip in 198.18.0.0/15) || ($ip in 224.0.0.0/3)) do={ :error "PPPoE address is not public" }
    :local now [/system resource get uptime]
    :if ([:typeof $CfExampleDDNSLastCheck] != "time") do={ :set CfExampleDDNSLastCheck 0s }
    :if (!(($CfExampleDDNSLastIP = $currentIP) && ($now >= $CfExampleDDNSLastCheck) && (($now - $CfExampleDDNSLastCheck) < 1h))) do={
        :local endpoint ("https://api.cloudflare.com/client/v4/zones/" . $zoneId . "/dns_records")
        :local headers ("Authorization: Bearer " . $apiToken . ",Content-Type: application/json")
        :local fetched [/tool fetch url=($endpoint . "?type=A&name=" . $fqdn) http-method=get http-header-field=$headers check-certificate=yes http-max-redirect-count=0 duration=20s output=user as-value]
        :local reply [:deserialize from=json value=($fetched->"data")]
        :if (($reply->"success") != true) do={ :error "Cloudflare lookup rejected" }
        :local records ($reply->"result")
        :local count [:len $records]
        :if ($count > 1) do={ :error "Multiple A records; manual review required" }
        :local needsWrite true
        :local method "post"
        :local writeUrl $endpoint
        :if ($count = 1) do={
            :local record ($records->0)
            :set method "patch"
            :set writeUrl ($endpoint . "/" . ($record->"id"))
            :if (([:tostr ($record->"content")] = $currentIP) && (($record->"proxied") = false) && (($record->"ttl") = 60)) do={ :set needsWrite false }
        }
        :if ($needsWrite) do={
            :local payload [:serialize to=json options=json.no-string-conversion value={"type"="A";"name"=$fqdn;"content"=$currentIP;"ttl"=60;"proxied"=false}]
            :set fetched [/tool fetch url=$writeUrl http-method=$method http-header-field=$headers http-data=$payload check-certificate=yes http-max-redirect-count=0 duration=20s output=user as-value]
            :set reply [:deserialize from=json value=($fetched->"data")]
            :if (($reply->"success") != true) do={ :error "Cloudflare update rejected" }
            :local updated ($reply->"result")
            :if ((([:tostr ($updated->"content")]) != $currentIP) || (($updated->"name") != $fqdn) || (($updated->"type") != "A") || (($updated->"proxied") != false) || (($updated->"ttl") != 60)) do={ :error "Unexpected update result" }
            :log info ("CF-DDNS: " . $fqdn . " -> " . $currentIP)
        }
        :set CfExampleDDNSLastIP $currentIP
        :set CfExampleDDNSLastCheck $now
    }
} on-error={
    :set CfExampleDDNSBusy false
    :log error "CF-DDNS: check/update failed; will retry next scheduled run"
    :error "CF-DDNS failed"
}

:set CfExampleDDNSBusy false

首次运行或 IP 变化时查询 CF;IP 没变,每小时核对一次。只更新 IPv4。

复制脚本更新其他域名时,把三个全局变量的 CfExampleDDNS 前缀一起改掉,避免共用状态。

0x04 定时执行

WinBox 进入 System → Scheduler,新建任务:

项目 值
Name cf_ddns
Start Time startup
Interval 00:00:30
Policy read,write,policy,test
On Event 粘贴完整脚本

手动运行:

{
    :local run [:parse [/system scheduler get [find where name="cf_ddns"] on-event]]
    $run
}

迁移完成后,禁用旧 DNSPod 任务。

0x05 验证

查看任务和日志:

/system scheduler print proplist=name,disabled,interval,run-count,next-run where name="cf_ddns"
/log print where message~"CF-DDNS:"

实际更新时才输出日志,格式如下:

CF-DDNS: ddns.example.com -> 203.0.113.10

电脑上查询,结果应为当前公网 IP:

dig ddns.example.com A +noall +answer
dig home.example.com A +noall +answer

TTL 可能显示缓存剩余时间。更新失败先检查 Token 权限、Zone ID、接口名称、路由器时间和 API 连通性。

routeros ddns ros7 cloudflare


正文到此结束
版权声明:若无特殊注明,本文皆为 Myluzh Blog 原创,转载请保留文章出处。
文章内容:https://itho.cn/ros/621.html
文章标题:《RouterOS v7 Cloudflare DDNS更新脚本》