Dynamic DNS на C# и Яндекс.API

Сегодня, помимо моего любимого занятия (возиться с Arduino в моем кружке детского творчества), решил я поставить себе сервер (Windows 2012 r2) и использовать его для различных манипуляций. Изучив тонну информации, всё прошло гладко.

Когда, закончил экспериментировать в локальной сети, у меня появился вопрос: «Как можно увидеть свой сервер из внешней сети, если у меня динамический IP». Снова помогла всемирная книга знаний и были найдены такие сервисы, как DynDNS, no-ip и т.п.

После регистрации увидел, что нужно качать прогу (в роутере настроек под dyndns нет), а как добросовестный параноик, я не люблю ставить сторонний софт. Вспомнив, что имею при себе домен второго уровня делегированный на Яндексе, принялся изучать сторону вопроса, для написания своего софта на C#.

За основу я взял статью «Самодельный Dynamic DNS». Для отправки запросов к API был написан следующий метод:

        static string GET(String getString)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(getString);
            request.Method = "GET";
            String test = String.Empty;
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                Stream dataStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(dataStream);
                test = reader.ReadToEnd();
                reader.Close();
                dataStream.Close();
            }
            return test;
        }

Теперь нужно было как-то получить внешний IP, а так, как я решил напрочь не пользоваться DynDNS, то и к страничке checkip.dyndns.org решил не обращаться. Начал искать у того же Яндекса. Отправив гет-запрос на страничку yandex.com/internet, нашёл интересную строчку ipv4.internet.yandex.net/internet/api/v0/ip в ответ на такой запрос, мне выдало красивейший IP в чистом виде.

Для понимания откуда взят токен и id, пожалуйста, обратитесь к статье, которую я взял за основу. Таким образом сложились следующие строчки:

        string ip = GET("http://ipv4.internet.yandex.net/internet/api/v0/ip").Trim('"');
        string respons = GET("https://pddimp.yandex.ru/nsapi/edit_a_record.xml?token="
                          + token + "&domain="
                          + domain + " &subdomain="
                          + subdomain + "&record_id="
                          + id + "&content="
                          + ip);

Наконец т.к. всё это дело должно работать на сервере, я решил пересоздать консольное приложение в службу. Были добавлены таймеры и проверка на изменение ip относительно предыдущего.

using System;
using System.ServiceProcess;
using System.IO;
using System.Net;
using System.Timers;
namespace DDNSyapi
{
    public partial class Service1 : ServiceBase
    {
        String outIp = "";
        Timer timer1;
        public const string token = "YOURTOKEN";
        public const string domain = "YOURDOMAIN";
        public const string subdomain ="UPDATINGSUBDOMAIN";
        public const string id = "id";
        //take token on pddimp.yandex.ru/get_token.xml?domain_name=YOURDOMAIN
        //take id on pddimp.yandex.ru/nsapi/get_domain_records.xml?token=YOURTOKEN&domain=YOURDOMAIN
        public Service1()
        {
            InitializeComponent();
        }
        public static void Logs(string err) 
        {
            StreamWriter txtIst = new StreamWriter(System.IO.Path.GetDirectoryName(
                                                   System.Environment.GetCommandLineArgs()[0]) + 
                                                   "//IpServerLog_" + DateTime.Now.Year + 
                                                   DateTime.Now.Month + DateTime.Now.Day + 
                                                   ".txt", true);
            txtIst.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " : " + err);
            txtIst.Close();
        }
        protected override void OnStart(string[] args)
        {
            timer1 = new System.Timers.Timer(30 * 60 * 1000);
            timer1.Elapsed += timer1_Tick;
            timer1.Start();
            timer1.Enabled = true;
            Logs("Запуск службы");
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Enabled = false;
            try
            {
                string ip = GET("http://ipv4.internet.yandex.net/internet/api/v0/ip").Trim('"');
                if (!ip.Equals(outIp))
                {
                    outIp = ip;
                    string respons = GET("https://pddimp.yandex.ru/nsapi/edit_a_record.xml?token="
                                             + token + "&domain="
                                             + domain + " &subdomain="
                                             + subdomain + "&record_id="
                                             + id + "&content="
                                             + ip);
                    Logs(respons);
                }
            }
            catch 
            {
                Logs("проблемы с интернетом.."); 
            }
            timer1.Enabled = true;
        }
        static string GET(String getString)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(getString);
            request.Method = "GET";
            String test = String.Empty;
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                Stream dataStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(dataStream);
                test = reader.ReadToEnd();
                reader.Close();
                dataStream.Close();
            }
            return test;
        }
        protected override void OnStop()
        {
            timer1.Enabled = false;
            timer1.Stop();
            timer1 = null;
            Logs(DateTime.Now.ToString("HH:mm:ss") + " - " + "Остановка службы");
        }
    }
}

В данный момент при записи нового ip очень много грязнит в лог — можно либо после отладки закомментировать вовсе, либо можно парсить по тегу Error error, тогда можно будет получать чистый ответ от API. Я это не делал, т.к. IP меняется очень редко, а когда всё отлажено, то и ошибки API будут редко.

Таким образом, берём внешний ip мы уже не с постороннего ресурса, а с того-же, где пользуемся доменом, так же код можно запустить на любой windows-машине, вплоть до домашнего компьютера. Но советую тогда переделать проверку IP, так, как сделал автор, на которого я ссылался: ему посоветовали сохранять ip в отдельный файл и брать из него последний, сравнивать с нынешним. Я не делал это потому, что у моего сервера аптайм довольно высокий и перезапуск службы не будет особо часто зачищать переменную outIP.

