侧边栏壁纸
博主头像
龍騰博客 博主等级

行动起来,活在当下

  • 累计撰写 182 篇文章
  • 累计创建 31 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

RouterOS V7.15 Cloudflare DDNS 部署保姆级教程

管理员
2026-08-22 / 0 评论 / 0 点赞 / 5 阅读 / 0 字

适用场景:软路由跑 RouterOS 7.15,PPPoE 拨号上网,域名解析用 Cloudflare 管理,需要动态更新公网 IP。


一、整体思路

路由器定时执行一个脚本:直接从 PPPoE 拨号接口读取当前公网 IP → 查询 Cloudflare 上该域名当前解析的 IP → 两者不一致就调用 Cloudflare API 更新。


二、确认 WAN 拨号接口名称

终端执行:

/interface pppoe-client print

输出里 name 那一列就是接口名(常见是 pppoe-out1),记下来,后面脚本要用。


三、Cloudflare 侧准备

  1. 打开 https://dash.cloudflare.com → 右上角头像 → My Profile → API Tokens → Create Token

  2. 选择 Create Custom Token

    • Permissions:Zone - DNS - Edit

    • Zone Resources:Specific zone,选中你要更新的域名(不要选全部域名,权限最小化)

  3. 创建后复制生成的 Token(只显示一次,务必先存好)

  4. 打开该域名的 Cloudflare Dashboard 首页,右侧栏 API 区域,复制 Zone ID

  5. 在 Cloudflare 上手动先建好一条 A 记录(比如 home.dragonwebs.vicp.net),IP 随便填一个占位值(如 1.1.1.1),脚本会自动把它改成真实 IP


四、导入 CA 根证书(解决 HTTPS 证书校验问题)

RouterOS 默认对 HTTPS 请求做证书链校验,不导入证书会直接报错 SSL: no trusted CA certificate found

1)下载完整的 CA 证书包(Mozilla 官方信任列表):

/tool fetch url="https://curl.se/ca/cacert.pem" dst-path=cacert-full.pem check-certificate=no

(这一步用 check-certificate=no 是因为此刻信任链还没建立,属于先有鸡还是先有蛋的特例,仅用于首次获取证书文件)

2)确认下载成功:

/file print where name~"cacert-full"

正常应显示一个约 180KB 左右的 .pem 文件。

3)导入证书:

/certificate import file-name=cacert-full.pem

提示 passphrase 时直接回车。正常返回类似:

certificates-imported: 120

(一百多个根证书就说明成功,涵盖 DigiCert、ISRG/Let's Encrypt、Amazon、Google 等主流签发机构)

4)确认证书库:

/certificate print

能看到一长串证书列表,T 标记代表已信任即可。


五、创建 DDNS 脚本

用户配置区(部署时必须修改的 5 项):

:local wanInterface "pppoe-out1"
:local cfToken "你的CloudflareAPIToken"
:local cfZoneID "你的ZoneID"
:local cfRecordName "home.dragonwebs.vicp.net"
:local checkCert "yes-without-crl"

完整脚本正文:

:local wanInterface "pppoe-out1"
:local cfToken "你的CloudflareAPIToken"
:local cfZoneID "你的ZoneID"
:local cfRecordName "home.dragonwebs.vicp.net"
:local checkCert "yes-without-crl"
# ========== 用户配置区结束 ==========

:log info "cf-ddns: 开始检测"

# ---- 1. 直接从WAN接口读取当前公网IP ----
:local dipaddr ""
:do {
    :local rawAddr [/ip address get [/ip address find interface=$wanInterface] address]
    :local slashPos [:find $rawAddr "/"]
    :set dipaddr [:pick $rawAddr 0 $slashPos]
} on-error={
    :log error ("cf-ddns: 未能从接口 " . $wanInterface . " 读取到地址,请检查接口名称/拨号是否成功")
}

:if ([:len $dipaddr] = 0) do={
    :error "cf-ddns: dipaddr为空,脚本终止"
}

:log info ("cf-ddns: 当前WAN口公网IP = " . $dipaddr)

# ---- 2. 查询 Cloudflare 上该记录当前的值 ----
:local cfQueryUrl ("https://api.cloudflare.com/client/v4/zones/" . $cfZoneID . "/dns_records?type=A&name=" . $cfRecordName)
:local cfData ""

:do {
    :local cfResult [/tool fetch url=$cfQueryUrl http-method=get \
        http-header-field=("Authorization: Bearer " . $cfToken . ",Content-Type: application/json") \
        output=user as-value check-certificate=$checkCert]
    :set cfData ($cfResult->"data")
} on-error={
    :log error "cf-ddns: 查询Cloudflare记录失败(网络/证书/Token问题),脚本终止"
}

