Сегодня, помимо моего любимого занятия (возиться с 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.

На этом всё, надеюсь кому-нибудь пригодится сие решение. Жду ваших комментариев и критики.
Поделиться с друзьями
-->

Комментарии (19)


  1. chelaxe
    20.09.2016 18:43

    Недавно писал подобное на Node.js, но после непродолжительной работы все сломалось… от сервера стало прилетать 5xx ошибка. Подумал и сделал на djbdns.

    Для Яндекс.API


    1. sergix
      20.09.2016 21:45
      +1

      Добрый вечер, пока с пятницы в рабочем состоянии. IP менялось 2 раза, всё нормально. Ещё один раз отваливался интернет на час, наспамило в лог «Проблемы с интернетом», в остальном хорошо)


  1. Ivan_83
    21.09.2016 00:32

    Сильно сложно!

    Когда то я тоже примерно так же делал только на perl: http://www.netlab.linkpc.net/forum/index.php?topic=11.0
    Там же в скриптах есть несколько сервисов которые по хттп тебе отдают твой ip (на nginx такое делается в 4 строчки конфига :) ).

    Сейчас всё делает простой скрипт на shell (не баш а переносимый шел)
    Аналогичным образом ещё для двух динднсов сделано.
    Скрипт запускает крон раз в 5-10 минут.
    На то что нужно дёргать запрос только когда IP поменялся я забил, и сервисам и мне комфортно раз в 5 минут.
    =============================================================
    #!/bin/sh
    ### Rozhuk Ivan, 2013
    ### DynDNS update client for dnsexit.com
    ### version 2.0
    # http://www.dnsexit.com/Direct.sv?cmd=ipClients

    ## Configure variables
    HostDomainName=«netlab.linkpc.net www.netlab.linkpc.net»
    Login=«xxxxxxxxxx»
    Password=«yyyyyyyy»
    DynDNSServerName=«update.dnsexit.com»
    EnableIPv4=1
    EnableIPv6=1

    #// DO NOT EDIT BELOW THIS LINE

    for i in ${HostDomainName}; do
    echo «Updating: ${i}»
    if [ $EnableIPv4 -eq 1 ]; then
    fetch -4 -q -d -o /dev/stdout «http://${DynDNSServerName}/RemoteUpdate.sv?login=${Login}&password=${Password}&host=${i}»
    echo ""
    fi
    if [ $EnableIPv6 -eq 1 ]; then
    fetch -6 -q -d -o /dev/stdout «http://${DynDNSServerName}/RemoteUpdate.sv?login=${Login}&password=${Password}&host=${i}»
    echo ""
    fi
    done

    echo «Well done!»
    =====================================================================================
    Кому лень создавать файл — можно сразу в крон забить вызов fetch/wget и забыть о проблеме.


    1. sergix
      21.09.2016 07:59

      Спасибо за ваш вариант!)
      Интересно было сделать на c#, для ардуино валялся готовый сервис с гетами( мы готовим робота управляемого ютуб комментариями, скоро сделаю обзор на гиктайме про свой кружок) поэтому 90% кода уже было готово, вписал только сами запросы, т.е. тело функции timer1_Tick. Для меня оказалось очень мелким кодом и вполне порадовало). Однако на шел, без установки фраймворка тоже отличный вариант, только подправить его под яндекс Апи и будет ещё один готовый к использованию скрипт!


  1. Dark_Rider
    21.09.2016 00:33

    Тоже самое я сделал на баше и изоленте. без такой кучи кода.


    1. sergix
      21.09.2016 08:00

      Я тоже питаю особые чувства к изоленте, а вот с Bourne-Again SHell знаком очень плохо :(


  1. Crazy_as
    21.09.2016 07:29
    +1

    неплохо, плюсик в карму за изобретательность :)


    1. sergix
      21.09.2016 08:00

      Спасибо, надеюсь пригодится кому-то понять Апи или сделать что-то новое :)


  1. vadimgrt
    21.09.2016 09:31

    Проверьте реальны отклик на смену Ip. Например, у меня клиенты коннектятся на сервак по доменному имени. Ip сменился. Через сколько минут реально у клиентов обновится ip? я полагаю это же не быстро


    1. sergix
      21.09.2016 09:35

      Добрый день, как видите у меня таймер включен на 30 минут, т.е. мне изначально быстрый отклик не был в приоритете. Т.к. меняется у меня раз в неделю и обычно ночью.
      Однако я на тестировании включал раз в минуту и яндекс очень быстренько подхватывал — т.е. практически за минуту всё поднималось обратно и яндекс даже не ругался на «тыканье» каждую минуту за ip.


  1. alz72
    21.09.2016 09:35
    +1

    Я бы посоветовал подключить log4net и писать в лог с его помощью. Это позволит более красиво оформить лог, и разделив уровни логирования ( например где спамит — можно поставить уровень Debug, и выделить в отдельный лог) сделать его более читаемым…


    1. sergix
      21.09.2016 09:37

      Уже давно собираюсь продвинуться на новую систему логирования, посмотрю в эту сторону, однако я люблю такие простенькие вещи писать полностью без дополнительных библиотек, в сложном проекте — конечно же буду рассматривать.


      1. alz72
        23.09.2016 06:53

        Специально для вас «запилил» инструкцию — надеюсь поможет во внедрении.


        1. sergix
          23.09.2016 08:23

          Спасибо, почитаю) Пока в проектах использую логирование через веб(поднят сайт, где принимаются гет запросы и складываются в нужную категорию, а веб морда показывает всё в отличной группировке) и резервно на диск при потере сети, как тут в статье. Если понравится с ним работать, может и попробую)


  1. molnij
    21.09.2016 11:32

    Использую похожую связку на powershell, task sheduller'е и godaddy.


  1. redmanmale
    23.09.2016 18:17

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


    1. sergix
      27.09.2016 08:27
      +1

      Спасибо за интерес :)
      побоялся, что сочтут за пиар, код лежит https://github.com/SergeyToff/AutoDNS
      Я принял их советы на счет других проектов, в маленькой службе, разве имеет смысл приклеивать тяжелый логгер?


      1. sergix
        27.09.2016 09:41

        Возможно вообще стоит писать в журнал 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);
        }
        


        1. alz72
          27.09.2016 13:29

          Логгеры позволяют писать практически во все вообразимые места, тот же 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>
          


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