На этом всё, надеюсь кому-нибудь пригодится сие решение. Жду ваших комментариев и критики.
Поделиться публикацией
Комментарии 21
    0
    Недавно писал подобное на Node.js, но после непродолжительной работы все сломалось… от сервера стало прилетать 5xx ошибка. Подумал и сделал на djbdns.

    Для Яндекс.API

      +1
      Добрый вечер, пока с пятницы в рабочем состоянии. IP менялось 2 раза, всё нормально. Ещё один раз отваливался интернет на час, наспамило в лог «Проблемы с интернетом», в остальном хорошо)
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        Спасибо за ваш вариант!)
        Интересно было сделать на c#, для ардуино валялся готовый сервис с гетами( мы готовим робота управляемого ютуб комментариями, скоро сделаю обзор на гиктайме про свой кружок) поэтому 90% кода уже было готово, вписал только сами запросы, т.е. тело функции timer1_Tick. Для меня оказалось очень мелким кодом и вполне порадовало). Однако на шел, без установки фраймворка тоже отличный вариант, только подправить его под яндекс Апи и будет ещё один готовый к использованию скрипт!
        0
        Тоже самое я сделал на баше и изоленте. без такой кучи кода.
          0
          Я тоже питаю особые чувства к изоленте, а вот с Bourne-Again SHell знаком очень плохо :(
          +1
          неплохо, плюсик в карму за изобретательность :)
            0
            Спасибо, надеюсь пригодится кому-то понять Апи или сделать что-то новое :)
            0
            Проверьте реальны отклик на смену Ip. Например, у меня клиенты коннектятся на сервак по доменному имени. Ip сменился. Через сколько минут реально у клиентов обновится ip? я полагаю это же не быстро
              0
              Добрый день, как видите у меня таймер включен на 30 минут, т.е. мне изначально быстрый отклик не был в приоритете. Т.к. меняется у меня раз в неделю и обычно ночью.
              Однако я на тестировании включал раз в минуту и яндекс очень быстренько подхватывал — т.е. практически за минуту всё поднималось обратно и яндекс даже не ругался на «тыканье» каждую минуту за ip.
              +1
              Я бы посоветовал подключить log4net и писать в лог с его помощью. Это позволит более красиво оформить лог, и разделив уровни логирования ( например где спамит — можно поставить уровень Debug, и выделить в отдельный лог) сделать его более читаемым…
                0
                Уже давно собираюсь продвинуться на новую систему логирования, посмотрю в эту сторону, однако я люблю такие простенькие вещи писать полностью без дополнительных библиотек, в сложном проекте — конечно же буду рассматривать.
                  0
                  Специально для вас «запилил» инструкцию — надеюсь поможет во внедрении.
                    0
                    Спасибо, почитаю) Пока в проектах использую логирование через веб(поднят сайт, где принимаются гет запросы и складываются в нужную категорию, а веб морда показывает всё в отличной группировке) и резервно на диск при потере сети, как тут в статье. Если понравится с ним работать, может и попробую)
                0
                Использую похожую связку на powershell, task sheduller'е и godaddy.
                  0

                  Как уже посоветовали выше, используте нормальный логгер, а ещё стоит выложить код на github/bitbucket и причесать.

                    +1
                    Спасибо за интерес :)
                    побоялся, что сочтут за пиар, код лежит https://github.com/SergeyToff/AutoDNS
                    Я принял их советы на счет других проектов, в маленькой службе, разве имеет смысл приклеивать тяжелый логгер?
                      0
                      Возможно вообще стоит писать в журнал windows…
                      public static void log(string message, EventLogEntryType tp)
                      {
                           if (!EventLog.SourceExists("DDNSyapi"))
                              EventLog.CreateEventSource("DDNSyapi", "System");
                           EventLog log = new EventLog();
                           log.Source = "DDNSyapi";
                           log.WriteEntry(message, tp);
                      }
                      
                        0
                        Логгеры позволяют писать практически во все вообразимые места, тот же log4net умеет писать в том числе и в event log
                        <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
                            <layout type="log4net.Layout.PatternLayout">
                                <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
                            </layout>
                        </appender>
                        


                        вот тут есть перечисление с примерами
                    0

                    Хотя бы потрудились нормально сервисы обозвать перед тем как на хабр писать, а то


                    public partial class Service1 : ServiceBase

                    никуда не годится.


                    А вообще вот:


                    1. yandex-dns-api — C# обертка над yandex api для dns
                    2. DynDNSviaYandexAPI — UI версия где используется этот api
                      0
                      В статье я разъяснял, что делал велосипед, т.к. мне хотелось свой код, который полностью понятен и можно переписать в любой момент, дополнить другими возможностями(отправлять по эмэйлу там последние смены и т.п.), плюс была цель сделать максимально компактным и читабельным.
                      Конечно, сейчас меня мой код тоже раздражает в некоторых местах, всегда то, что писал год назад кажется смешным, но оно работало и выполняло свои задачи хорошо.

                      Что вы покрасовались своим гитхабом — это всегда пожалуйста(причем в вашем хабе первый коммит позже, чем сама статья).

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

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