RouterOS V7 CloudFlare 动态域名脚本
刚注意到我的一台路由器的 DDNS 功能失灵了,看了一下用的是一个从 github 上找的支持 cloudflare DNS 的脚本,现在一执行就报错。本想改改继续用,觉得这个脚本不够优雅,还要生成临时文件。于是就自己写了一个,特点是支持多路 PPPOE 拨号,不生成临时文件,也不定义临时的全局变量,并支持发送钉钉消息(如不需要把 dingtalk 值留空或相关语句屏蔽即可)。
脚本实际上分为两个,第一个脚本放在 /ppp/Profiles 那里,打开 pppoe 拨号使用的那个 profile 条目,在 scripts 标签页下的 On Up 编辑框里填写如下内容:
# 定义要调用的 DDNS 脚本名称
:local scrName "CloudFlare_DDNS"
# 获取拨号接口名称
:local ifname [/interface get $interface name];
# 获取公网IP
:local currentIP $"local-address";
# 定义要传输的变量
:local var1 "ifname=$ifname"
:local var2 "currentIP=$currentIP"
# 调用 DDNS 脚本并注入变量
:execute script="[[:parse \"[:parse [/system script get $scrName source]] $var1 $var2\"]]"
第二个脚本在 /system/scripts 处添加,命名为“CloudFlare_DDNS”,内容如下:
# =========================================================
# --- Cloudflare DDNS 全自动脚本 for RouterOS v7 ---
# --- 自动从On Up事件获取IP和接口 ---
# =========================================================
# ----------------------------------------------------
# --- 参数配置 ---
# ----------------------------------------------------
# CloudFlare Token(DNS 权限)
:local cfToken "你的 Token 数据"
# CloudFlare 区域 ID
:local cfZoneId "你的区域 ID"
# --- 接口 -> 域名 映射关系 (在这里扩展) ---
# 如果只有一条拨号线路,只须将域名赋值给 cfDomain,将运营商名字赋值给 ISP;
# 后面两行 if 语句屏蔽即可。
:local cfDomain "lt.yourdomain.com"
:local ISP "联通"
# 多条线路时使用
# 如果有多条拨号线路,上面的 cfDomain 和 ISP 两个值留空(不可删除);
# 取消下面两条 if 语句前面的 #,并按实际情况填写 ifname(拨号接口名称)、cfDomain 和 ISP。
# if ($ifname = "pppoe-out-LT") do={:set cfDomain "lt.yourdomain.com";:set ISP "联通"}
# if ($ifname = "pppoe-out-YD") do={:set cfDomain "yd.yourdomain.com";:set ISP "移动"}
# 如果有第三个拨号,在这里继续添加。
# --- 调用钉钉发送通知(可选,关键字方式)---
# 不需要钉钉时可以将 url 变量留空,或者全部删除
:global dingtalk do={
:local keyword "关键字";
:local url "https://oapi.dingtalk.com/robot/send?access_token=你的钉钉token"
:local now ([/system/clock get date] ." ".[/system/clock get time])
:local data "{\"msgtype\": \"text\", \"text\": {\"content\": \"$now $1 $keyword\"}}"
:local header "Content-Type:application/json";
:log info "data: $data"
if ([:len $url] > 0) do={
do {
/tool/fetch http-method=post mode=https http-header-field="$header" http-data="$data" url="$url"
} on-error={log error "发送钉钉消息失败。"}
}
}
# ----------------------------------------------------
# --- 合法性检查 ---
# 接口检查
if ([:len $ifname] = 0) do={
:log warning "未获取到 PPPOE 接口名称,脚本退出。"
:quit
}
# 域名检查
if ([:len $cfDomain] = 0) do={
:log warning "域名为空,脚本退出。"
:quit
}
# 公网 IP 检查
if ([:len $currentIP] = 0) do={
:log warning "未获取到公网 IP 地址,脚本退出。"
:quit
} else={:log info "$ISP 接口名称:$ifname,公网 IP 地址:$currentIP,匹配域名:$cfDomain"}
# --- 查询Cloudflare上的DNS记录信息 ---
:local recordId ""
:local dnsIP ""
:local recordQueryUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records?name=$cfDomain&type=A"
:local queryResponse [/tool fetch url=$recordQueryUrl http-header-field="Authorization: Bearer $cfToken" as-value output=user]
if ([:find ($queryResponse->"data") "\"success\":true"] != nil) do={
:local responseData ($queryResponse->"data")
:local idStart [:find $responseData "\"id\":\""]
if ($idStart != nil) do={
:set idStart ($idStart + 6); :local idEnd [:find $responseData "\"" $idStart]; :set recordId [:pick $responseData $idStart $idEnd]
:local contentStart ([:find $responseData "\"content\":\""] + 11); :local contentEnd [:find $responseData "\"" $contentStart]; :set dnsIP [:pick $responseData $contentStart $contentEnd]
:log info "$ISP接口:域名 $cfDomain 当前解析记录为: $dnsIP"
} else={ :log error "$ISP接口:API调用成功,但未找到域名 $cfDomain 的 A 类型记录。"; :error "未找到DNS记录" }
} else={ :log error "$ISP接口:查询 DNS 信息失败。"; :error "查询DNS信息失败" }
# --- 对比IP并决定是否更新 ---
if ([:len $recordId] > 0) do={
if ($currentIP = $dnsIP) do={
:log info "$ISP接口:IP地址 ($currentIP) 未变更,无需更新。"
} else={
:log warning "$ISP接口:IP地址已变更 ($dnsIP -> $currentIP)。准备更新..."
# --- 执行更新操作 ---
:local cfApiUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records/$recordId"
:local jsonData "{\"type\":\"A\",\"name\":\"$cfDomain\",\"content\":\"$currentIP\",\"ttl\":120,\"proxied\":false}"
:local updateResponse [/tool fetch url=$cfApiUrl http-method=put http-header-field="Authorization: Bearer $cfToken,Content-Type: application/json" http-data=$jsonData as-value output=user]
if ([:find ($updateResponse->"data") "\"success\":true"] != nil) do={
:local msg "$ISP接口:公网 IP 变更为 $currentIP,域名 $cfDomain 的DNS记录更新成功。"
:log info $msg
# ---发送钉钉消息,可选 ---
[$dingtalk $msg]
} else={ :log error "$ISP接口:DNS记录更新失败。" }
}
}
CloudFlare 的 Token 和 ZoneId 数据需要自行在 CloudFlare 官网上去设置或查找。
和其它脚本需要手工指定拨号接口、从 /ip/address 处查询 IP、甚至需要生成全局变量或临时文件不同,这个脚本的有趣之处在利用 On Up 事件时系统返回的端口 ID 号和 IP 地址,并巧妙地传递给了修改域名解析的脚本(RouterOS 脚本并不支持命令行方式传入参数)。