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

Залез зачем-то в справку почты для домена яндекса и заметил там Его — долгожданный API DNS.
И захотелось сделать более нативный клиент dDNS, чем всего лишь 2-х месячной давности решение через эмуляцию пользователя.


Поскольку на С делать моими силами долго получилось бы, а ни Python ни Perl я не знаю, делать будем на перле.

Итого дано:
  • динамический внешний IP
  • домен, делегированный на яндекс
  • роутер под управлением Debian

Хочется их всех передружить.

Для начала определим IP, на который будем заменять запись:
sub getip {
    (my $interface,my $ipconf) = @_;
    $ipconf = defined($ipconf) ? $ipconf : '/sbin/ip';
    my $res = `ip a s $interface | grep «inet »` or die(«Can't get info from ip: ».$!);
    if ($res =~ /([\d]{1,3}.[\d]{1,3}.[\d]{1,3}.[\d]{1,3})/) {
        return $1;
        }
    die(«Can't find ip»);
    }

Берём первый попавшийся IP указанного интерфейса.

И выкинем в функцию выполнение запроса к API и общие проверки ответа:
sub yapi {
    my $url = $_[0];
    die 'Not URL in params yapi func'
        unless defined($url);
    my $cont = $ua->get($url);
        die 'HTTP Error: ', $cont->status_line
            unless $cont->is_success;
        die 'Not XML, given ', $cont->content_type
            unless $cont->content_type eq 'text/xml';
    my $resp = XML::Simple
                    ->new()
                    ->XMLin( $cont->content );
        die 'API error: ',$resp->{domains}->{error}
            unless $resp->{domains}->{error} eq 'ok';
    return ($resp->{domains}->{domain});
    }

В частности, удостоверимся, что есть ответ сервера, распарсим XML и проверим, Яндекс не вернул ли API ошибку.

Теперь и основная часть:
#!/usr/bin/perl
# доменное имя
my $domain = 'example.org';
#токен авторизационный
my $token = '1bdf72e04d6b50c82a48c7e4dd38cc6920116dfd6774a9e7b32eddfe'; 
# новый IP, определяем по переданному первым аргументом имени интерфейса
# т.е., например, ./yaddns.pl ppp0
my $currentip = getip($ARGV[0]); 
 
use strict;
use LWP::UserAgent;
use XML::Simple;
#use Data::Dumper; #for debug!
my $ua = LWP::UserAgent->new;
$ua->agent(«dDNS client for pdd.yandex.ru.»);
 
 
my $domainlist = yapi('https://pddimp.yandex.ru/nsapi/get_domain_records.xml?token='.$token.'&domain='.$domain)->{response}->{record};
while ( my ($record_id, $record) = each(%$domainlist) ) {
    #print Dumper($record);
    if ($record->{type} eq 'A' && $currentip ne $record->{content} ) {
        my $update = 'https://pddimp.yandex.ru/nsapi/edit_a_record.xml?token='.$token
                    .'&domain='.$domain.'&subdomain='.$record->{subdomain}
                    .'&record_id='.$record_id.'&content='.$currentip;
        print 'Обновление записи '.$record_id.' (хост '.$record->{subdomain}.")\n";
        yapi($update);
        }
    }

Сим получаем список записей этого домена и обновляем все A-записи, у которых IP не тот, что мы хотим.

Вот и всё, в общем-то. Теперь осталось прописать запуск скрипта при поднятии интерфейса и можно спокойно жить дальше.

PS: под debian'ом нужно доставить, если не стоят, пакеты libcrypt-ssleay-perl, libxml-simple-perl и libwww-perl.

Обратите внимание, что API устарело в декабре 2014г.
Share post

Comments 25

    +1
    немного понудю

    my ($interface,$ipconf) = @_;

    my $url = shift;

    Прям глаза режет
      0
      А как правильно будет?
      Извиняюсь, первый перловый опыт.
        +1
        ну вот я и написал как правильно.

        (my $interface,my $ipconf) = @_;
        меняем на
        my ($interface,$ipconf) = @_;

        и
        my $url = $_[0];
        меняем на
        my $url = shift;

        ну а если первый опыт то вообще можно не обращать внимания на мои слова)
          0
          А, всё, понял.
          Впрочем, мне кажется не совсем очевидным, зачем shift. И не очевидным, с каким массивом он работает, но это — фича языка, уже понял.
            0
            shift без параметров работает как раз с @_
              0
              не всегда. Распостраненная ошибка новичков, кстати с работой функции shift. Пожалуй автору стоит познакомиться с ламабуком для того, чтобы лучше понимать Перл. А поведение конкретно функции shift распишет
              $ perldoc -f shift

              Perl язык неочевидных тонкостей, привыкайте. А вот если нужен one way очевидности, то это уже питон.
                0
                В контексте функции всегда. Или я что то путаю. Или Вы?
                  0
                  Да, в контексте функции всегда. Но я помню как сам ошибся, неправильно использовав ее вне функции. Вы правильно сказали, но слишком широко. И пока программист не привык уточнять и проверять такие вещи (а судя по коду и посту, человек с нуля начал писать сразу на Perl) ему стоит уточнять, что тут тоже есть всегда более, чем один путь развития событий… :D
                    0
                    Согласен, я уже после отправки комментария, понял что я слишком широко ответил!
                      0
                      Да нет, не с нуля. Искренне надеюсь, что хоть на джуниора меня уже хватит. Так что критика приветствуется.
                      На качество кода однозначно сказалось то, что от первого знакомства с языком до изложенного скрипта прошёл 1 день. Действительно, не вникал во все тонкости языка.

                      И не стоит думать, что прежде, чем написать комментарий, я не прочёл ман на shift и не поглядел, когда с какими данными работает по-умолчанию. Наоборот, потому и выразил сомнение, решив, что $_[0] лучше указывает на то, с чем работаем, чем shift. В общем, здесь уже больше роль играют принятые негласные стандарты написания.
                        0
                        Как-то забыл дописать — на джуниора в PHP.
        +11
        | Поскольку… ни Python ни Perl я не знаю, делать будем на перле.
        Ээ…
          +1
          Perl ближе к Си, чем питон.
            0
            Perl != перл ?)))))))))))))
              +1
              Ага, ибо
              Perl ne 'перл'
              +2
              Да, а что? Логично же!
              0
              Эх… Хотел написать такой скрипт для роутера на системе MirotikOS — так там https не поддерживается, а в другой роутер на базе OpenWRT не могу впихнуть curl, т.к. флеш очень маленький.
              Как будет свободное время — попробую на какой-нибудь бесплатном хостинге с поддержкой php и curl сделать скрипт.
                +1
                Т.е. тем же wget'ом пинаем заранее известный url, а тот уже апдейтит запись DNS? О том речь идёт, правильно понял?
                  0
                  Да, именно так. Роутер раз в минуту пинает скрипт на сервере, если айпи изменился — этот скрипт меняет запись в DNS.
                  +1
                  На микротик OpenWRT можно поставить в виртуалку
                    0
                    Очень не хочется плодить сущности. И так два роутера на столе. Один проводной MikroTik и Dlink 615 прошитый в OpenWRT, как точка доступа WiFi.

                    Держать ещё один в виртуалке в микторике лишь для того, чтобы обновлять DNS — это я считаю лишней тратой ресурсов. Тем более микротик у меня один из самых слабеньких, 750GL.
                  0
                  Вот только сегодня думал, как реализовать на PDD смену IP-адреса в А-записи при падении основного инет-канала.
                    0
                    nsupdate наше все.
                    0
                    Насколько я понимаю предполагается токен получать вручную и вписывать в скрипт?
                      0
                      Да, именно так. При том, предполагается яндексом: именно получение вручную и указано как способ получения токена.

                    Only users with full accounts can post comments. Log in, please.