Eigener DynDNS-Dienst

Es gibt bestehende kostenlose bzw. kostenpflichte DynDNS-Dienste von No-IP bzw. Dyn – letzterer hat beispielsweise den kostenlosen DynDNS-Dienst im Mai 2014 eingestellt. Man kann natürlich zu einem anderen kostenlosen Anbieter wechseln – oder aber seinen eigenen DynDNS-Dienst unter Linux realisieren. Und so gehts:

Konfiguration eines DynDNS-Clients auf einem DSL-RouterDie Verwendung des Begriffs „DynDNS“ kann in der Praxis sehr leicht zu Verwirrungen führen, da die einen darunter den bisher gleichnamigen Dienst (inzwischen „Remote Access“) der US-amerikanischen Firma Dyn verstehen, die anderen es als technische Abkürzung für Dynamic DNS sehen. Und beide Male wird letztendlich die einem DNS-Eintrag zugeordnete IP-Adresse geändert. Der Unterschied besteht darin, dass bei Dyn eine HTTP- bzw. HTTPS-Anfrage mit Authentifizierung zum Einsatz kommt während Dynamic DNS (gemäß RFC 2136) das DNS-Protokoll verwendet und die Authentifizierung über die Quell-IP-Adresse bzw. TSIG realisiert wird.

Aber warum existieren eigentlich Dienste wie DynDNS? Internetprovider geben Ihren Endkunden üblicherweise eine öffentliche dynamische IPv4-Adresse welche sich beispielsweise im Zuge der Zwangstrennung nach 24 Stunden oder bei jeder Neueinwahl ändert. Bei manchen DSL- und Kabel-Anbietern ändert sich die öffentliche IPv4-Adresse sogar nur bei Neustarts bzw. Veränderungen am Equipment des Providers in der Vermittlungsstelle. Eine dynamische öffentliche IPv4-Adresse macht das Betreiben von Serverdiensten (wie einem Webserver) auf dem heimischen Internetanschluss jedoch schwerer da bei jeder Änderung der öffentlichen IPv4-Adresse diese im DNS aktualisiert werden muss – denn der Zugriff erfolgt in der Praxis über einen leicht merkbaren und sich nicht ständig ändernden DNS-Namen. DynDNS löst dieses Problem indem der Router (oder generell ein Client hinter dem Internetanschluss) bei Änderung der eigenen öffentlichen IPv4-Adresse diese zusammen mit Benutzername und Kennwort per HTTP bzw. HTTPS an einen DynDNS-Dienst meldet. Dieser wiederum aktualisiert den bestehenden DNS-Namen im Nameserver mit der neuen IPv4-Adresse.

Da der der älteste (oder zumindest vermutlich bekannteste) DynDNS-Dienst von Dyn im Mai 2014 seine kostenlosen Dienste nach mehreren Einschränkungen nun ganz eingestellt hat, bleibt entweder die Möglichkeit auf die kostenpflichtigen Dyn-Dienste (für 25 US-Dollar pro Jahr) oder zu einem anderen DynDNS-Anbieter wie No-IP zu wechseln. Oder aber man baut sich seinen eigenen DynDNS-Dienst – dazu benötigt es aber sowohl den Zugriff auf einen Webserver als auch auf mindestens einen DNS-Server. Meine Anleitung setzt dafür auf den im Linux-Umfeld gängigen Apache Webserver mit PHP sowie ISC BIND. Im Unterschied zu vielen anderen Lösungen die in irgendwelchen Blogs kursieren geht es mir zum einen um eine 1:1-Nachbildung der Standardfunktionalitäten der DynDNS Update API bzw. der No-IP DDNS Update API und zum anderen um eine technisch wirklich sauber und sicher entwickelte Lösung. Waghalsige Skripte mit fehlenden Eingabeprüfungen, PHP-Skripte mit root-Rechten die Zonendateien schreiben oder verändern, ressourcenverschwendende minütliche Cronjobs oder selbstentwickelte mit root-Rechten laufende Daemons für HTTP bzw. HTTPS und/oder DNS sind in meinen Augen eher fragwürdig und sollten daher möglichst vermieden werden.

