среда, 5 ноября 2014 г.

Сам себе DynDNS. Или делаем собственный DynDNS сервер.

Немного отвлечемся от темы телефонов, модемов, да и вообще GSM в целом. В этой заметке я расскажу вам о том как с минимальными затратами поднять собственный DynDNS сервер. Хотя наверное это будет слишком громко сказано, поэтому вкратце опишем задачу по-другому. Допустим у нас есть домен company.ru и мы хотели бы использовать его поддомены для резольвинга некоторых хостов имеющих динамические IP. Поясню на примере. Допустим у нас есть несколько удаленных точек, на которых функционирую, ну, к примеру, IP камеры. Вы бы хотели чтобы доменные имена camera01.company.ru, camera02.company.ru и т.п. резольвились на их текущий IP. При условии что IP-адреса на данных точках выдаются провайдером динамически. Как вариант, конечно, можно воспользоваться сервисами вроде DynDNS или NO-IP, но их использование (в бесплатном режиме) связано с определенными ограничениями, например на DynDNS необходимо ежемесячно подтверждать хост, иначе ваша учетная запись будет удалена и т.п.

Что нам понадобится изначально? Во-первых сам домен - у нас это будет dnsapi.ru. Во-вторых хостинг с поддержкой PHP и включенными модулями CURL и SimpleXML (найти такой дешево - тоже не проблема). Поставим конкретную задачу, допустим, у нас есть хост - назовем его decker и мы хотим чтобы имя decker.dnsapi.ru всегда резольвилось по текущему IP данного хоста.

Теперь настало время рассказать вам о замечательном сервисе Почта для домена от Яндекс и о его API для работы с DNS - API DNS Яндекс.Почты. Итак, первое что мы должны сделать - это делегировать наш домен на Яндекс, чтобы в интерфейсе почты для домена он у нас выглядел вот так:


Про то как делегировать домен на Яндекс - здесь мы подробно останавливаться не будем, т.к. все эти моменты очень подробно расписаны в помощи почты для домена. Если в двух словах, то сначала (!) нам надо подтвердить владение доменом одним из предложенных способов, а затем изменить NS записи DNS-зоны нашего домена на dns1.yandex.net и dns2.yandex.net. Основное сделано. Теперь внимательно читаем мануал про API DNS, из которого мы сможем узнать, что для домена делегированного на Яндекс возможно программное изменение любых элементов DNS-зоны. Т.е. мы можем добавлять поддомены, изменять записи и т.п. программное. Понимаете к чему я веду?

Создали мы поддомен decker в зоне dnsapi.ru, т.е. decker.dnsapi.ru - изменили программное A-запись для этого поддомена на свой текущий IP, и бинго. Но как узнать свой текущий API, а тем более как написать скрипт для изменения A-записи? Все элементарное - просто. А именно - я все это сделал уже за вас )) Теперь единственное что от вас потребуется - это разместить нижепреведенный скрипт на вашем хостинге. Но для начала необходимо получить токен. Это делается 1 раз, и этот токен потом необходимо прописать в скрипте. 

get_token - Метод предназначен для получения авторизационного токена. Авторизационный токен используется для авторизации в API DNS, которая требуется для обращения к остальным методам API. Получать токен нужно только один раз. Чтобы получить токен, следует иметь подключенный домен, авторизоваться его администратором и перейти по указанному в документации адресу.

Ну а теперь собственно код скрипта (в этот файл нужно вставить свой токен, свои имя домена, и имя хоста (поддомена) для которого будет обновляться IP):

<html><body><pre>
<?php

function validip($ip) {
 if (!empty($ip) && $ip == long2ip(ip2long($ip)))
 {
  // reserved IANA IPv4 addresses
  // http://www.iana.org/assignments/ipv4-address-space
  $reserved_ips = array (
    array('0.0.0.0','2.255.255.255'),
    array('10.0.0.0','10.255.255.255'),
    array('127.0.0.0','127.255.255.255'),
    array('169.254.0.0','169.254.255.255'),
    array('172.16.0.0','172.31.255.255'),
    array('192.0.2.0','192.0.2.255'),
    array('192.168.0.0','192.168.255.255'),
    array('255.255.255.0','255.255.255.255')
  );

  foreach ($reserved_ips as $r) {
    $min = ip2long($r[0]);
    $max = ip2long($r[1]);
    if ((ip2long($ip) >= $min) && (ip2long($ip) <= $max)) return false;
  }
  return true;
 }
 else return false;
}

