Нам придется использовать именно API для обновления записей DNS. Чтобы использовать ее, нам нужно использовать как минимум RouterOS версии 6.44 для /tool fetch для поддержки параметра http-header-field и три вещи из Cloudflare:
- Токен API
- Идентификатор зоны
- Идентификатор DNS
Кроме того, маршрутизатор MikroTik должен иметь интерфейс WAN с динамическим общедоступным IP-адресом.
- Получение токена API
- Перейдите на страницу управления токенами Cloudflare
- Создайте токен, используя шаблон “Edit zone DNS”
- Настройте его в соответствии с вашими потребностями
- Получение идентификатора зоны
- Зайдите в ваш домен в CloudFlare и откройте Overview
- Вы должны найти ее на боковой панели в правой части страницы
- Получение идентификатора DNS
На этом этапе нам потребуется приложить больше усилий, чтобы получить идентификатор DNS. Мы должны получить это из ответа API DNS.
Вы уже создали API-токен и нашли свой идентификатор зоны? Мы будем использовать его в следующем запросе к API.
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_IDENTIFIER/dns_records?type=A&name=$DNS_NAME" \
-H "Authorization: Bearer $API_TOKEN"
*DNS_NAME - это домен, у которого будет динамический IP, например: myddns.xenolith.ru. Если у вас его еще нет, создайте новый в Cloudflare Dashboard.
Скрипт
Скрипт должен делать следующее:
- Отправлять запрос к API, только если IP изменился
- Логировать только в случае необходимости, чтобы сэкономить больше места в журнале
- Логировать информацию об ошибках при сбое DDNS
Давайте перейдем к скрипту. Первое, что мы собираемся сделать, это установить некоторые переменные.
Переменные
# глобальные переменные
# мы будем обновлять его при каждом успешном запуске ddns
:global currentIp
# исходящий интерфейс
:local wanInterface "ether1"
# переменные cloudflare, измените в соответствии с вашими
:local cfToken "xxxxxxxxxxxxxxxxxxxxxx"
:local cfZoneId "xxxxxxxxxxxxxxxxxxxxxxx"
:local cfDnsId "xxxxxxxxxxxxxxxxxxxxxxxx"
:local dnsType "A"
:local dnsName "myddns.xenolith.ru"
:local dnsTTL "1"
:local dnsProxied "false"
Существует несколько динамических переменных, зависящих от текущего состояния маршрутизатора. Нам нужно выполнить несколько команд, чтобы задать эти переменные.
Получить текущий IP-адрес интерфейса WAN
Вот страница с примерами сценариев MikroTik.
# получаем текущий IP-адрес
:local newIpCidr [/ip address get [find interface="$wanInterface"] address ]
# убираем маску подсети
:local newIp [:pick $newIpCidr 0 [:find $newIpCidr"/"]]
Составление переменных API
Перейдём к документации по обновлению записей DNS, если вам нужно четкое объяснение ограничений на значения полезной нагрузки.
# укажите адрес api
# вики: https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record
:local apiUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records/$cfDnsId"
# headers & payload
:local headers "Authorization: Bearer $cfToken"
:local payload "{\"type\":\"$dnsType\",\"name\":\"$dnsName\",\"content\":\"$newIp\",\"ttl\":$dnsTTL,\"proxied\":$dnsProxied}"
Главное
Чтобы убедиться, что наш поток будет выполняться только в том случае, если IP изменился, мы поместим остальную часть скрипта в условие if. Если это условие не выполняется, скрипт завершит работу без дополнительных затрат.
- Отправляйте запрос API только в том случае, если IP изменился. ✅
:if ($newIp != $currentIp) do={
...
}
Я хотел зарегистрировать ответ об ошибке API в случае сбоя запроса, пока не понял, что скрипт RouterOS выдает ошибку, если HTTP-ответ /tool fetch не был получен успешно, что нарушало работу скрипта.
После некоторого просмотра я узнал, что единственный способ справиться с этими ошибками - использовать блок перехвата ошибок во время выполнения, но, тем не менее, нет способа получить ответ на ошибку. Я немного разочарован, поскольку мы будем регистрировать ошибку без подробных сведений.
- Сохраняет сведения об ошибке при сбое DDNS ✅
:do {
:local response [/tool fetch http-method="put" url=$apiUrl http-header-field=$headers http-data=$payload as-value output=user]
...
} on-error {
:log error "DDNS: ошибка изменения с $currentIp на $newIp"
}
Мы логируем, если только запрос был выполнен успешно, а затем обновим переменную CurrentIP.
- Логируем, только если это необходимо ✅
:if ($response->"status" = "finished") do={
:log info "DDNS: изменен $currentIp на $newIp"
# обновление $currentIp на новый адрес
:set currentIp $newIp
}
Окончательный вариант скрипта
Пожалуйста, обратите внимание, что я не специалист в этой области. Если вы собираетесь использовать скрипт в рабочей среде, вам следует протестировать его самостоятельно. Возможно, есть какие-то ошибки, о которых я пока не знаю. 👻
У нас есть наш окончательный сценарий, в котором выполнены все поставленные цели. Я думаю.
- Отправляет запрос к API только в том случае, если IP изменился ✅
- Логирует только в случае необходимости ✅
- Логирует сведения об ошибках при сбое DDNS ✅
В виде текста - ddns-cloudflare.txt [1.78 Kb]
#--------------------------------------------
# MikroTik DDNS Script | Cloudflare API v4
# xenolith.ru
#--------------------------------------------
# global variables
# we'll update it on every ddns success
:global currentIp
# outgoing interface
:local wanInterface "ether1"
# get current $wanInterface IP
:local newIpCidr [/ip address get [find interface="$wanInterface"] address ]
:local newIp [:pick $newIpCidr 0 [:find $newIpCidr"/"]]
:if ($newIp!= $currentIp) do={
# cloudflare variables, adjust with yours
:local cfToken "xxxxxxxxxxxxxxxxxxxxxxxxx"
:local cfZoneId "xxxxxxxxxxxxxxxxxxxxxxxx"
:local cfDnsId "xxxxxxxxxxxxxxxxxxxxxxxxx"
:local dnsType "A"
:local dnsName "myddns.bayukurnia.com"
:local dnsTTL "1"
:local dnsProxied "false"
# compose endpoint
# docs: https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record
:local apiUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records/$cfDnsId"
# compose headers & payload
:local headers "Authorization: Bearer $cfToken"
:local payload "{\"type\":\"$dnsType\",\"name\":\"$dnsName\",\"content\":\"$newIp\",\"ttl\":$dnsTTL,\"proxied\":$dnsProxied}"
# make API request
:do {
:local response [/tool fetch http-method="put" url=$apiUrl http-header-field=$headers http-data=$payload as-value output=user]
:if ($response->"status" = "finished") do={
:log info "DDNS: changed $currentIp to $newIp"
# update $currentIp with the new one
:set currentIp $newIp
}
} on-error {
:log error "DDNS: failed to change IP $currentIp to $newIp"
}
}