Da ich einen bereits funktionsfähigen Apache Webserver mit PHP sowie einen ebenfalls funktionstüchtigen ISC BIND voraussetze müssen meine Beispiele entsprechend logisch auf Ihr System angepasst bzw. umgeschrieben werden. Für sichere dynamische DNS-Aktualisierungen wird in jedem Fall ein Schlüssel benötigt der mit ddns-confgen erzeugt werden kann. Viele Anleitungen verwenden an dieser Stelle fälschlicherweise dnssec-keygen, das zwar einen technisch genauso verwendbaren Schlüssel erzeugt, der aber vom Ausgabeformat und den Formatierungen eigentlich für DNSSEC und nicht für DDNS gedacht ist. Da die Entwickler von BIND das Werkzeug ddns-confgen sicherlich nicht grundlos entwickelt haben kommt dieses als erstes zum Einsatz:

tux:~ # ddns-confgen -q -a hmac-sha512 -k ddns-update-key
key "ddns-update-key" {
algorithm hmac-sha512;
secret "YmUxMTIxOTc4MDNkODNkYTkwMWNlNThjZmNiNWY5OTFmNjU4NDhiNmMyMjQ2MWUwNDA1YTFhMTRjODU5NjUzNA==";
};

tux:~ #

Welcher Algorithmus verwendet wird, hier hmac-sha512, ist relativ egal – allerdings sollte der Schlüssel, insbesondere wenn dieser auch außerhalb des Servers verwendet wird, nicht zu schwach sein. Der obige Konfigurationsabschnitt muss als nächstes der BIND-Konfigurationsdatei /etc/named.conf bzw. /etc/bind/named.conf.local hinzugefügt werden. Bitte achten Sie unbedingt darauf, dass Sie den neuen Konfigurationsabschnitt nicht innerhalb einer bestehenden Direktive einfügen.

Anschließend wird eine DNS-Zone benötigt in welcher die DDNS-Einträge gepflegt werden sollen. Dafür kann entweder eine bereits existierende Zone verwendet werden oder es wird eine neue Zone nur für diesen Zweck eingerichtet. Bei einer neuen Zone wird ein Konfigurationsabschnitt benötigt der ungefähr folgendermaßen aussehen sollte:

zone "ddns.example.net" in
{
type master;
notify yes;
file "dynamic/ddns.example.net";
allow-query { any; };
update-policy { grant ddns-update-key wildcard *.ddns.example.net. A AAAA; };
};

Da die Zonendatei später von BIND verwaltet werden soll, müssen die Verzeichnis- und Datei-Berechtigungen entsprechend gesetzt sein bzw. werden. In einigen Linux-Distributionen gibt es dafür ein vorbereitetes dynamic-Verzeichnis denn standardmäßig hat BIND normalerweise nur Leseberechtigungen auf die Zonendateien.

Verwaltet wird die Update-Berechtigung über die Option update-policy die sogar eine so feine Granulation ermöglicht, dass innerhalb einer Zone nur bestimmte DNS-Records von bestimmten Schlüsseln aktualisiert werden dürfen. Nachdem die Steuerbefehle für die Änderung von DNS-Einträgen später sowieso nur von einem PHP-Skript ausgelöst werden reicht ein Schlüssel völlig aus – trotzdem sollte die Update-Berechtigung auf die relevanten DNS-Einträge beschränkt werden sofern nicht die ganze Zone verändert werden darf. Die Parameter und weitere Details zu den Konfigurationsmöglichkeiten der Option update-policy finden sich im Abschnitt Dynamic Update Policies der BIND 9 Configuration Reference sowie im Abschnitt update-policy aus dem ZYTRAX Open Source Guide DNS for Rocket Scientists. Neben der Konfigurationsoption update-policy gibt es natürlich noch die ältere (und damit vermutlich bekanntere) Konfigurationsoption allow-update die die Update-Berechtigung nur auf Basis der Quell-IP-Adresse vergibt. Das ist vor allem dann heikel wenn es sich nicht um eine separate DNS-Zone für DynDNS handelt sondern die Zone bereits reguläre DNS-Einträge für beispielsweise eine Domain enthält die auch unter keinem Umständen über DynDNS oder gar eine Fehlkonfiguration auf Seiten des Apache Webservers bzw. PHP geändert werden können sollen.