:if ([:len $cfData] = 0) do={
    :error "cf-ddns: Cloudflare返回数据为空,退出"
}

# ---- 3. 从返回JSON里提取 record id 和 当前 content(IP) ----
:local idPos [:find $cfData "\"id\":\""]
:if ([:typeof $idPos] = "nil") do={
    :error "cf-ddns: 未找到记录ID。请确认: 1)域名拼写与Cloudflare上完全一致 2)Cloudflare上已存在这条A记录 3)ZoneID正确"
}
:local idStart ($idPos + 6)
:local idEnd [:find $cfData "\"" $idStart]
:local recordID [:pick $cfData $idStart $idEnd]

:local contentPos [:find $cfData "\"content\":\""]
:local contentStart ($contentPos + 11)
:local contentEnd [:find $cfData "\"" $contentStart]
:local currentDNS [:pick $cfData $contentStart $contentEnd]

:log info ("cf-ddns: Cloudflare当前解析IP = " . $currentDNS . " , recordID = " . $recordID)

# ---- 4. 比较并按需更新 ----
:if ($dipaddr = $currentDNS) do={
    :log info "cf-ddns: IP未变化,无需更新"
} else={
    :log info "cf-ddns: 检测到IP变化,开始更新Cloudflare记录"

    :local patchUrl ("https://api.cloudflare.com/client/v4/zones/" . $cfZoneID . "/dns_records/" . $recordID)
    :local patchData ("{\"type\":\"A\",\"name\":\"" . $cfRecordName . "\",\"content\":\"" . $dipaddr . "\",\"ttl\":120,\"proxied\":false}")

    :do {
        /tool fetch url=$patchUrl http-method=patch \
            http-header-field=("Authorization: Bearer " . $cfToken . ",Content-Type: application/json") \
            http-data=$patchData output=none check-certificate=$checkCert
        :log info ("cf-ddns: 更新成功! " . $cfRecordName . " -> " . $dipaddr)
    } on-error={
        :log error "cf-ddns: 更新Cloudflare记录失败,请检查Token权限"
    }
}

创建为 script 对象(WinBox 图形方式):

System → Scripts → 加号(+),Name 填 cf-ddns,Policy 勾选 read, write, test, ftp,把上面脚本正文粘贴进 Source 框,点 OK。

或者用终端命令:

/system script add name=cf-ddns policy=read,write,test,ftp source={
<脚本正文粘贴在这里>
}

六、手动测试

/system script run cf-ddns
/log print where message~"cf-ddns"

正常日志顺序:

cf-ddns: 开始检测
cf-ddns: 当前WAN口公网IP = xxx.xxx.xxx.xxx
cf-ddns: Cloudflare当前解析IP = ... , recordID = ...
cf-ddns: 检测到IP变化,开始更新Cloudflare记录
cf-ddns: 更新成功! xxx -> xxx.xxx.xxx.xxx

第一次跑因为占位 IP 和真实 IP 不同,一定会触发更新,去 Cloudflare Dashboard 刷新确认 A 记录已变成真实 IP。再跑一次应显示:

cf-ddns: IP未变化,无需更新

说明比较逻辑正常。


七、设置定时任务

/system scheduler add name=cf-ddns-timer interval=5m start-time=startup on-event="/system script run cf-ddns"

确认已添加:

/system scheduler print

interval=5m 即每 5 分钟检查一次,可按需调整为 10m30m,Cloudflare API 限额很宽松。


八、故障排查表

现象

排查方向

SSL: no trusted CA certificate found

未导入CA证书,或导入的证书链覆盖不全,重新走第四步导入完整证书包;实在解决不了就把 checkCert 改成 "no",跳过证书校验(不影响加密,只是不验证服务器身份,家用场景风险可接受)

查询Cloudflare返回401

Token 复制错了,或已过期/被删除

查询Cloudflare返回403

Token 的 Zone Resources 没勾对这个域名

未找到记录ID

Cloudflare上没有预先手动建这条A记录,或 cfRecordName 拼写和Cloudflare上不完全一致(大小写、空格)

未能从接口读取到地址

wanInterface 名字填错,回第二步用 /interface pppoe-client print 核对;或PPPoE当前未拨号成功

想单独验证Token是否有效

/tool fetch url="https://api.cloudflare.com/client/v4/user/tokens/verify" http-header-field=("Authorization: Bearer 你的token") output=user as-value


九、日常维护

  • 每隔几天执行一次 /log print where message~"cf-ddns" 抽查日志,确认没有反复报错

  • 如果一开始用 checkCert="no" 跑通的,之后有空可以重新走一遍第四步导入更完整的证书包,再切回 "yes-without-crl",不影响当前功能正常使用

  • Token 建议定期(比如半年到一年)在 Cloudflare 后台轮换一次,安全性更好

0

评论区