Eigener Dynamischen DNS (DDNS) Service betreiben (Eigenes DynDNS)

Mit Dynamischem DNS (DDNS) kann man DNS Einträge zur Laufzeit verändern. So kann z. B. ein DHCP Server autoomatisch zu jedem DHCP Client ein DNS Eintrag erstellen. Ein anderer Anwendungszweck ist das bereitstellen eines DNS-Eintrags (z. B. A oder AAAA Records) für eine sich oft wechselnde IP-Adresse, damit diese immer unter dem selben Namen erreichbar ist. Bekannte Anbieter solcher Dynamischen DNS Services sind dyn.com oder noip.com. So einen Dienst kann man aber auch selber betreiben.

Voraussetzungen

Als Voraussetzung für diese Anleitung braucht man einen konfigurierten Bind Nameserver. In dieser Anleitung ist der DNS-Server zuständig für die Zone example.org. Bei mir läuft der Nameserver auf Debian 6.

Keys erstellen

Mit dnssec-keygen kann man sich einen Schlüssel erstellen, welcher später dazu dient, sich beim DNS Server zu authentifizieren:

$ /usr/sbin/dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST foobar.example.org
Kfoobar.example.org.+157+12900

Nach dem Generieren der Schlüssel hat man zwei neue Dateien:

$ ls -l *example.org*
-rw------- 1 root root 123 May 22 14:37 Kfoobar.example.org.+157+12900.key
-rw------- 1 root root 229 May 22 14:37 Kfoobar.example.org.+157+12900.private

Der Key ist symmetrisch und daher in beiden Dateien der gleiche. Er ist base64 kodiert und könnte so aussehen:

$ cat Kfoobar.example.org.+157+51053.private
Algorithm: 157 (HMAC-SHA512)
Key: GFzIGlzdCBkZXIgZ2VoZWltZSBTY2hsw7xzc2VsLgpEZW5rc3RlIGljaCBwdWJsaXppZXJlIGRlbiBPcmlnaW5hbGtleSBoaWVyPwpWaWVsIFNwYXNzCg==
Bits: AAA=
Created: 20130522130759
Publish: 20130522130759
Activate: 20130522130759

Nameserver konfigurieren

Der generierte Schlüssel muss dem Nameserver hinterlegt werden. Wir definieren einen neuen Key, welcher in der Datei /etc/bind/named.keys abgelegt wird:

$ awk '/Key/{ print "key foobar.example.org {\n \
  algorithm HMAC-SHA512;\n  secret "", $2, "";\n};"  }' \
  Kfoobar.example.org.+157+12900.private | sudo tee -a /etc/bind/named.keys

Diese Datei darf natürlich nur vom User bind gelesen werden. Zudem benötigt dieser User Schreibberechtigungen auf dem Verzeichnis /etc/bind:

$ sudo chmod 600 /etc/bind/named.keys
$ sudo chown -R bind.bind /etc/bind

Die Datei /etc/bind/named.keys sieht dann so aus:

$ sudo cat /etc/bind/named.keys
key foobar.example.org {
  algorithm HMAC-SHA512;
  secret "GFzIGlzdCBkZXIgZ2VoZWltZSBTY2hsw7xzc2VsLgpEZW5rc3RlIGljaCBwdWJsaXppZXJlIGRlbiBPcmlnaW5hbGtleSBoaWVyPwpWaWVsIFNwYXNzCg==";
};

In dieser Datei können mehrere Keys gespeichert werden.

Damit Bind von den Keys weiss, fügen wir folgende Zeile vor den Zonendefinitionen in der Datei /etc/bind/named.conf.local ein:

include "/etc/bind/named.keys";

Berechtigungen für A Record

Damit jetzt dieser Key ein A Record verändern darf, fügt man die Option update-policy in der Zonendefinition ein:

zone "example.org" {
  type master;
  file "/etc/bind/db.example.org";
  update-policy {
    grant foobar.example.org name foobar.example.org A;
  };
};

Somit darf der Key foobar.example.org den Eintrag A Record von foobar.example.org verändern.

Änderungen übernehmen

Zuletzt startet man den Nameserver neu und prüft das Logfile auf Fehlermeldungen:

$ sudo /etc/init.d/bind9 restart
$ sudo less /var/log/daemon.log

DNS Server dynamisch updaten

Das Keyfile mit der Endung .private kopiert man auf den Client in z.B. das /etc Verzeichnis. Der Key sollte nur für den User lesbar sein, welcher das Skript ausführt, damit nicht andere User auf dem System die DNS-Einträge ändern können.

Mit nsupdate kann man den DNS Server während der Laufzeit verändern. Die Verbindung zum DNS-Server wird wie folgt hergestellt:

$ nsupdate -k /etc/Kfoobar.example.org.+157+51053.private

So ändert man einen Eintrag:

server ns.example.org
zone example.org
update delete foobar.example.org
update add foobar.example.org 60 A 10.23.5.42
send

Diese Befehle fügt dem Nameserver ns.example.org in der Zone example.org einen neuen A Record mit dem Namen foobar.example.org hinzu. Die TTL ist 60 Minuten (1 Stunde) und die dazugehörige IP Adresse lautet 10.23.5.42.

Das kann man mit einem Skript automatisieren:

#!/bin/bash
#
# ddns-updater - Update dynamic dns entries
#

CONF="${1:-/etc/ddns-updater.conf}"
. $CONF

if [[ -z "$KEYFILE" || -z "$NAMESERVER" || -z "$ZONE" \
   || -z "$HOSTNAME" || -z "$TTL" ]]
then
  echo "Error loading configuration file $CONF"
  exit 1
fi

IPADDR="${CUSTOMIP:-$(dig myip.opendns.com @resolver1.opendns.com +short)}"
IPADDROLD="$(dig $HOSTNAME @$NAMESERVER +short)"

if [[ "$IPADDR" == "$IPADDROLD" ]]
then
  exit
fi

(
  echo "server $NAMESERVER"
  echo "zone $ZONE"
  echo "update delete $HOSTNAME"
  echo "update add $HOSTNAME $TTL A $IPADDR"
  echo "send"
) | nsupdate -v -k $KEYFILE

Die dazugehörige Konfigurationsdatei sieht so aus:

#
# ddns-updater.conf
#

KEYFILE="/etc/Kfoobar.example.org.+157+51053.private"
NAMESERVER="ns.example.org"
ZONE="example.org"
HOSTNAME="foobar.example.org"
TTL="60"
#CUSTOMIP="10.42.23.5"

Zuerst prüft das Skript, ob es mit einer mitgegebenen Konfigurationsdatei aufgerufen wurde. Sonst wird die Standardkonfiguration aus /etc/ddns-updater.conf geladen. Danach wird die öffentliche IP-Adresse von OpenDNS bezogen (dig +short myip.opendns.com @resolver1.opendns.com, thx @Danilo). Falls sich die IP Adresse geändert hat, wird nsupdate gestartet und der A Record aktuallisiert. Dabei werden die in der Sektion Variables verwendete Angaben verwendet. Um selbst eine IP Adresse zu definieren kann man diese mit der Option CUSTOMIP setzen.

Das Skript ist auch in meinem Scripts-Repository auf GitHub zu finden: ddns-updater.

Automatisiert updaten

Die Keys kann man z. B. unter /usr/local/etc oder direkt unter /etc speichern und das Skript unter /usr/local/bin. Danach genügt folgender Crontab Eintrag, welcher den A Record alle 60 Minuten (immer 5 nach) auf den neusten Stand bringt:

5 * * * * /usr/local/bin/ddns-updater

Links und weitere Informationen

16 thoughts on “Eigener Dynamischen DNS (DDNS) Service betreiben (Eigenes DynDNS)

  1. Verwende nun schon seit fast 6 Wochen das ddns-updater Script mit deinem Server und funktioniert nach wie vor wunderbar, kann es also allen nur empfehlen. Danke 🙂

  2. Cool, werde ich gleich mal einrichten.

    Wieso generierst du eigentlich einen HMAC-MD5 key, und nicht HMAC-SHA256 oder HMAC-SHA512?

    • Hey Danilo

      Danke.

      Hmm. Weil ichs wohl vergeigt habe 🙂

      Ich habe das gleich mal im Post auf `HMAC-SHA256` geändert. Denkst Du das ist ausreichend, oder würdest Du gleich 512 machen?

      Merci und Gruss
      Emanuel

      • Keine Ahnung. Gemäss Steffen bin ich ja ein “Paranoider Linuxhacker”, deshalb hab ich SHA512 😉

        Nochwas: Mein nsupdate (ist jetzt auf Arch im AUR) hat das “Private-key-format 1.3” nicht gefressen. Wenn man auf dem Client aber den Key im Bind-Format (

        key domain.tld { algorithm ...

        ) verwendet, klappts.

        • Nachtrag (da du kein Comment-Edit hast): Da ein DDNS Eintrag normalerweise unkritisch ist, würde SHA256 wohl völlig ausreichen.

        • Man kann nie vorsichtig genug sein!

          Ich nutze nsupdate auf einem Debian Server, aber wäre auch schon froh gewesen um das nsupdate package in Arch. Super.

          Werde das mit dem Keyformat später einmal anschauen und testen und dann vtl. den Post anpassen.

  3. Hallo,
    ist es möglich das hier auch von einem PHP Script ausführen zu lassen?
    Wenn ja habt Ihr für mich ein Bsp. da ich in PHP noch nicht auf dem niveau bin.
    Der Grund ist das ich gerne die IP über die Fritzbox updaten lassen, anstatt über Linux!
    PS: Habt Ihr schonmal dran gedacht das ganze per PHP —> Mysql —-> per Script auslesen —> Bind

    • Also ich hab mal so ein primitives Skript in PHP geschrieben, hat funktioniert.
      Man braucht Zugriff auf seinen eigenen DNS, also auf die Zonendatei der Stammdomain.
      Ich musste allerdings mit:

      chmod 644 /etc/bind/rndc.key

      rndc.key lesbar für den www-data machen.

      Das Skript update.php:

      <?php
      $debug = $_GET["debug"];
      $ip = $_GET["ip"];
      $code = $_GET["code"];
      if (!preg_match("/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/", $ip)) exit("Keine IP uebergeben!");
      // else exit("Korrekte IP uebergeben!");
      if ($code != "12345") exit ("Falscher Code");

      $file = "/etc/bind/exampledyndns.org.hosts";
      // Open file for read
      $fh = fopen($file, "r");
      // get content of file
      $contents = fread($fh, filesize($file));

      if ($debug) echo ("contents: ".$contents."<br /><br />");

      // get serial number
      preg_match("/\d{10}/", $contents, $matches);
      $serial = $matches[0];

      if ($debug) echo("serial: ".$serial."<br /><br />");

      // increment serial number
      $new_serial = $serial + 1;

      if ($debug) echo("new_serial: ".$new_serial."<br /><br />");

      // change serial number
      $new_contents = str_replace($serial, $new_serial, $contents);

      preg_match("/(franc\.exampledyndns\.org\.\s+60\s+IN\s+A\s+)(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)/", $new_contents, $matches);

      if ($debug) {
        echo "<pre>";
        print_r($matches);
        echo "</pre>";
      }

      $old_ip = $matches[2];

      if ($debug) echo("old_ip: ".$old_ip."<br /><br />");

      $new_contents = str_replace($old_ip, $ip, $new_contents);

      fclose($fh);

      if ($debug) echo ("new_contents: ".$new_contents."<br /><br />");

      //Open file to write
      $fh = fopen($file, 'r+');
      fwrite($fh, $new_contents);
      fclose($fh);

      // Reload new Zone
      $last_line = system("/usr/sbin/rndc reload exampledyndns.org", &$output);

      if ($debug) {
        echo "<pre>";
        echo ("Last line: " . $last_line . "<br />");
        echo ("Return of system: ");
        print_r($output);
        echo "</pre>";
      }
      ?>

      wird dann aufgerufen mit:

      http://exampledyndns.org/update.php?code=12345&amp;ip=1.2.3.4&amp;debug=1

      Wobei der code hier 12345 ist und die exemplarische IP 1.2.3.4, debug=1 gibt ein paar Ausgaben, falls was nicht stimmt.
      Man muss sich den Code aber schon ein bisschen anschauen denke ich, vermutlich ist er auch sehr holprig und unnötig lang.

      franc

    • Hallo Dennis

      Ich hab das nachträglich von SHA256 auf SHA512 geändert und dort wohl vergessen. Ich habs auf HMAC-SHA512 -b 512 korrigiert.

      Merci 🙂
      Gruss
      Emanuel

      • Sollte dann nicht auch noch beim Schritt Nameserver konfigurieren bei
        “$ sudo cat /etc/bind/named.keys
        key foobar.example.org {
        algorithm HMAC-SHA256;

        auf SHA512 geändert werden?

        • Hallo Markus

          Ich hab das nachträglich von SHA256 auf SHA512 geändert und dort wohl vergessen. Ich habs korrigiert.

          Merci für den Hinweis.

          Gruss
          Emanuel

  4. Kleines Update: ifconfig.me ist in letzter Zeit extrem unzuverlässig und löst fast nie auf.

    Ich habe zwei extrem schnelle Alternativen gefunden:

    curl http://whatismyip.akamai.com/

    Oder noch cooler via DNS Query (UDP):

    dig +short myip.opendns.com @resolver1.opendns.com

    Gruss, Danilo

    PS: Fallfehler in der Überschrift 😉

    • Hey Danilo

      Das mit ifconfig hab ich auch bemerkt. Jetzt hab ichs auf die Variante mit dem DNS Query angepasst. Das ist wirklich cool.

      Ich habe das Script zudem noch etwas erweitert:

      1) Das Skript prüft, sendet nur die eigene IP Adresse, falls sich diese geändert hat
      2) Die Konfiguration ist ein einem eigenen Konfigurationsfile. So kann man besser das Script aktuallisieren, ohne immer die Variabeln rüberzukopieren
      3) $1 ist jetzt für die Konfigurationsdatei und nicht mehr für eine benutzerdefinierte IP Adresse. Diese kann jetzt in der Variable $CUSTOMIP festgelegt werden.

      Merci fürs Feedback!

      Stimmt der Titel jetzt? Je länger ich mich drauf konzentrieren, desto komischer wird der 🙂

      Gruess
      Mänu

Leave a Comment