Danach muss noch die eigentliche initiale Zonendatei angelegt werden, die /var/named/dynamic/ddns.example.net bzw. /var/lib/bind/ddns.example.net heißt und mit etwa nachfolgendem Inhalt gefüllt werden sollte.

$ORIGIN .
$TTL 84600 ; 23 hours 30 minutes
ddns.example.net IN SOA ns.example.net. hostmaster.example.net. (
2024112101 ; serial
10800 ; refresh (3 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns.example.net.

Ganz wichtig sind die korrekten Berechtigungen des Elternverzeichnisses und der Zonendatei selbst – sonst kann BIND die Zone nicht dynamisch verwalten und aktualisieren. Die dynamischen Zonen-Anpassungen geschehen übrigens nicht in der Zonendatei selbst sondern in einer Journal-Datei *.jnl (welche wie die Zone heißt).

Hat man sich nicht für eine separate Zonendatei für DynDNS entschieden muss zwingend beachtet werden, dass absofort vor jedem Bearbeiten der Zonendatei ein rndc freeze ausgeführt wird damit zum einen das binäre Journal in die menschenlesbare Zonendatei zurückgeschrieben wird und zum anderen die automatischen Aktualisierungen des Journals angehalten werden. Nach jedem Bearbeiten der Zonendatei ist ein rndc thaw absofort ebenfalls unerlässlich um die automatischen Aktualisierungen über das Journal wieder zu aktivieren. Dabei wird unter Umständen auch die Journal-Datei (temporär) gelöscht. Gleichzeitig gehen bei der Verwendung von rndc freeze und rndc thaw individuelle Formatierungen in der Zonendatei verloren. Hat man sich für eine separate Zonendatei entschieden müssen natürlich absofort genauso rndc freeze und rndc thaw verwendet werden – allerdings dürfte die Notwendigkeit für händische Anpassungen bei einer separaten Zonendatei für DynDNS in der Praxis deutlich geringer sein.

Wird für DynDNS eine separate Zonendatei verwendet ist noch eine DNS-Delegation von der Elternzone erforderlich. Diese Delegation sieht in der Zonendatei der Elternzone beispielsweise folgendermaßen aus:

ddns IN NS ns.example.net.

Nach dem Neuladen des Dienstes ist die DNS-Konfiguration damit theoretisch abgeschlossen und kann mit nsupdate getestet werden:

tux:~ # nsupdate -v
> server ns.example.net
> key hmac-sha512:ddns-update-key YmUxMTIxOTc4MDNkODNkYTkwMWNlNThjZmNiNWY5OTFmNjU4NDhiNmMyMjQ2MWUwNDA1YTFhMTRjODU5NjUzNA==
> zone ddns.example.net
> update delete tux.ddns.example.net. A
> update delete tux.ddns.example.net. AAAA
> update add tux.ddns.example.net. 20 A 192.0.2.29
> send
> quit
tux:~ #

Liefert send keine Rückmeldung zurück dann ist die Aktualisierung erfolgreich gewesen. Im Fehlerfall meldet nsupdate beispielsweise:

; TSIG error with server: tsig indicates error
update failed: NOTAUTH(BADKEY)

Passend dazu findet sich dann in /var/log/messages oder journalctl folgende Ausgabe:

Nov 21 07:32:07 tux named[513]: client 2001:db8::4#19109: view external: request has invalid signature: TSIG ddns-example-key: tsig verify failure (BADKEY)

Die obigen beiden Fehlermeldungen bedeuten, dass entweder der Schlüssel fehlerhaft ist oder fehlerhaft angegeben wurde. Das kann zum Beispiel passieren wenn ein falscher Schlüsselname oder der falsche Algorithmus beim Befehl key angegeben wird.

Deutlich heimtückischer hingegen ist nachfolgende Fehlermeldung (innerhalb von nslookup) welche zu gar keiner Meldung in /var/log/messages oder journalctl führt. Selbst das höchste Loglevel von BIND fördert keine weiteren Informationen zu Tage:

update failed: NOTAUTH

Mit NOTAUTH ist lediglich „not authoritative“ gemeint. Das bedeutet, dass man z.B. versucht den lokalen Caching DNS zu verändern – was natürlich nicht geht. Allerdings passiert das ganze ungewollt relativ leicht in Verbindung mit einer umfangreicheren BIND-Konfiguration mit beispielsweise Views. Der gängigste Fall dürfte sein, dass man einfach in der falschen View landet. Hier hilft es unter Umständen die interne View anzupassen indem man ein bestehendes match-clients { 127.0.0.1; ::1; dmz; }; auf match-destinations { 127.0.0.1; ::1; }; abändert. Ein match-clients sorgt dafür, dass alle Anfragen die von 127.0.0.1, ::1 oder einer IP-Adresse der DMZ kommen in der internen View landen – die oftmals nur Caching DNS bereitstellt. Ändert man es auf die Ziel-IP-Adresse mittels match-destinations ab muss jedoch in der lokalen /etc/resolv.conf auch 127.0.0.1 oder ::1 anstatt der öffentlichen IP-Adresse der DMZ stehen. Dafür landen Anfragen an die öffentliche IP-Adresse in der externen View, die für die gewünschte Zone autoritativ ist bzw. sein sollte. Weitere Informationen zu Views bzw. match-destinations finden sich im Abschnitt match-destinations der ZYTRAX Open Source Guide DNS for Rocket Scientists bzw. in den Artikeln AA-00295 und AA-00296 der ISC Knowledge Base.

Erhält man von nsupdate auf ein send nachfolgende Fehlermeldung zurück, dann deutet das entweder auf eine Fehlkonfiguration der Parameter der Konfigurationsoption update-policy in der Konfigurationsdatei /etc/named.conf bzw. /etc/bind/named.conf.local oder aber auf eine falsche Angabe innerhalb von nsupdate im Bezug auf die zu ändernden DNS-Einträge hin.

update failed: REFUSED

Die obige Fehlermeldung wird in /var/log/messages oder journalctl in etwa so ausgegeben:

Nov 21 07:32:07 tux named[513]: client 2001:db8::4#62025: view external: updating zone 'ddns.example.net/IN': update failed: rejected by secure update (REFUSED)

Möglich wird der eigene DynDNS-Dienst aber erst mit meinem PHP-Skript und den zugehörigen drei Konfigurationsdateien. Sie können übrigens die ganzen Dateien meines sogenannten „ddns-server“ auch als Archivdatei herunterladen und im gewünschten Verzeichnis entpacken. Andernfalls erstellen Sie z.B. die Datei /var/www/html/ddns-server/index.php und fügen Sie den nachfolgenden PHP-Code ein.

<?php
 
/*
  *  ddns-server 0.1.0
  *
  *  (c) 2013-2014 by Robert Scheck <ddns-server@robert-scheck.de>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc.,
  *  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */

  // Output according to DynDNS and No-IP DDNS Update API
  
function ddns_api($code$headers = array())
  {
    if(
is_array($headers))
      foreach(
$headers as $header)
        
header($header);
    else
      
header($headers);

    echo (
is_array($code)) ? implode("\n"$code) : $code;
    exit;
  }

  
// Check if given string is a valid IPv4 address
  
function is_ipv4($ipv4)
  {
    
$num "(25[0-5]|2[0-4]\d|[01]?\d\d|\d)";

    if(
preg_match("/^($num)\\.($num)\\.($num)\\.($num)$/"$ipv4))
      return 
TRUE;
    else
      return 
FALSE;
  }

  
// Check if given string is a valid IPv6 address
  
function is_ipv6($ipv6)
  {
    
$pattern1 "([A-Fa-f0-9]{0,4}:){7}[A-Fa-f0-9]{0,4}";
    
$pattern2 "[A-Fa-f0-9]{0,4}::([A-Fa-f0-9]{0,4}:){0,5}[A-Fa-f0-9]{0,4}";
    
$pattern3 "([A-Fa-f0-9]{0,4}:){2}:([A-Fa-f0-9]{0,4}:){0,4}[A-Fa-f0-9]{0,4}";
    
$pattern4 "([A-Fa-f0-9]{0,4}:){3}:([A-Fa-f0-9]{0,4}:){0,3}[A-Fa-f0-9]{0,4}";
    
$pattern5 "([A-Fa-f0-9]{0,4}:){4}:([A-Fa-f0-9]{0,4}:){0,2}[A-Fa-f0-9]{0,4}";
    
$pattern6 "([A-Fa-f0-9]{0,4}:){5}:([A-Fa-f0-9]{0,4}:){0,1}[A-Fa-f0-9]{0,4}";
    
$pattern7 "([A-Fa-f0-9]{0,4}:){6}:[A-Fa-f0-9]{0,4}";

    if(
preg_match("/^($pattern1)$|^($pattern2)$|^($pattern3)$|^($pattern4)$|^($pattern5)$|^($pattern6)$|^($pattern7)$/"$ipv6))
      return 
TRUE;
    else
      return 
FALSE;
  }

  
$usermap $hostmap $keyneed $keymap $cache $hostnames $nsupdates = array();

  
// Read available users and passwords into a usermap
  
$lines = @file("users.conf");

  if(
is_array($lines))
    foreach(
$lines as $line)
    {
      if(
preg_match("/^#/"$line))
        continue;

      list(
$user$password) = explode(":"trim($line), 2);
      
$usermap[$user] = $password;
    }
  else
    
ddns_api("911");

  
// Read available hostnames and related users into a hostmap
  
$lines = @file("hosts.conf");

  if(
is_array($lines))
    foreach(
$lines as $line)
    {
      if(
preg_match("/^#/"$line))
        continue;

      list(
$host$ttl$zone$server$key$users) = explode(":"trim($line), 6);
      
$hostmap[$host]['ttl'] = (empty($ttl) ? 60 $ttl);
      
$hostmap[$host]['zone'] = $zone;
      
$hostmap[$host]['server'] = $server;
      if(!empty(
$key))
      {
        
$hostmap[$host]['key'] = $key;
        
$keyneed[] = $host;
      }
      
$hostmap[$host]['users'] = explode(","$users);
    }
  else
    
ddns_api("911");

  
// Read available keys with algorithm into a keymap
  
$lines = @file("keys.conf");

  if(
is_array($lines))
    foreach(
$lines as $line)
    {
      if(
preg_match("/^#/"$line))
        continue;

      list(
$name$hmac$secret) = explode(":"trim($line), 3);
      
$keymap[$name]['hmac'] = (empty($hmac) ? "hmac-md5" $hmac);
      
$keymap[$name]['key'] = $secret;
    }
  else
    if(
count($keyneed) > 0)
      
ddns_api("911");

  
// Read available cached hostnames and IP addresses
  
$lines = @file("cache.db");

  if(
is_array($lines))
    foreach(
$lines as $line)
    {
      list(
$host$ip) = explode(":"trim($line), 2);
      
$cache[$host] = $ip;
    }
  else
    
ddns_api("911");

  
// Only HTTP methods GET and POST are allowed
  
if(empty($_SERVER['REQUEST_METHOD']) || !in_array($_SERVER['REQUEST_METHOD'], array("GET""POST")))
    
ddns_api("badagent""HTTP/1.1 405 Method Not Allowed");

  
// Request HTTP basic access authentication if needed
  
if(empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW']))
    
ddns_api("badauth", array("WWW-Authenticate: Basic realm=\"DynDNS API Access\"""HTTP/1.1 401 Unauthorized"));

  
// Authenticate using crypt(3) against user map
  
if(empty($usermap[$_SERVER['PHP_AUTH_USER']]) || crypt($_SERVER['PHP_AUTH_PW'], $usermap[$_SERVER['PHP_AUTH_USER']]) !== $usermap[$_SERVER['PHP_AUTH_USER']])
    
ddns_api("badauth""HTTP/1.1 403 Forbidden");

  
// Get IPv4 or IPv6 address, fallback to remote address
  
if(!empty($_REQUEST['myip']) && (is_ipv4($_REQUEST['myip']) || is_ipv6($_REQUEST['myip'])))
    
$myip $_REQUEST['myip'];
  else
    
$myip $_SERVER['REMOTE_ADDR'];

  
// Get hostname(s) to be processed
  
if(!empty($_REQUEST['hostname']))
  {
    foreach(
explode(","strtolower($_REQUEST['hostname'])) as $hostname)
    {
      
// Check if hostname is a valid FQDN
      
if(!preg_match("/^[a-zA-Z0-9.-]+$/"$hostname))
        
$hostnames[$hostname] = "notfqdn";

      
// Check if user is allowed to update the hostname
      
elseif(empty($hostmap[$hostname]['users']) || !in_array($_SERVER['PHP_AUTH_USER'], $hostmap[$hostname]['users']))
        
$hostnames[$hostname] = "nohost";

      
// Check if IP address is already cached
      
elseif(!empty($cache[$hostname]) && $cache[$hostname] === $myip)
        
$hostnames[$hostname] = "nochg $myip";

      
// Fill list of hostname(s) to be updated
      
else
      {
        
$hostnames[$hostname] = "";
        
$nsupdates[] = $hostname;
      }
    }
  }
  else
    
ddns_api("notfqdn");

  
// Process hostname(s) to be updated
  
if(count($nsupdates) > 0)
  {
    
$result = -1;
    
$process proc_open("nsupdate -v", array(=> array("pipe""r"), => array("file""/dev/null""w"), => array("file""/dev/null""w")), $pipes);

    if(
is_resource($process))
    {
      foreach(
$nsupdates as $hostname)
      {
        
fwrite($pipes[0], "server " $hostmap[$hostname]['server'] . "\n");
        if(!empty(
$hostmap[$hostname]['key']) && !empty($keymap[$hostmap[$hostname]['key']]['key']))
          
fwrite($pipes[0], "key " $keymap[$hostmap[$hostname]['key']]['hmac'] . ":" $hostmap[$hostname]['key'] . " " $keymap[$hostmap[$hostname]['key']]['key'] . "\n");
        
fwrite($pipes[0], "zone " $hostmap[$hostname]['zone'] . ".\n");
        
fwrite($pipes[0], "update delete $hostname. A\n");
        
fwrite($pipes[0], "update delete $hostname. AAAA\n");
        
fwrite($pipes[0], "update add $hostname. " $hostmap[$hostname]['ttl'] . " " . (is_ipv4($myip) ? "A" "AAAA") . $myip\n");
        
fwrite($pipes[0], "send\n");
      }

      
fclose($pipes[0]);

      
$result proc_close($process);
    }

    
// Mark processed hostname(s) as such
    
foreach($nsupdates as $hostname)
    {
      if(
$result === 0)
      {
        
$hostnames[$hostname] = "good $myip";
        
$cache[$hostname] = $myip;
      }
      else
      {
        
$hostnames[$hostname] = "dnserr";
        unset(
$cache[$hostname]);
      }
    }

    
// Write cache file or mark host(s) as failed
    
if(!array_walk($cachecreate_function('&$val, $key''$val = "$key:$val";')) || file_put_contents("cache.db"join("\n"$cache)) === FALSE)
      foreach(
$nsupdates as $hostname)
        
$hostnames[$hostname] = "dnserr";
  }

  
// Cause final output
  
ddns_api(array_values($hostnames));
?>

Neben dem eigentlichen relativ überschaubaren PHP-Code bedarf es noch einer Konfigurationsdatei für die Benutzerverwaltung. Ein sicheres Passwort für diese kann beispielsweise mit php an der Kommandozeile mit nachfolgendem Aufruf erzeugt werden. Bitte ersetzen Sie dabei zudem den Salt, hier beispielhaft 16_Zeichen_Salt!, durch eine zufällige Zeichenkette mit (im Idealfall) 16 Zeichen. Diese zufällige Zeichenkette kann – wie das Passwort auch – u.a. mit pwgen erzeugt werden. Erhalten Sie jedoch bitte unbedingt die einleitende Zeichenkette $6$ und das abschließende $ da diese für einen SHA-512-Hash sorgen.

tux:~ # php -r 'echo crypt("Tux+Fisch", "\$6\$16_Zeichen_Salt!\$") . PHP_EOL;'
$6$16_Zeichen_Salt!$1AheiyuRVuZ2ocRMej7l6czkX5flIOMydWrSpTlYlpSfjLHWxuyYxmUxc1QHo/uP5a.mVaTR4GdbGElRL0tYG/
tux:~ #

Dieser generierte Passwort-Hash muss (einschließlich dem Salt) in die Konfigurationsdatei für die Benutzerverwaltung, z.B. die Datei /var/www/html/ddns-server/users.conf, eingefügt werden. Diese Datei sieht dann ungefähr folgendermaßen aus:

# Format: <unique username>:<encrypted password using crypt(3)>
penguin:$6$16_Zeichen_Salt!$1AheiyuRVuZ2ocRMej7l6czkX5flIOMydWrSpTlYlpSfjLHWxuyYxmUxc1QHo/uP5a.mVaTR4GdbGElRL0tYG/

Die nächste Konfigurationsdatei verwaltet die DynDNS-Hostnamen und ordnet die Benutzer zu die eine Aktualisierung vornehmen dürfen. Daneben kann optional eine TTL angegeben werden ansonsten werden die standardmäßigen 60 Sekunden verwendet. Darüber hinaus sind technische Informationen wie der Name der Zone und der zuständige Nameserver erforderlich. Der DDNS-Schlüssel ist optional und wird nur benötigt wenn in der BIND-Konfiguration die Option update-policy verwendet wurde. Haben Sie sich hingegen für allow-update entschieden muss das Feld für den Namen des DDNS-Schlüssel leer bleiben. Das ganze muss in die Konfigurationsdatei für die Hostnamen-Verwaltung, z.B. /var/www/html/ddns-server/hosts.conf eingefügt werden und sieht dann etwa so aus:

# Format: <unique hostname>:[<ttl>]:<zone name>:<dns server>:[<keyname>]:<username1[,username2,...]>
tux.ddns.example.net:20:ddns.example.net:ns.example.net:ddns-update-key:penguin

Die im obigen Beispiel gewählte TTL mit 20 Sekunden wird scheinbar bei kostenpflichtigen DynDNS-Accounts von Dyn genutzt. Haben Sie in der Hostnamen-Verwaltung einen DDNS-Schlüssel angegeben ist die Konfigurationsdatei für die Schlüsselverwaltung, z.B. /var/www/html/ddns-server/keys.conf, unbedingt erforderlich. Kommt bei Ihnen in der BIND-Konfiguration nur die Option allow-update zum Einsatz kann diese Datei entfallen. Letztendlich sollte in die Konfigurationsdatei für die Schlüsselverwaltung ungefähr nachfolgender Inhalt eingefügt werden – analog zur ganz am Anfang mit ddns-confgen erzeugten Ausgabe.

# Format: <unique keyname>:[<algorithm/hmac>]:<secret as base64>
ddns-update-key:hmac-sha512:YmUxMTIxOTc4MDNkODNkYTkwMWNlNThjZmNiNWY5OTFmNjU4NDhiNmMyMjQ2MWUwNDA1YTFhMTRjODU5NjUzNA==

Erfolgreiche Aktualisierungen von DNS-Einträgen werden in einer Cache-Datei, z.B. /var/www/html/ddns-server/cache.db, zwischengespeichert um unnötige Aktualisierungen der Zonendatei zu vermeiden. Diese Cache-Datei kann wie folgt angelegt werden:

tux:~ # touch /var/www/html/ddns-server/cache.db
tux:~ #

Nachdem das PHP-Skript mit den Rechten des Apache Webservers läuft sollte dieser die Cache-Datei natürlich auch beschreiben können; somit müssen die Rechte beispielsweise folgendermaßen angepasst werden:

tux:~ # chown apache:apache /var/www/html/ddns-server/cache.db
tux:~ #

Zusätzlich empfiehlt es sich die sensiblen Konfigurationsdaten im betreffenden Verzeichnis mit einer .htaccess-Datei vor ungewollten Zugriffen durch Dritte per HTTP bzw. HTTPS zu schützen. Dazu sollte nachfolgende Konfiguration in z.B. /var/www/html/ddns-server/.htaccess eingefügt werden:

# Keep configuration and sensitive information private
<FilesMatch "^([\._]ht|.*\.conf|.*\.db|ChangeLog$|COPYING$|README$)">
Order allow,deny
Deny from all
</FilesMatch>

Zur Perfektion fehlt noch eine Update-URL wie http://members.example.net/nic/update?hostname=<domain>&myip=<ipaddr> für die Konfiguration im heimischen Router – das lässt sich mit mod_rewrite als Bestandteil des Apache Webservers schnell erledigen indem Sie einfach nachfolgende Zeilen in z.B. die Datei /var/www/html/.htaccess einfügen:

RewriteEngine On
RewriteRule ^/?nic/update$ /ddns-server/index.php

Bitte bedenken Sie, dass der Apache Webserver auf alle Dateien unterhalb von /var/www/html/ mindestens Lesezugriff benötigt damit Sie Ihren eigenen DynDNS-Dienst erfolgreich nutzen können. Berücksichtigen Sie auch eventuelle weiterführende bzw. abweichende Berechtigungen sofern Sie SELinux, suEXEC oder ähnliche Sicherheitserweiterungen bzw. Technologien einsetzen.

Verbessern und optimieren lässt sich der eigene DynDNS-Dienst noch durch eine SSL-/TLS-Verschlüsselung sprich HTTPS sowie darüber hinaus mit DNSSEC und DANE – abgesehen davon, dass Sie für den produktiven Einsatz natürlich sekundäre DNS-Server einsetzen sollten. Meine Anleitung bezieht sich der Einfachheit halber nur auf den primären Nameserver; die Erweiterung auf sekundäre Nameserver verhält sich nicht abweichend zu regulären statischen Zonen.

Die AVM FRITZ!Box ist meines Erachtens einer der in Deutschland verbreitetsten Router für DSL- und Kabel-Anschlüsse; daher hier eine kurze Anleitung: Wählen Sie bei der Konfiguration von „DynDNS“ bei der Einstellung „DynDNS-Anbieter“ den Wert „Benutzerdefiniert“ aus. Tragen Sie im darunterliegenden Feld „Update-URL“ die angepasste URL mit dem Aufbau http://members.example.net/nic/update?hostname=<domain>&myip=<ipaddr> ein. Die Felder „Domainname“, „Benutzername“ und „Kennwort“ können anschließend ganz normal ausgefüllt werden. Weiterführende Informationen zur Konfiguration von Dynamic DNS in der AVM FRITZ!Box erhalten Sie im Hilfe-Bereich von AVM.

Und obwohl ich nun meinen eigenen DynDNS-Dienst betreibe und benutze ist es mir nicht leicht gefallen den genutzten Namen unterhalb von homelinux.net aufzugeben. Andererseits war ich von September 2001 bis April 2014 lediglich ein Benutzer des kostenlosen DynDNS-Dienstes von Dyn und die zukünftige Nutzung des Dienstes ist mir beim besten Willen keine 25 US-Dollar pro Jahr wert – zumal ich selbst seit vielen Jahren eine eigene DNS-Infrastruktur betreibe die technisch zu gleichem in der Lage ist.