RouterOS v7 Cloudflare DDNS更新脚本
0x01 获取 Cloudflare DNS API Token
域名先接入 Cloudflare,NS 生效后再配置。
打开 API Tokens:
- 点击
Create Token,选择Edit zone DNS模板。 - 权限选
Zone → DNS → Edit。 Zone Resources选Include → Specific zone → 自己的域名。- 点击
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 连通性。