Сам себе DynDNS

Если по каким-то причинам вы не можете или не хотите использовать один из публичных Dynamic DNS сервисов и у вас есть свой домен, то данная заметка может оказаться вам полезной.

Будем считать, что у нас уже есть настроенный DNS-сервер Bind, который обслуживает зону example.com и веб-сервер с поддержкой PHP, который будет использоваться для обновления A-записей в DNS.

Прежде всего необходимо создать ключ для динамического обновления dns-записей.

 
 dnssec-keygen -a HMAC-MD5 -b 512 -r /dev/urandom -n USER dyndns
Kdyndns.+157+50370
 ll Kdyndns.+157+50370.*
-rw------- 1 root root 113 May  3 17:17 Kdyndns.+157+50370.key
-rw------- 1 root root 229 May  3 17:17 Kdyndns.+157+50370.private

В созданном файле Kdyndns.+157+50370.private нас интересует только одна строка, которая начинается с Key:

Private-key-format: v1.3
Algorithm: 157 (HMAC_MD5)
Key: PaLaoO01EvheJqQ5AfOJ9yZoumGATKhFeMdcfWFzA+VGCzURzBfhee0X4Ad54Tss7BANs562LOTQpCM52j6YGQ==
Bits: AAA=
Created: 20150503131756
Publish: 20150503131756
Activate: 20150503131756

Ключ нужно добавить в файл конфигурации bind, например /etc/named.conf, и разрешить для него обновления целевой зоны.

key "dyndns" {
  algorithm hmac-md5;
  secret "PaLaoO01EvheJqQ5AfOJ9yZoumGATKhFeMdcfWFzA+VGCzURzBfhee0X4Ad54Tss7BANs562LOTQpCM52j6YGQ==";
};

zone "example.com" {
  type master;
  file "/var/named/example.com";
  allow-update { key "dyndns"; };
};

Так же понадобится секретный хеш для обновления DNS посредством http-запроса, который можно сгенерировать, например с помощью openssl так:

 openssl rand -hex 24
701cc7069159e9547def51b9f9c423a5660730ee3a44cdf6

Мой пример php-скрипта для обновления DNS:

 
  // configuration of users and domains
  $udns = array(
    'user' => array(
       'home' => '701cc7069159e9547def51b9f9c423a5660730ee3a44cdf6'
    )
  );
  // main domain for dynamic DNS
  $dyndns = "example.com";
  $dnskey = "PaLaoO01EvheJqQ5AfOJ9yZoumGATKhFeMdcfWFzA+VGCzURzBfhee0X4Ad54Tss7BANs562LOTQpCM52j6YGQ==";

  // short sanity check for given IP
  function checkip($ip) {
    $iptupel = explode(".", $ip);
    foreach ($iptupel as $value) {
      if ($value < 0 || $value > 255)
        return false;
      }
    return true;
  }

  // check request data
  function checkreq(&$value, $default = false) {
    return isset($value) ? $value : $default;
  }

  // retrieve IP
  $ip = $_SERVER['REMOTE_ADDR'];
  // check for given user
  $user = checkreq($_REQUEST['u']);
  // check for given domain
  $subdomain = checkreq($_REQUEST['d']);
  // check for given secret
  $secret = checkreq($_REQUEST['s']);

  // check for needed variables
  if($subdomain && $ip && $user && $secret) {
    // short sanity check for given IP
    if(preg_match("/^(\d{1,3}\.){3}\d{1,3}$/", $ip) && checkip($ip) &&
       $ip != "0.0.0.0" && $ip != "255.255.255.255") {
      // short sanity check for given domain
      if(isset($udns[$user][$subdomain]) && $udns[$user][$subdomain] == $secret) {
          // check for some ip
          if(gethostbyname($subdomain.".".$dyndns) == $ip) {
             echo "IP address $ip not changing for domain $subdomain";
          }
          else {
             // shell escape all values
             $subdomain = escapeshellcmd($subdomain);
             $ip = escapeshellcmd($ip);
             // prepare command
             $data = '<<EOF
zone $dyndns
update delete $subdomain.$dyndns A
update add $subdomain.$dyndns 60 A $ip
send
EOF';
             // run DNS update
             exec("/usr/bin/nsupdate -y dyndns:$dnskey $data", $cmdout, $ret);
             // check whether DNS update was successful
             if ($ret != 0) {
               echo "Changing DNS for $subdomain.$dyndns to $ip failed with code $ret";
             }
          }
        }
        else {
          echo "Domain $subdomain for $user from $ip with $subdomain was wrong";
        }
      }
  }
  else {
     echo "DDNS change for $user from $ip with $subdomain failed because of missing values";
  }

Запрос скрипта со стороны клиента будет выглядеть так (безопаснее использовать https):

 wget https://dyn.rascal.su/?u=user&d=home&s=701cc7069159e9547def51b9f9c423a5660730ee3a44cdf6