← 专栏目录:RouterOS 网络实践

技术 / Network

RouterOS Cloudflare DDNS 配置

用 RouterOS 脚本安全更新 Cloudflare AAAA 记录:校验证书、序列化 JSON,并在 API 确认成功后缓存地址。

文章目录

本篇由原始笔记整理,凭据已替换成占位符。以下模板按 RouterOS 7 官方文档校订,要求设备支持 :serialize、:deserialize 和 json.no-string-conversion;本轮未在真实路由器上执行。先在测试设备验证,再启用定时任务。

需要准备

  • 仅允许编辑目标 Zone DNS 的 Cloudflare API Token。
  • Zone ID、已有 AAAA 记录的 Record ID,以及对应域名,例如 home.example.com。
  • 拥有公网 IPv6 的接口名称,例如 pppoe-out1;如果地址实际配置在 bridge/LAN 上,应填该接口。
  • 正确的设备时间及可信 CA 证书。模板使用 check-certificate=yes;证书验证失败时先修复时间、信任链或 RouterOS 版本,不要关闭验证后发送 Token。

实际 Token 不要放进公开文章、截图或日志。RouterOS 脚本配置和备份也可能包含凭据,应限制管理权限。

地址选择与 JSON

模板只选择指定接口上启用且有效的 2000::/3 全球单播地址,去掉 /64 等前缀长度。没有匹配地址或有多个匹配地址时停止,避免把链路本地地址、ULA 或任意一个候选地址写进公网 DNS;多地址环境应按自己的地址规划进一步收窄条件。

请求体通过 :serialize 构造,避免手工拼接 JSON 时丢失引号转义。响应也要解析并确认 success=true,不能只凭 HTTP 请求结束就认定更新成功。

Cloudflare 更新脚本模板

在 System → Scripts 创建名为 CloudflareDDNS 的脚本,将以下内容作为完整脚本保存,替换前五个配置值。脚本的 Policy 勾选 read、write、test,与下方 scheduler 的权限保持一致;不要保留脚本默认全选的 Policy 后再用权限不足的 scheduler 调用。每次执行时局部变量在同一脚本作用域内;不要逐行在终端运行片段。

:local CFAPITOKEN "YOUR_CLOUDFLARE_API_TOKEN";
:local CFZONEID "YOUR_ZONE_ID";
:local CFRECORDID "YOUR_RECORD_ID";
:local CFDNSNAME "home.example.com";
:local WANINTERFACE "pppoe-out1";
:global LAST6;
:local PUB6 "";
:local candidates 0;

:foreach item in=[/ipv6/address/print as-value where interface=$WANINTERFACE disabled=no invalid=no] do={
  :local address ($item->"address");
  :if ($address ~ "^[23]") do={
    :set PUB6 [:pick $address 0 [:find $address "/"]];
    :set candidates ($candidates + 1);
  }
}

:if ($candidates != 1) do={
  :error "CF-DDNS: expected exactly one global IPv6 address; check interface and address selection";
}

:if ($PUB6 != $LAST6) do={
  :local payload [:serialize to=json options=json.no-string-conversion value={"type"="AAAA";"name"=$CFDNSNAME;"content"=$PUB6;"ttl"=120;"proxied"=false}];
  :local result [/tool fetch check-certificate=yes http-max-redirect-count=0 http-method=put output=user as-value url=("https://api.cloudflare.com/client/v4/zones/" . $CFZONEID . "/dns_records/" . $CFRECORDID) http-header-field=("Authorization: Bearer " . $CFAPITOKEN . ",Content-Type: application/json") http-data=$payload];
  :local response [:deserialize from=json value=($result->"data")];
  :if ((($result->"status") = "finished") && (($response->"success") = true)) do={
    :set LAST6 $PUB6;
    :log info "CF-DDNS: AAAA record updated";
  } else={
    :error "CF-DDNS: API did not confirm success; cached address unchanged";
  }
} else={
  :log info "CF-DDNS: IPv6 unchanged, skipping";
}

LAST6 仅在 Cloudflare 确认成功后更新;网络、TLS、HTTP 或解析错误会终止本次脚本,下一次仍会重试。模板禁用 HTTP 重定向,避免带凭据的请求被转发到其他地址。不要在日志中输出 Token 或完整请求头。

缓存只代表本路由器上次成功提交的地址,不能检测别人修改 DNS。手动修改记录、接口或域名后,可先清空 LAST6 再运行。多域名脚本应使用不同缓存变量,避免互相影响。

手动验证与定时任务

先手动执行:

/system script run CloudflareDDNS
/log print where message~"CF-DDNS"

在 Cloudflare 控制台或独立 DNS 查询中核对 AAAA 记录。再运行一次应跳过更新;清空缓存后,应重新提交:

:global LAST6; :set LAST6 "";
/system script run CloudflareDDNS

手动测试通过后再添加定时任务,避免重复创建同名任务:

/system scheduler add name=CloudflareDDNS interval=5m on-event="/system script run CloudflareDDNS" policy=read,write,test

脚本与 scheduler 的运行用户和策略会影响执行结果;也要从 scheduler 触发一次并核对日志,不能只验证管理员手动运行。按设备版本和实际权限需求配置,不要为排错直接赋予所有权限。

排错与参考

  • 不支持 JSON 命令:先核对 RouterOS 版本及官方脚本命令支持情况。
  • 证书错误:检查系统时间、可信根证书及 CRL 访问,保留 check-certificate=yes。
  • 找不到唯一地址:检查接口、IPv6 地址分配及多地址选择条件。
  • HTTP/API 失败:检查 Token 的最小权限、Zone ID、Record ID、记录类型和域名是否对应。
  • 提交后仍需重试:缓存没有在失败时更新,这是预期行为。

参考:MikroTik Fetch、RouterOS 脚本与 JSON 序列化、Cloudflare 更新 DNS 记录 API。