Регистрация
MikroTik DDNS с Cloudflare API v4

Нам придется использовать именно API для обновления записей DNS. Чтобы использовать ее, нам нужно использовать как минимум RouterOS версии 6.44 для /tool fetch для поддержки параметра http-header-field и три вещи из Cloudflare:

  1. Токен API
  2. Идентификатор зоны
  3. Идентификатор DNS

Кроме того, маршрутизатор MikroTik должен иметь интерфейс WAN с динамическим общедоступным IP-адресом.

  1. Получение токена API
  • Перейдите на страницу управления токенами Cloudflare
  • Создайте токен, используя шаблон “Edit zone DNS”
  • Настройте его в соответствии с вашими потребностями
  1. Получение идентификатора зоны
  • Зайдите в ваш домен в CloudFlare и откройте Overview
  • Вы должны найти ее на боковой панели в правой части страницы

  1. Получение идентификатора 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"
  }
}