目 录CONTENT

文章目录

【运维】RouterOS批量DDNS脚本实现指南

EulerBlind
2025-06-30 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

概述

动态DNS(DDNS)是网络管理中的重要技术,特别是对于使用动态IP地址的家庭或小型企业网络。当需要管理多个域名解析记录时,手动更新变得繁琐且容易出错。本文将详细介绍如何在RouterOS设备上实现批量DDNS更新脚本,支持CloudFlare DNS服务,并能够保持原有的CDN代理设置。

技术原理

DDNS工作机制

DDNS的核心工作原理:

  1. IP监测:定期检查WAN接口的公网IP地址
  2. 变化检测:与之前记录的IP地址进行比较
  3. 批量更新:当IP发生变化时,批量更新所有相关域名记录
  4. 状态保持:保持CloudFlare CDN代理设置不变

CloudFlare API集成

脚本通过CloudFlare API v4实现DNS记录管理:

  • 认证方式:使用Bearer Token进行API认证
  • 操作权限:需要Zone:Read和Zone:Edit权限
  • 批量处理:单次脚本执行可更新多个域名记录
  • 状态保持:读取并保持每个记录的代理状态

脚本功能特性

核心功能

  1. 自动IP检测:支持PPPoE、静态IP等多种WAN接口类型
  2. 批量域名管理:一次性管理多个子域名记录
  3. 代理状态保持:保持CloudFlare CDN代理设置不变
  4. 错误恢复:初始化时自动创建配置文件
  5. 调试支持:可开启详细的调试日志

安全特性

  • API密钥保护:使用环境变量或安全配置存储密钥
  • 权限最小化:仅请求必要的DNS操作权限
  • 请求限制:内置API调用间隔,避免触发频率限制

完整配置模板


################# CloudFlare 批量DDNS脚本 (保持CDN设置版本) #################

# 是否开启debug调试模式

:local CFDebug "false"

# 是否开启CFcloud功能(使用RouterOS云服务获取公网IP)

:local CFcloud "false"

  

# 修改为有公网IP的接口名称(例如:pppoe、ether1等)

:global WANInterface "pppoe"

  

# CloudFlare 全局密钥token或者有权限操作解析域名的token

:local CFtkn "your_cloudflare_token_here"

  

# 域名zoneId(在CloudFlare控制台右侧栏可以找到)

:local CFzoneid "your_zone_id_here"

  

# 记录类型 一般无需修改

:local CFrecordType "A"

  

# 记录ttl值,一般无需修改

:local CFrecordTTL "120"

  

# 定义所有需要更新的域名和对应的记录ID

# 格式:"域名"="记录ID"

:local domainList {

"api.example.com"="record_id_1";

"blog.example.com"="record_id_2";

"chat.example.com"="record_id_3";

"home.example.com"="record_id_4"

}

:log info "开始批量更新解析记录..."

################# 内部变量 variables #################

:local previousIP ""

:global WANip ""

################# 获取DNS记录当前代理状态的函数 #################

:local getCurrentProxyStatus do={

:local recordId $1;

:local zoneId $2;

:local token $3;

:local debug $4;

:local CFurl "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId";

:local tmpFile "cf-record-$recordId.tmp";

:do {

/tool fetch http-method=get mode=https url="$CFurl" http-header-field="Authorization:Bearer $token" dst-path="$tmpFile";

:delay 1s;

:if ([/file find name=$tmpFile] != "") do={

:local content [/file get [/file find name="$tmpFile"] contents];

/file remove $tmpFile;

# 简单解析JSON,查找proxied字段

:local proxiedPos [:find $content "\"proxied\":"];

:if ($proxiedPos != "") do={

:local valueStart ($proxiedPos + 10);

:local valueEnd [:find $content "," $valueStart];

:if ($valueEnd = "") do={ :set valueEnd [:find $content "}" $valueStart]; }

:local proxiedValue [:pick $content $valueStart $valueEnd];

:if ($debug = "true") do={

:log info ("CF: 记录 $recordId 当前代理状态: $proxiedValue");

}

:if ($proxiedValue = "true") do={

:return "true";

} else={

:return "false";

}

}

}

} on-error={

:if ($debug = "true") do={

:log error ("CF: 获取记录 $recordId 代理状态失败,默认使用false");

}

:return "false";

}

:return "false";

}

################# 获取或设置以前的ip变量 #################

:if ($CFcloud = "true") do={

:set WANip [/ip cloud get public-address]

};

:if ($CFcloud = "false") do={

:local currentIP [/ip address get [/ip address find interface=$WANInterface ] address];

:set WANip [:pick $currentIP 0 [:find $currentIP "/"]];

};

# 定义统一的临时文件名

:local tmpFileName "batch-ddns-ip.txt"

# 检查并创建临时文件