function getip() {
 if (isset($_SERVER)) {
  if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && validip($_SERVER['HTTP_X_FORWARDED_FOR'])) {
   $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
  } elseif (isset($_SERVER['HTTP_CLIENT_IP']) && validip($_SERVER['HTTP_CLIENT_IP'])) {
   $ip = $_SERVER['HTTP_CLIENT_IP'];
  } else {
   $ip = $_SERVER['REMOTE_ADDR'];
  }
 } else {
  if (getenv('HTTP_X_FORWARDED_FOR') && validip(getenv('HTTP_X_FORWARDED_FOR'))) {
   $ip = getenv('HTTP_X_FORWARDED_FOR');
  } elseif (getenv('HTTP_CLIENT_IP') && validip(getenv('HTTP_CLIENT_IP'))) {
   $ip = getenv('HTTP_CLIENT_IP');
  } else {
   $ip = getenv('REMOTE_ADDR');
   }
 }

 return $ip;
}

function open_https_url($url,$refer = "",$usecookie = false) { 
    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, $url); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 
    curl_setopt($ch, CURLOPT_HEADER, 0); 
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); 
    if ($refer != "") { 
        curl_setopt($ch, CURLOPT_REFERER, $refer ); 
    } 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
 $result =curl_exec ($ch); 
 curl_close ($ch); 
 return $result; 
}

/* Settings */
$token = ""; // ваш токен
$domain = ""; // ваш домен
$host = "";      // ваше имя хоста (поддомена)

$res = open_https_url("https://pddimp.yandex.ru/nsapi/get_domain_records.xml?token=".urlencode($token)."&domain=".urlencode($domain));
$xml = @simplexml_load_string($res,'SimpleXMLElement', LIBXML_NOCDATA);

if (!empty($xml)) {

if (($xml->domains->error)  == "ok") {
foreach ($xml->domains->domain->response->record as $record) {
 // echo $record["subdomain"]."\t".$record["type"]."\t".$record["id"]."\r\n";
 if (($record["subdomain"] == $host) && ($record["type"] = "A")) break;
}

if (($record["subdomain"] == $host) && ($record["type"] = "A")) {
// нашли запись о нужном поддомене
   echo "Domain: ".$record["domain"].", IP: ".$record[0]." [ID: ".$record["id"]."]\r\n";
   //$ip = "192.168.1.5"; 
   $ip = getip();
   echo "New IP: ".$ip."\r\n";
   $res = open_https_url("https://pddimp.yandex.ru/nsapi/edit_a_record.xml?token=".urlencode($token).
 "&domain=".urlencode($domain)."&subdomain=".urlencode($record["subdomain"])."&record_id=".
 urlencode($record["id"])."&content=".$ip);
   $xml = @simplexml_load_string($res,'SimpleXMLElement', LIBXML_NOCDATA);
   echo "Status: ".$xml->domains->error."\r\n";
} // failed to find host
} // failed to retrieve zone records
} // empty xml

?>
</pre></body></html>

После того, как мы отредактировали секцию settings в скрипте - смело заливаем его на свой хостинг и открываем с того хоста (!) для которого необходимо обновить IP, результат выполнения вы видите прямо в браузере:


Как мы видим, IP адрес для поддомена decker.dnsapi.ru был успешно изменен. Если мы теперь сделаем nslookup decker.dnsapi.ru - то увидим IP нашего хоста. Правда есть единственное "но", для обновления DNS-записей "по всему интернету" требуется некоторое время.

Таким образом серверная часть у нас готова. Например, мы разместили скрипт на нашем хостинге по адресу http://ourhosting.org/script.php, теперь на ПК decker (или на маршрутизаторе) достаточно добавить в автозагрузку (или в планировщик, для периодического обновления, например каждые 30 минут) запуск этого скрипта. Т.е. фактически нам нужно сделать HTTP GET запрос к скрипту http://ourhosting.org/script.php ... Берем wget, и создаем bat'ник в котором будет запускаться wget http://ourhosting.org/script.php каждые 30 минут. Бинго! В итоге хост decker.dnsapi.ru всегда ссылается на внешний IP той машины, на которой запущен данный скрипт. К слову, возможности выполнения HTTP GET запросов встроены в любое сетевое оборудование, например, в Mikrotik и т.п. Так что вариантов использования предложенного способа масса.

Вопросы, замечания, пожелания, комментарии приветствуются ...

p.s.  По просьбам трудящихся скрипт для Mikrotik (необходимые права test, write, read):

/tool fetch url="http://ourhosting.org/script.php" mode=http dst-path=decker.txt
:log info [/file get decker.txt contents]

Здесь в качестве URL - выступает URL размещенного нами PHP скрипта. 

1 комментарий :

  1. Большое спасибо! Реально работает, только ошибся когда вводил поддомен (ввел целиком с доменом), примера не хватает.

    ОтветитьУдалить