Делаем dDNS-клиент для DNS Яндекса на MikrotikOS

    Зашёл недавно с другом разговор про DynDNS и подобные сервисы, и я вспомнил что давно хотел реализовать аналог на базе API которое предоставляет Yandex для управления DNS-хостингом. Уже несколько лет я владею чудесной железкой Mikrotik RB750GL и очень хотелось чтобы обновляла запись именно она.
    Но до недавнего времени это было не возможно, так как MikroTik умеет скачивать файлы только по HTTP, а API Yandex работает только по HTTPS. И вот зайдя на Wiki Mikrotik увидел заветную запись:
    Fetch now supports HTTPS protocol. By default no certificate checks are made, but setting check-certificate to yes enables trust chain validation from local certificate store. CRL checking is never done.


    Скрипт начал писать ещё тогда когда версия Mikrotik RouterOS была 6.0rc14, а продолжил уже на релизной версии 6.0
    Ну а теперь собственно скрипт:

    Первая часть скрипта – это настройка. Все необходимые параметры указываем в теле самого скрипта как локальные переменные. Это имя домена, токен и ID записи. Текущий IP будем получать из свойств интерфейса, указываем его имя.
    Токен можно получить только ручками, получение ID можно автоматизировать, но мне это не было нужно. Почитать можно в документации API DNS:

    :local YaDNSdomain "domain.ru"
    :local YaDNStoken "132456789012345678901234567890"
    :local YaDNSrecordid "1234567"
    :local YaDNSTTL "300"
    :local YaDNSInterfaceName "PPPoE_NBN"
    
    :global YaDNSForceUpdateOnce
    :global YaDNSPreviousIP
    

    Здесь же 2 глобальные переменные, о них позже.

    Вторая часть скрипта – получение текущего IP с интерфейса. В переменной $YaDNSCurrentIP получим IP адрес, если где-то ошибка — скрипт напишет в лог пояснение и завершится.
    # get the current IP address from the interface
    
    :if ([:len [/interface find name=$YaDNSInterfaceName]] = 0 ) do={
     :log info "UpdateYaDNS: No interface named $YaDNSInterfaceName , please check configuration."
     :error "UpdateYaDNS: No interface named $YaDNSInterfaceName , please check configuration."
    }
    
    :local YaDNSYaDNSCurrentIPMask [ /ip address get [/ip address find interface=$YaDNSInterfaceName] address ]
    
    :local YaDNSCurrentIP [:pick $YaDNSYaDNSCurrentIPMask 0 [:find $YaDNSYaDNSCurrentIPMask "/"]]
    
    :if ([ :typeof $YaDNSCurrentIP ] = "nothing" ) do= {
     :log info "UpdateDynDNS: No ip address present on $YaDNSInterfaceName, please check."
     :error "UpdateDynDNS: No ip address present on $YaDNSInterfaceName, please check."
    }
    


    Немного поясню с различными «предыдущими» IP. Их у меня 2:
    • $YaDNSPreviousIP – это IP значение с момента когда скрипт последний раз пытался обновить IP
    • $YaDNSDomainRecord – это значение которое мы спросили у Яндекса через метод get_domain_records

    :if ([:typeof $YaDNSPreviousIP] = "nothing" ) do={ :global YaDNSPreviousIP 0.0.0.0 }
    
    :local YaDNSsrcpath1 ( "nsapi/get_domain_records.xml\?token=" . $YaDNStoken . "&domain=" . $YaDNSdomain )
    
    :local YaDNSAPI [:resolve "pddimp.yandex.ru"]
    /tool fetch mode=https address="$YaDNSAPI" host="pddimp.yandex.ru" src-path=$YaDNSsrcpath1 dst-path="/YaDNSGetDomainRecord.txt"
    
    :local Result1 [/file get YaDNSGetDomainRecord.txt contents]
    :local Result2 [:pick $Result1 ([:find $Result1 "id=\"$YaDNSrecordid"]) ([:find $Result1 "id=\"$YaDNSrecordid"]+42) ]
    :set YaDNSDomainRecord [:pick $Result2 ([:find $Result2 ">"] + 1) ( [:find $Result2 "<"] ) ]
    


    А вот теперь об этом куске скрипта и почему так получилось:
    :local YaDNSAPI [:resolve "pddimp.yandex.ru"]
    /tool fetch mode=https address="$YaDNSAPI" host="pddimp.yandex.ru" src-path=$YaDNSsrcpath1 dst-path="/YaDNSGetDomainRecord.txt"
    

    Сначала я использовал вызов /tool fetch следующим образом:
    /tool fetch mode=https address="pddimp.yandex.ru" src-path=$YaDNSsrcpath dst-path="/YaDNS.txt"
    

    Но при таком варианте вызова из скрипта команда срабатывала примерно в четверти случаев и скрипт просто прерывался на этом месте. Долго не мог почему так. Много раз запускал этот скрипт из консоли пока не понял, что Яндекс иногда возвращает ошибку 404, но почему – так и не понял. Пообщался с техподдержкой микротика и они навели меня на следующую мысль – сначала резолвить IP API, а потом уже обращаться к нему по IP. Такой вариант у меня заработал.

    И заключительная часть скрипта, непосредственно обновление. Чтобы не дёргать понапрасну Яндекс обновлять будем только если текущий IP не совпадает с одним из предыдущих. Переменная $YaDNSForceUpdateOnce оставлена на тот случай если надо чтобы скрипт отработал в любом случая, использовать по своему разумению, у меня есть отдельный скрипт, который устанавливает её равной true.
    :if (($YaDNSForceUpdateOnce or ($YaDNSCurrentIP != $YaDNSPreviousIP) or ($YaDNSCurrentIP != $YaDNSDomainRecord)) =  true) do={
    
      :log info "UpdateYaDNS: Try Update"
    
      :log info "UpdateYaDNS: YaDNSForceUpdateOnce = $YaDNSForceUpdateOnce"
      :log info "UpdateYaDNS: YaDNSPreviousIP = $YaDNSPreviousIP"
      :log info "UpdateYaDNS: YaDNSCurrentIP = $YaDNSCurrentIP"
      :log info "UpdateYaDNS: YaDNSDomainRecord = $YaDNSDomainRecord"
    
      :local YaDNSsrcpath2 ( "nsapi/edit_a_record.xml\?token=" . $YaDNStoken . "&domain=" . $YaDNSdomain . "&record_id=" . $YaDNSrecordid . "&ttl=" . $YaDNSTTL . "&content=" . $YaDNSCurrentIP )
    
      :local YaDNSAPI [:resolve "pddimp.yandex.ru"]
    
      /tool fetch mode=https address="$YaDNSAPI" host="pddimp.yandex.ru" src-path=$YaDNSsrcpath2 dst-path="/YaDNS.txt"
      :local result [/file get YaDNS.txt contents]
    
      :global YaDNSResult [:pick $result ([:find $result "<error>"]+7) [:find $result "</error>"]]
    
      :if ( $YaDNSResult = "ok" ) do={
       :set YaDNSForceUpdateOnce false
        :set YaDNSPreviousIP $YaDNSCurrentIP
        :log info "UpdateYaDNS: Update Success"
      }
    
      :log info "UpdateYaDNS: Result: $YaDNSResult"
    }
    


    Минусы, от которых так и не смог избавиться:
    • Хранение полученных от Яндекcа ответов в файлах. По другому не смог сделать
    • Парсинг XML сделан через пятую точку


    Для использования скрипта добавьте его в планировщик, я поставил интервал 5 минут.
    /system script run UpdateYaDNS

    Скачать полный текст скрипта можно на PasteBin

    UPD
    PasteBin
    Добавлена локальная переменная
    :local YaDNSsubdomain "xxx.domain.ru"
    

    Как понятно из имени — субдомен. Если нужно обновлять запись самого домена — то нужно указать эту переменную равной $YaDNSdomain
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 17

      +1
      Минусы, от которых так и не смог избавиться:
      Хранение полученных от Яндекса ответов в файлах. По другому не смог сделать


      Посмотрите в сторону выводы нужной информации в лог, а дальше парсите лог, будет скрипт больше, но зато экономия на записи в nand
        0
        Так в том то и дело, что fetch не умеет выводить информацию. Он только скачивает.

        /tool fetch url=«www.mikrotik.com/index.html» mode=http
        Создаст на диске файл index.html

        Если сделать как-то так:
        :local var1 [/tool fetch url=«www.mikrotik.com/index.html» mode=http]
        То результат будет довольно странный на мой взгляд. Если файл скачался – будет пустая переменная, если ошибка – будет описание ошибки.

        [Klajnor@MikroTik] > :local var1 [/tool fetch url="http://www.mikrotik.com/index.html" mode=http]; :put $var1; :put [:len $var1]
          status: finished
        
        
        0
        
        


        [Klajnor@MikroTik] > :local var1 [/tool fetch url="http://www.mikrotik.com/indeks.html" mode=http]; :put $var1; :put [:len $var1] 
          status: failed
        
        failure: closing connection: <404 Not Found> 54.242.175.225:80 (4)
        

        Здесь имя файла заведомо неправильное. Получаем 404 ошибку, что ожидаемо. Но выполнение после этого остановится, убедиться в этом можно заменив переменную на глобальную. Переменная просто не будет объявлена.
          +1
          Я незнаком с api yandex.
          id записи статична?
          если да то, можно обойтиcь простым запросом :resolve к dns серверу yandex. и получить текущий Ip для домена.
          при выполнении fetch keep-result=no не будет вывод сохранять в файл.

            0
            Да, keep-result=no не сохраняет в файл, но и не возвращает результата.
            У меня в скрипте 2 запроса и результат мне нужен.
            И да, id записи статичен.
            На счёт получать текущий IP от яндекса через резолв — подумаю. Идея хорошая.
              0
              Посмотрел. Кажется :resolve не умеет резолвить используя указанный DNS сервер. Только используя общие настройки роутера. Этот вариант мне не подходит, т.к. у провайдера на DNS серверах запись обновляется с задержкой 5-10 минут, получится что скрипт будет стучаться на яндекс 2-3 раза для обновления записи.
                +1
                у вас же в руках маршрутизатор ловить output tcp/udp 53 трафик и отправляйте на yandex

                создайте правило, перед выполнение включайте это правило, потом выключайте, а также в начале чистите кэш для данной записи.

                Просто если id статично зачем вам получать ответ от сервера, можно ведь проверить явно запросом dns.
                А также данный скрипт должен выполняться только при включении, и при сменен ip адреса.
                  0
                  Не, перенаправлять DNS запросы на сервера яндекса правилом — я считаю, что это слишком большое извращение.
                  ID статично. Я по этому ID ищу в XML данные по этому записи.
                  Как запускать скрипт только при смене IP? Мне приходит в голову только парсить лог, но если только таким способом — то этот вариант тоже не лучше
                    +1
                    получаешь свой ip
                    заносишь его адрес лист
                    сверяешь текущий ip c адрес листом
                    если совпадает то молчишь
                    если разные выполняешь запрос на изменение зоны
                    проверяешь
                    меняешь в адрес листе
                      0
                      Так и у меня запрос на изменение записи делается только при условии ($YaDNSCurrentIP != $YaDNSPreviousIP) or ($YaDNSCurrentIP != $YaDNSDomainRecord) = true
                      $YaDNSPreviousIP — IP взятый с интерфейса в момент прошлой попытки обновления, которая вернула OK
                      $YaDNSDomainRecord — IP этой записи полученный запросом с яндекса.

                      Если текущий IP не совпадает с одним из них — делается попытка изменения. Если попытка изменения прошла неудачно или если яндекс вернул OK, но почему-то не записал новые данные — скрипт через 5 минут сделает ещё один запрос на изменение

          +1
          А зачем собственно предыдущий IP сравнивать с текущим через API? просто резолв не подходит разве? результат то должен быть одинаковым, иначе смысла в этом скрипте нет.
          да и впринципе вы же в скрипте проверяете результат выполнения смены айпи адреса, если возврат ОК 200, то IP надо полагать сменился, можно просто предыдущий айпишник в глобальную переменную загнать и только с ним и сверять например.
          в результате запись файла в микротик будет не каждый раз при запуске скрипта, а только в том случае когда айпи адрес сменился, в моём случае это примерно раз в месяц, а в общем у всех как я понимаю в среднем раз в сутки или реже.
            0
            Возможно и излишне. Сделано скорее для подстраховки, т.к. мой скрипт для DynDNS иногда отрабатывал без ошибок, но запись в DNS не обновлялась. Было это где-то 1 раз из 20. Решил просто ещё одним принудительным запуском скрипта.

            У меня IP сменяется раз в сутки, в 5.30 принудительно переподключаюсь, т.к. раньше у провайдера была проблема, что на оборудовании оставались зависшие соединения. Сейчас такой проблемы уже нет, так что переподключение оставлено тоже на всякий случай.
            Но у провайдера время сессии ограничено 72 часами.

            Про резолв – я выше в комментариях ответил.
            0
            С dns.he.net/ это сделать намного проще.
              0
              Спасибо, по Вашему мануалу все получилось.

              Есть только одна проблема: если запись имела вид XXX.domain.ru, то при обновлении XXX удаляется.
              Я понимаю, что для этого надо добавлять свойство subdomain, только вот у меня не получилось…
                0
                Обновил пост.
                0
                На прошивке 6.15, видимо, всё поломалось :(
                Не может получить текущий IP интерфейса, который получаю по DHCP, ну а дальше и делать ничего не делает.
                  0
                  У меня работает. На pastebin новая версия. Изменений с момента поста минимум.
                  В какой-то версии у меня оставались старые адреса в списке и не делался запрос к API яндекса.

                  И что пишет в лог?
                  Добавьте после строки
                  :local YaDNSCurrentIP [:pick $YaDNSYaDNSCurrentIPMask 0 [:find $YaDNSYaDNSCurrentIPMask "/"]]
                  

                  строку
                  :log info "UpdateYaDNS: YaDNSCurrentIP = $YaDNSCurrentIP"
                  
                  0
                  Пробую скрипт запустить на микротике. И получаю ошибку no_auth. Работает ли у Вас скрипт и сейчас? Мне кажется яндекс поменял api. В документации везде api2 фигурирует

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое