DynDNS auf eigenen Server betreiben

Vor einiger Zeit hat der Dienst DynDNS, den ich lange Zeit genutzt habe, seine Allgemeinen Geschäftsbedingungen verändert. Die neuen Bedingungen sehen nur noch sehr eingeschränkten kostenlosen Zugang vor. Nun habe ich aber Zugriff auf einen Linux Root-Server im Internet. Entsprechend wollte ich probieren selbst einen entsprechenden Dienst als Alternative zu den kommerziellen Anbietern zu entwickeln. Natürlich nur für mich und mit entsprechend eher geringeren Sicherheitsanforderungen bzw. die Lösung muss nur für maximal 10 User entworfen sein. Für den Anfang begnüge ich mich sogar mit einen User. Wenn der Dienst stabil läuft wird das System entsprechend auf mehrere User erweitert. Dennoch bleibt die Lösung eine für den normalen Hausgebrauch.

Folgende Voraussetzungen sind nötig um so einen Dienst selbst aufzusetzen:

  • Zugriff auf einen Linux Server mit fester IP (im Text unten IPv4 1.1.1.1 und IPv6 fd12::1234)
  • Auf dem Server muss BIND verfügbar/installiert sein
  • Eine öffentliche Domain (im Text unten domain.de)
  • Zugriff auf die DNS Einstellungen dieser Domain inkl. der Möglichkeit einen NS Record anzulegen

Im Anschluss die einzelnen Schritte der Konfiguration des Servers.

Schritt 1 – Domain vorbereiten

Um mehrere DynDNS-Hosts verwalten zu können ist es als erstes notwendig eine Subdomain anzulegen in der diese Hosts zusammengefasst werden. DNS Anfragen die diese Subsdomain betreffen sollen von unserem Server beantwortet werden. Schließlich kennt nur der Server die jeweilige Zuordnung zwischen dynmischer IP-Adresse und festem Hostnamen. Entsprechend wird ein NS-Rekord angelegt. In meinem Fall habe ich diese Subdomain über das Webfrontend meines DNS-Hosters angelegt.

domain.de                   A      1.1.1.1
domain.de                   AAAA   fd12::1234
dyndns.domain.de            NS     domain.de

Mit den obigen Einstellungen für die Domain landen nun alle Anfragen die die Subdomain dyndns.domain.de betreffen bei unserem Server. Im nächsten Schritt müssen wir dafür sorgen, dass der Server sie auch beantworten kann.

Schritt 2 – Bind konfigurieren

Um dynamische Änderungen in einer DNS-Zone durchzuführen wird ein Key benötigt. Anhand diesem Key entscheidet BIND ob eine Update-Anfrage erlaubt ist oder nicht. Zuerst müssen wir also einen entsprechenden Key anlegen.

root@phoenixtor /tmp$  dnssec-keygen -a hmac-sha512 -b 512 -n HOST dyndns.domain.de
Kdyndns.domain.de.+165+55497

Der so erzeugte Key ist symmetrisch (sprich: Der Client der einen DNS Eintrag ändern will verwendet die gleiche Zeichenkette wie der verwaltende BIND Server). Zum Anzeigen des Keys reicht folgendes Kommando:

root@phoenixtor /tmp$ cat Kdyndns.domain.de.+165+55497.private
Private-key-format: v1.3
Algorithm: 165 (HMAC_SHA512)
Key: ogVhyGn4+xnsq2Ozo4vgFDl/StmIOuY0S7RMJsaKcPLUcpKPID3Pje+/UoQSG+Mw2oQfRJqZyOIIZx/HLyyRJw==
Bits: AAA=
Created: 20150724124529
Publish: 20150724124529
Activate: 20150724124529

Im nächsten Schritt müssen wir auf unserem Server die DNS-Zone dyndns.domain.de anlegen und updates entsprechend mit dem obigen Key absichern. Dazu wird folgende Datei angelegt:

key "dyndns.domain.de" {
  algorithm hmac-sha512;
  secret "ogVhyGn4+xnsq2Ozo4vgFDl/StmIOuY0S7RMJsaKcPLUcpKPID3Pje+/UoQSG+Mw2oQfRJqZyOIIZx/HLyyRJw==";
};

zone "dyndns.domain.de" IN {
  type master;
  file "/var/lib/bind/db.dyndns.domain.de";
  allow-query { any; };
  allow-transfer { none; };
  allow-update { key dyndns.domain.de; };
};

Nun muss die Zonen-Datei noch in die generelle BIND-Konfiguration eingefügt werden:

# [...]
include "/etc/bind/named.conf.dyndns";

Damit ist die hälfte des Setups geschaft. Als nächsten Schritt brauchen wir ein Skript um die updates tatsächlich durchzuführen.

Schritt 3 – Update-Skript einrichten

Um DynDNS-Updates durchzuführen hab ich ein kleines PHP-Skript entwickelt. Es funktioniert bisher nur für einen Host und hat nur eine sehr rudimentäre Benutzerkontrolle. Für meinen kleinen Einsaztzweck ist es ausreichend. Es gibt aber durchaus Ideen wie es noch verbessert werden könnte (siehe auch die Gedanken am Ende von diesem Artikel).

Das Skript läuft bei mir auf einem Apache Webserver mit PHP Erweiterung. Es sollte aber auch jeder andere PHP-Fähige Webserver funktionieren.


//
//    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 3 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, see .
//

// error_reporting(E_ALL);
//
// Define your setup below
//
// Allowed username
define("USERNAME", "choose_username_here");

// The password for this user
define("PASSWORD", "choose_password_here");

// The allowed FQDN
define("DOMAIN", "dynhost.dyndns.domain.de");

// Timeout Value for bind
define("TIMEOUT", "60");

// Location of the zone-key for the update
define("KEY", "/home/user/www/Kdyndns.domain.de.+165+13714.key");

// Location of nsupdate binary
define("NSUPDATE", "/usr/bin/nsupdate");

// **********************************************
// No chances should be necessary below this line
// **********************************************

// Grab the data from the HTTP-request
$src_ip   = $_SERVER["REMOTE_ADDR"];
$username = $_GET["username"];
$pass     = $_GET["pass"];
$domain   = $_GET["domain"];
if(isset($_GET["ip4"])) { $ip4=$_GET["ip4"]; } else { $ip4=""; }
if(isset($_GET["ip6"])) { $ip6=$_GET["ip6"]; } else { $ip6=""; }

// a simple log
$logfile = fopen("dyndns_access_log.txt","a");
fwrite($logfile, date("Y-m-d H-i-s") . "#$src_ip#$username#$pass#$domain#$ip4#$ip6\n"); fclose($logfile);

// Before we continue some security checks for all values which will be handed over to bind

// Check: All needed parameters set?
if(! (isset($username) && isset($pass) && isset($domain) && !empty($ip4)) ) { die("Not enough parameters"); }

// Check: Are the supplied IPs really valid IPs?
if(!empty($ip4) & !filter_var($ip4, FILTER_VALIDATE_IP)) { die("Not a valid IP4 Adress"); }
if(!empty($ip6) & !filter_var($ip6, FILTER_VALIDATE_IP)) { die("Not a valid IP6 Adress"); }

// Check Username, Password and allowed hostname
if( strcmp($username, USERNAME) ) { die ("Invalid Username"); }
if( strcmp($pass, PASSWORD) ) { die ("Wrong User/Pasword"); }
if( strcmp($domain, DOMAIN) ) { die ("Hostname not acceptable"); }

// Next create a update-file for bind
$filehandle=fopen("ddns-update-data.txt","w");

if (!empty($ip4)) {
   fwrite($filehandle,"update delete " . $domain . " A\n");
   fwrite($filehandle,"update add " . $domain . " " . TIMEOUT . " A " . $ip4 . "\n");
}

if (!empty($ip6)) {
   fwrite($filehandle,"update delete " . $domain . " AAAA\n");
   fwrite($filehandle,"update add " . $domain . " " . TIMEOUT . " AAAA " . $ip6 . "\n");
}

fwrite($filehandle, "send\n");
fclose($filehandle);
// End creation of update-file

// Create commandline and execute
$cmd=NSUPDATE . " -k ". KEY . " ddns-update-data.txt";
system($cmd, $retval);

if($retval==0) { echo "good ". $ip4 . " ".  $ip6 ; } // some routers would like to get some positiv feedback...
else { echo "error"; }                // ... and some like negative feedback as well.
?>

Im letzten Schritt muss noch der Router konfiguriert werden.

Schritt 4 – Router einrichten

Bei mir steht eine Fritzbox als Internet-Router. Unter Freigaben befindet sich ein Punkt „Dynamic DNS“ der wie folgt auszufüllen ist:

Fritzbox DynDNS Freigabe

Fritzbox DynDNS Freigabe

Die Update-URL ist in dem Bild nicht ganz sichtar. Komplett lautet sie

  • http://dyndns.domain.net/ddns_update_stefan.php?username=&pass=&domain=&ip4=&ip6=

Wobei der letzte Teil „&ip6=“ weg gelassen werden kann wenn an dem Anschluss kein IPv6 verfügbar ist.

Weitere Punkte

Beim schreiben der obigen Anleitung hab ich folgende Quellen verwendet:

Im nächsten Schritt wird das PHP-Skript auf Mulituser-Unterstüzung erweitert. Das mache ich aber erst wenn ich wirklich konkreten Bedarf an weiteren dynamischen Hostnamen habe. Des weiteren wäre es vermutlich eine sehr gute Idee HTTPS statt HTTP zu verwenden und zu prüfen ob eine Authentifizierung per HTTP-AUTH möglich ist. Es gibt also durchaus noch Verbesserungspotential an dem fertigen Skript.