DNS-хостинг Яндекса vs Динамический IP

    В сентябре прошлого 2010 года компания Яндекс открыла для публичного использования DNS-хостинг в рамках Почты для доменов. Радости пользователей не было предела, топик был встречен массой положительных комментариев, а Яндекс был объявлен корпорацией добра.

    К сожалению администрирование DNS-записей было предусмотрено только через web-интерфейс. API для администрирования предусмотрено не было, до сих пор не появилось, и возможно еще долго не появится. Этот факт опечалил многих владельцев доменов с динамическим IP не меньше, чем перевод отличного бесплатного сервиса free.editdns.net на платную основу (для custom доменов), в связи с покупкой последнего компанией DynDNS.

    Убедившись, что чуда не случилось, я взял в руки Python напильник с целью исправить эту несправедливость…

    Первым делом я отправился к всезнающему Google в поисках хоть какой нибудь информации об API сервисов Яндекса. Первым мне попалось подробное описание API Почты для доменов. Увы, из 32 имеющихся функций в нем не оказалось ничего, связанного с администрированием DNS-хостинга, и я продолжил поиски. Добавив к запросу волшебные слова python, а затем и c sharp, я наткнулся на статью Алексея Немиро с подробным описанием работы браузера при авторизации на сервисах Яндекса и примерами кода на VB и C#.

    Прочитав статью и убедившись, что все же придется имитировать браузер, я вооружился FireBug'ом и HTTP Analyzer'ом. Потратив немного времени на изучение тонкостей авторизации и работы с DNS-хостингом, я выяснил, что авторизация на сервисах Яндекса работает достаточно просто. Процедура авторизации начинается с куки yandexuid, получаемой при входе на любой сервис Яндекса:

    Copy Source | Copy HTML
    1. def initialize(self):
    2.     connection = httplib.HTTPConnection('www.yandex.ru')
    3.     connection.request('GET', '/')
    4.  
    5.     response = connection.getresponse()
    6.     cookies = response.getheader('set-cookie', None)
    7.     response.close()
    8.  
    9.     match = re.search('(?<=yandexuid=)[^;]*', cookies)
    10.     self._yandexuid = match.group( 0)
    11.     print 'yandexuid =', self._yandexuid


    Получив куку yandexuid браузер передает POST-запросом логин, пароль и таймстамп в формате UNIX. Если с формированием запроса проблем не возникло, то с формулой таймстампа я просидел долго:

    Copy Source | Copy HTML
    1. def login(self):
    2.     content = 'login={0}&passwd={1}&timestamp={2}'
    3.     content = content.format(self._login, self._passwd, self.timestamp())
    4.  
    5.     connection = httplib.HTTPConnection('passport.yandex.ru')
    6.     connection.request('POST', '/passport?mode=auth', content, {'Cookie': self.getcookies()})
    7.  
    8.     response = connection.getresponse()
    9.     content = response.read()
    10.     response.close()
    11.  
    12.     match = re.search('idkey\"\s.*', content)
    13.     match = re.search('(\d\w*)', match.group( 0))
    14.     self._idkey = match.group( 0)
    15.     print 'idkey =', self._idkey


    Получив волшебный idkey, в ответ на запрос «Установить постоянную авторизацию на этом компьютере» потребовалось сформировать запрос, имитирующий нажатие кнопки «Нет»:

    Copy Source | Copy HTML
    1. def authenticate(self):
    2.     content = 'filled=yes&timestamp={0}&idkey={1}&no=%D0%9D%D0%B5%D1%82'
    3.     content = content.format(self.timestamp(), self._idkey)
    4.  
    5.     connection = httplib.HTTPConnection('passport.yandex.ru')
    6.     connection.request('POST', '/passport?mode=auth', content, {'Cookie': self.getcookies()})
    7.  
    8.     response = connection.getresponse()
    9.     cookies = response.getheader('set-cookie', None)
    10.  
    11.     ... парсинг кук регекспами ...
    12.  
    13.     response.close()


    Теперь, имея под рукой все необходимые куки, для работы с Почтой для доменов достаточно имитировать нажатие кнопки «Сохранить» в редакторе DNS-записей через внутренний AJAX API:

    Copy Source | Copy HTML
    1. def updatedomain(self, ns_record_id):
    2.     content = 'domain={0}&ns_record_id={1}&ns_rec_type=A&ns_subdomain=%40&ns_weight=&ns_port=&ns_content={2}&ns_priority=1'
    3.     content = content.format(self._domain, ns_record_id, self._externalip)
    4.  
    5.     connection = httplib.HTTPSConnection('pdd.yandex.ru')
    6.     connection.request('POST', '/ajax/ns_simple_record_edit.ajax.xml', content,\
    7.                       {'Accept': 'application/json, text/javascript, */*',\
    8.                        'Cookie': self.getcookies()})
    9.     response = connection.getresponse()
    10.     response.close()


    Как и в предыдущем случае, на этом этапе заголовок Accept оказался обязательным. Впрочем это не самое важное. Для получения ns_record_id пришлось разобрать HTML-код страницы со списком DNS-записей:

    Copy Source | Copy HTML
    1. def domainlist(self):
    2.     connection = httplib.HTTPSConnection('pdd.yandex.ru')
    3.     connection.request('GET', '/domain_ns/{0}/'.format(self._domain), None,\
    4.                       {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\
    5.                        'Cookie': self.getcookies(),\
    6.                        'Referer': 'https://pdd.yandex.ru'})
    7.  
    8.     response = connection.getresponse()
    9.     content = response.read()
    10.  
    11.     block = re.findall('item:\s\'[\d]+\'(.+)value="[\w\.]+"', content)
    12.  
    13.     for item in block:
    14.         match = re.search('(?<=item:\s\')[\d]*', item)
    15.         ns_record_id = match.group( 0)
    16.  
    17.         match = re.search('ns_subdomain(.+?)value=\"(.+?)\"', item)
    18.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
    19.         ns_subdomain = match.group( 0)
    20.  
    21.         match = re.search('ns_rec_type(.+?)value=\"(.+?)\"', item)
    22.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
    23.         ns_rec_type = match.group( 0)
    24.  
    25.         match = re.search('ns_content(.+?)value=\"(.+?)\"', item)
    26.         match = re.search('(?<=value=)\".+?\"', match.group( 0))
    27.         ns_content = match.group( 0)
    28.  
    29.         record = 'ns_record_id = {0} | ns_subdomain = {1} | ns_rec_type = {2} | ns_content = {3}'
    30.         print record.format(ns_record_id, ns_subdomain, ns_rec_type , ns_content)
    31.  
    32.     response.close()


    Так как это была моя первая программа на Python, я ограничился httplib и ручным формированием кук. Добавив в этот коктейль параметр командной строки, парсинг конфига и получение внешнего IP, я получил простой скрипт для обновления DNS-записи на DNS-хостинге Яндекса.

    скачать исходный код и пример конфига.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

      0
      на яндекс можно быложе только почту увести, а сам домен запарковать где-то в другом месте…
        +1
        К сожалению вменяемых хостингов с возможностью бесплатно припарковать домен второго/третьего уровня все меньше — DynDNS всех скупает…
        EveryDNS и EditDNS например уже куплены и готовятся к переезду (о чем можно судить по большому баннеру на главной)
          +2
          я пока использую freedns.ws
            +2
            долгие поиски привели меня на pointhq.com, в котором за год не было повода разочароваться
              +1
              dns.he.net. Не скупит — силёнок не хватит.
                +1
                юзаю freedns.afraid.org
                вполне вменяемый)
                  0
                  тоже уже второй год его использую, проблем нет и обновление айпишки через ссылку очень удобно(cron+wget)
                  0
                  xname.org + secondary.net.ua
                +47
                Первым делом я отправился к всезнающему Google в поисках хоть какой нибудь информации об API сервисов Яндекса.

                Шикарная фраза :-)
                  0
                  По моему, не стоит путать динамический DNS с нормальным. У первого TTL мало, у второго значительное.
                    +1
                    Я пользуюсь freedns.afraid.org/ и у меня все хорошо :)
                      0
                      Бесплатные динамические ДНС с возможностью работы по АПИ — habrahabr.ru/blogs/i_am_advertising/103676/

                      Эх, кто бы Вин/Мак приложение написал… =)
                        +1
                        под вин/мак нельзя запустить питон скрипт?!
                          0
                          Я имел ввиду для указанного по моей ссылке сервиса, чтобы им могли пользоваться обычные люди =)
                        –6
                        Я конечно понимаю что Google и все дела, но кажется что API яндекса логичнее было искать самим яндексом.
                        0
                        Не используйте httplib, он зело плохой на нагрузке >0. Странные глюки с кешированием и отваливающимися запросами. В свое время пришлось переписать couchdb-python на использование curl вместо оригинального httlib. 500-е ошибки на продакшн-сервере как рукой сняло.
                          0
                          В смысле использовать pycurl вместо httplib/urllib?
                            0
                            К urllib никаких претензий, благо что он в stdlib питона. Простой как топор и работает нормально, а вот httplib берет на себя черезчур много. Пул коннектов, кэш, куки. В итоге, почему-то, при увеличенном loadaverage, начинаются глюки.
                              0
                              Чего-то вы путаете. urllib работает ПОВЕРХ httplib. Похоже вы про httplib2 говорите?
                                0
                                Ммм… Да, httplib2 :)

                                Извиняюсь.
                              0
                              Я для чего-то более сложного чем просто запрос-ответ — pycurl. Он банально функциональнее и удобнее. Не претендую на вселенскую истину.
                                0
                                Там проблема с числом открытых файлов при множестве DNS-запросов в стандартной библиотеке. Мы полечили исправив немого пул коннектов, но не очень хорошо получилось. Вообще с curl это лучше решение, может быть тоже забьем и его заюзаем.
                            +1
                            Как всегда код поругаю…
                            1) Уж очень сильная любовь к регуляркам. Надо искоренять!
                            2) Для конфигов ну хотя-бы ConfigParser (.ini файлы)
                            3) Вместо
                            content = 'login={0}&passwd={1}×tamp={2}'
                            content = content.format(self._login, self._passwd, self.timestamp())
                            и т.п. делаем
                            content=urllib.urlencode({
                                'login': self._login,
                                'passwd': self._passwd,
                                'timestamp': self.timestamp()})

                            4) С timestamp не понял плясок… Можно просто
                            int(time.time()*1000)

                            5) Тут не критично, но вообще файлы после использования принято закрывать ибо есть лимит на 1024 открытых файла для программы
                            file = open('config', 'r')
                            config = file.read()
                            заменяем на
                            file = open('config', 'r')
                            config = file.read()
                            file.close()
                            а еще лучше на
                            with open('config', 'r') as file:
                                config = file.read()

                            6) использовать в качестве имен переменных имена встроенных функций неприкольно. Трудно будет баг отловить. Так что заменяем
                            with open('config', 'r') as file:
                                config = file.read()
                            на
                            with open('config', 'r') as f:
                                config = f.read()
                            (т.к. file это встроенная функция)

                            Но вообще, для первой программы на питоне очень даже ничего!
                              0
                              Спасибо за замечания, я до urllib еще не добрался в изучении, впрочем как и до многих других тонкостей…
                              По поводу timestamp — у Яндекса timestamp это количество миллисекунд с UNIX Time (=
                                0
                                > По поводу timestamp — у Яндекса timestamp это количество миллисекунд с UNIX Time (=

                                Так time.time() — количество секунд с unixtime. Домножаешь на 1000 — получаешь миллисекунды.

                                Они к javascript'ному time(), видимо, привязались, там тож миллисекунды по умолчанию выдает.
                                  0
                                  In [1]: import time, datetime
                                  In [2]: print int(time.time())*1000 + datetime.datetime.now().microsecond/1000 ; print int(time.time()*1000)
                                  1309337559396
                                  1309337559396

                                  До точки с запятой ваш вариант, после — мой. Результаты одинаковые. Фишка в том, что (у меня по крайней мере) time.time() возвращает время в виде float с миллисекундами. Хотя если делать int(time.time())*1000 вместо int(time.time()*1000) то этого можно и не заметить.

                                  Еще один побочный эффект вашего подхода — вы 2 раза обращаетесь к системным часам: один раз в datetime.datetime.now() и второй раз в time.time(). И, строго говоря, время может быть получено разное т.к. между вызовами datetime.datetime.now() и time.time() тоже проходит какое-то время)) Если хочется из datetime.now() получить его UNIX timestamp, то проще всего сделать как-то так так:
                                  dt_now=datetime.datetime.now()
                                  time.mktime(dt_now.timetuple())
                                +1
                                Прописали бы CNAME на dyndns-овское имя.
                                  0
                                  А как быстро обновляются зоны на yandex после отправки запроса?
                                  Просто сейчас начал писать нечто похожее на c++ для reg.ru но у них походу после отправки данных еще есть тайаут на вступление изменений в силу… минут 15, что не очень приятно, ведь сайт это время не доступен…

                                  Как обстоят с этим дела у яндекса?
                                  Как быстро на это реагировал канувший в историю фрии аккаунт на dyndns?

                                  Спасибо.
                                    0
                                    Извините, промазал с ответом. Смотрите коммент ниже.
                                    0
                                    Это зависит не только от DNS-сервиса, но и от провайдеров (как конечного, так и магистральных).
                                    У меня на разных провайдерах DNS-хостинг Яндекса обновлял записи менее чем за 3 минуты.
                                    Кроме того, вы сами настраиваете параметры зоны, такие как TTL, REFRESH, RETRY и т.д.
                                      0
                                      Ну на регру так же обстоит ситуация.
                                      В итоге их 15 минутный таймаут на самом деле 2-5 минуты… так что не критично кого выбирать.

                                      А есть идеи как решить проблему с недоступностью сайта эти 2-5 минут?
                                        0
                                        Оплатить статику :)
                                        0
                                        Просто интересно получается… вот тебе и бесплатный ddns от вроде как не левых компаний :)
                                        странно…

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

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