:if ([/file find name=$tmpFileName] = "") do={

:log error "没有找到记录前一个公网IP地址的文件, 自动创建..."

:set previousIP $WANip;

:execute script=":put $WANip" file=[:pick $tmpFileName 0 [:find $tmpFileName "."]];

:delay 2s;

:log info ("CF: 初始化批量DDNS, 当前IP = $WANip")

# 批量更新所有域名(初始化时保持代理状态)

:foreach domain,recordId in=$domainList do={

:local CFurl "https://api.cloudflare.com/client/v4/zones/$CFzoneid/dns_records/$recordId";

:local proxied [$getCurrentProxyStatus $recordId $CFzoneid $CFtkn $CFDebug];

:log info ("CF: 初始化更新域名 $domain = $WANip (代理: $proxied)")

/tool fetch http-method=put mode=https output=none url="$CFurl" http-header-field="Authorization:Bearer $CFtkn,content-type:application/json" http-data="{\"type\":\"$CFrecordType\",\"name\":\"$domain\",\"ttl\":$CFrecordTTL,\"content\":\"$WANip\",\"proxied\":$proxied}"

:delay 1s;

}

:log info "初始化完成,已更新所有域名记录,脚本将继续执行正常检查"

} else={

:if ( [/file get [/file find name=$tmpFileName] size] > 0 ) do={

:global content [/file get [/file find name=$tmpFileName] contents] ;

:global contentLen [ :len $content ] ;

:global lineEnd 0;

:global line "";

:global lastEnd 0;

:set lineEnd [:find $content "\n" $lastEnd ] ;

:set line [:pick $content $lastEnd $lineEnd] ;

:set lastEnd ( $lineEnd + 1 ) ;

:if ( [:pick $line 0 1] != "#" ) do={

:set previousIP [:pick $line 0 $lineEnd ];

# 清理所有可能的空白字符

:if ([:find $previousIP "\r"] != "") do={

:set previousIP [:pick $previousIP 0 [:find $previousIP "\r"]];

}

:if ([:find $previousIP "\n"] != "") do={

:set previousIP [:pick $previousIP 0 [:find $previousIP "\n"]];

}

# 去除前后空格

:while ([:pick $previousIP 0 1] = " ") do={

:set previousIP [:pick $previousIP 1 [:len $previousIP]];

}

:while ([:pick $previousIP ([:len $previousIP] - 1) [:len $previousIP]] = " ") do={

:set previousIP [:pick $previousIP 0 ([:len $previousIP] - 1)];

}

}

} else={

:log warning ("CF: 临时文件 $tmpFileName 为空或不存在,将当前IP设为前一个IP")

:set previousIP ""

}

}

  

######## 将调试信息写入日志 #################

:log info ("CF: 前一个解析IP地址 = '$previousIP' (长度: " . [:len $previousIP] . ")")

:log info ("CF: 当前WAN接口IP = '$WANip' (长度: " . [:len $WANip] . ")")

:log info ("CF: 需要更新的域名数量 = " . [:len $domainList])

  

:if ($CFDebug = "true") do={

:log info ("CF: IP比较详情 - 前一个IP长度: " . [:len $previousIP] . ", 当前IP长度: " . [:len $WANip])

:log info ("CF: 前一个IP转hex: " . [:tostring $previousIP mode=hex])

:log info ("CF: 当前IP转hex: " . [:tostring $WANip mode=hex])

};

  

######## 比较并批量更新记录 #####

:if ($previousIP != $WANip) do={

:log info ("CF: IP地址已变更,开始批量更新解析记录: $previousIP -> $WANip")

:local updateCount 0;

:local successCount 0;

:local failCount 0;

# 批量更新所有域名(保持代理状态)

:foreach domain,recordId in=$domainList do={

:set updateCount ($updateCount + 1);

:local CFurl "https://api.cloudflare.com/client/v4/zones/$CFzoneid/dns_records/$recordId";

# 获取当前记录的代理状态

:local proxied [$getCurrentProxyStatus $recordId $CFzoneid $CFtkn $CFDebug];

:log info ("CF: [$updateCount/" . [:len $domainList] . "] 更新域名 $domain = $WANip (保持代理: $proxied)")

:do {

/tool fetch http-method=put mode=https url="$CFurl" http-header-field="Authorization:Bearer $CFtkn,content-type:application/json" output=none http-data="{\"type\":\"$CFrecordType\",\"name\":\"$domain\",\"ttl\":$CFrecordTTL,\"content\":\"$WANip\",\"proxied\":$proxied}"

:set successCount ($successCount + 1);

:log info ("CF: 成功更新域名 $domain (代理状态: $proxied)")

} on-error={

:set failCount ($failCount + 1);

:log error ("CF: 更新域名 $domain 失败")

}

# 添加延迟避免API限制

:delay 2s;

}

:log info ("CF: 批量更新完成! 成功: $successCount, 失败: $failCount, 总数: $updateCount")

# 清空DNS缓存

/ip dns cache flush

# 更新临时文件中的IP记录

:if ( [/file get [/file find name=$tmpFileName] size] > 0 ) do={

/file remove $tmpFileName

}

:execute script=":put $WANip" file=[:pick $tmpFileName 0 [:find $tmpFileName "."]]

} else={

:log info "CF: IP地址未发生改变,无需更新! 当前IP: $WANip"

}

  

:log info "批量DDNS脚本执行完成."

配置步骤详解

步骤1:获取CloudFlare凭据

  1. 登录CloudFlare控制台
  1. 获取Zone ID
  • 在域名概览页面右侧栏找到"Zone ID"
  • 复制并替换脚本中的 CFzoneid变量
  1. 创建API Token
  • 访问 API Tokens页面
  • 点击"Create Token"
  • 选择"Custom token"模板
  • 设置权限:
  • Zone:Zone:Read
  • Zone:DNS:Edit
  • 设置Zone Resources:选择对应的域名
  • 复制生成的Token替换 CFtkn变量

步骤2:获取DNS记录ID

使用以下API调用获取域名的记录ID:


# 替换YOUR_TOKEN和YOUR_ZONE_ID

curl -X GET "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/dns_records" \

-H "Authorization: Bearer YOUR_TOKEN" \

-H "Content-Type: application/json"

从返回的JSON中找到对应域名的 id字段,更新脚本中的 domainList

步骤3:RouterOS配置

  1. 上传脚本

# 通过WinBox或命令行上传脚本文件

/system script add name="batch-ddns" source=[/file get batch-ddns.script contents]

  1. 配置定时任务

# 每5分钟执行一次检查

/system scheduler add name="ddns-check" interval=00:05:00 on-event="/system script run batch-ddns"

  1. 验证WAN接口名称

# 查看接口列表

/interface print

# 查看IP地址配置

/ip address print

技术细节解析

IP地址检测机制

脚本支持两种IP获取方式:

  1. 本地接口检测 (CFcloud = "false")
  • 直接读取指定WAN接口的IP地址
  • 适用于PPPoE、静态IP等场景
  • 性能更好,无需外部依赖
  1. CloudFlare检测 (CFcloud = "true")
  • 使用RouterOS云服务获取公网IP
  • 适用于复杂网络环境
  • 可能存在延迟或依赖问题

代理状态保持机制

脚本的核心创新在于保持CDN代理设置:

  1. 状态读取:通过API GET请求获取当前记录的 proxied状态
  2. JSON解析:使用RouterOS字符串操作解析JSON响应
  3. 状态保持:在PUT请求中使用原有的代理设置
  4. 错误处理:获取失败时默认使用 false状态

文件系统管理

脚本使用临时文件记录IP变化:

  1. 初始化检查:首次运行时创建IP记录文件
  2. 内容解析:读取并清理文件中的IP地址
  3. 格式处理:去除换行符、回车符、空格等
  4. 原子更新:删除旧文件后创建新文件

故障排除

常见问题及解决方案

  1. API认证失败
  • 检查Token权限设置
  • 验证Zone ID是否正确
  • 确认Token未过期
  1. 记录ID错误
  • 重新获取DNS记录列表
  • 验证域名拼写
  • 检查记录类型是否为A记录
  1. IP检测异常
  • 确认WAN接口名称正确
  • 检查接口是否获得公网IP
  • 验证RouterOS版本兼容性
  1. 脚本执行失败
  • 开启调试模式查看详细日志
  • 检查文件系统权限
  • 验证网络连接状态

调试技巧

  1. 开启调试模式

:local CFDebug "true"

  1. 手动执行脚本

/system script run batch-ddns

  1. 查看系统日志

/log print where topics~"script"

性能优化建议

执行频率优化

  • 正常场景:5-10分钟间隔检查
  • 频繁变化:1-3分钟间隔检查
  • 稳定环境:15-30分钟间隔检查

API调用优化

  • 在域名较多时增加延迟间隔
  • 实施指数退避策略处理API限制
  • 考虑使用批量API调用(如果CloudFlare支持)

资源消耗控制

  • 定期清理临时文件
  • 限制日志文件大小
  • 监控脚本执行时间

扩展功能建议

多DNS服务商支持

可以扩展脚本支持其他DNS服务商:

  • 阿里云DNS
  • 腾讯云DNSPod
  • AWS Route 53
  • Google Cloud DNS

通知机制

添加IP变化通知功能:

  • 邮件通知
  • 微信通知
  • Telegram通知
  • 系统日志集成

高可用性增强

  • 多WAN接口支持
  • 故障转移机制
  • 配置备份与恢复
  • 健康检查机制

总结

本文介绍的RouterOS批量DDNS脚本提供了一个完整、可靠的解决方案,特别适合需要管理多个域名解析记录的场景。脚本的核心优势包括:

  • 批量处理:单次执行更新多个域名
  • 状态保持:保持CloudFlare CDN代理设置
  • 自动恢复:具备错误处理和初始化能力
  • 调试友好:提供详细的日志和调试信息

通过合理配置和定期维护,该脚本可以显著提高网络管理效率,确保域名解析的及时性和准确性。建议在生产环境使用前进行充分测试,并根据实际需求调整检查频率和超时参数。

重要提示:在使用脚本前,请确保备份现有的DNS配置,并在测试环境中验证脚本功能。API Token应妥善保管,避免泄露给未授权人员。

0

评论区