标签 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 脚本并不支持命令行方式传入参数)。