Einrichtung eines unabhängigen DynDNS Dienstes mit PowerDNS und MySQL/MariaDB

Da ich es eher nervig fand, aller 30 Tage die Aktivität meiner URL bei einem DynDNS Anbieter zu bestätigen, entschied ich mich dazu, meinen eigenen DynDNS Dienst einzurichten. Nachfolgend werde ich erklären, wie ich dies umgesetzt habe.

Der Artikel wird sich dabei in die folgenden einzelnen Schritte aufteilen:

  • Anlegen neuer Records beim Provider
  • Einrichtung von PowerDNS
  • Einrichtung der Datenbank
  • Anlegen eigener Records
  • Einrichtung der automatischen Aktualisierung von Records
  • Cronjob

Die Voraussetzungen, um einen solchen DynDNS Dienst zu betreiben, erfüllte ich zum Start bereits:

  • Server mit statischer, öffentlicher IP Adresse
  • Eigene Domain

Auf Empfehlung meines Bruders entschied ich mich dazu, die Software PowerDNS einuzsetzen. Da ich ohnehin schon mehrere MariaDB Datenbanken auf meinem Server betreibe, habe ich diese als Backend zum speichern und abrufen der DNS Records eingesetzt. Hier kann allerdings auch etwas leichteres wie z.B SQLite genutzt werden.

Die Installation habe ich auf einem System mit Debian 9 vorgenommen. Solltet ihr eine andere Distribution nutzen, können Kommandos und Pakete abweichen.

Während meiner Anleitung werde ich nicht darauf eingehen, wie man die Installation eines Webservers (z.B. Apache, Nginx) oder eines Datenbankmanagementsystems (z.B. MySQL) vornimmt. Solltet ihr eines der beiden noch nicht installiert haben, solltet ihr dies vor der Abarbeitung meiner Anleitung erledigen.

Anlegen von NS und A Record beim Domain-Provider

Damit der DNS Server, welcher anschließend eingerichtet wird, auch aus dem Internet erreichbar ist, müssen die folgenden Einträge beim Anbieter gesetzt werden, bei welchem ihr eure Domain registriert habt.

A Record – um dem Internet mitzuteilen unter welcher Adresse der Nameserver zu erreichen ist.

ns1.beispiel.de    A     10.0.0.0

NS Record – um dem Internet mitzuteilen, an welchen Nameserver alle Anfragen weitergeleitet werden sollen.

dyndns.beispiel.de     NS     ns1.beispiel.de

Einrichtung von PowerDNS

Zu Beginn werden die benötigten Pakete installiert:

apt-get install pdns-server

apt-get install pdns-backend-mysql

Nun gibt es mehrere möglichkeiten die Konfiguration der Datenbankverbindung in PowerDNS vorzunehmen. Ich habe hier die Variante der offiziellen powerdns Dokumentation präferiert, bei welcher die Datenbankverbindung in der pdns.conf unter /etc/powerdns/ eingetragen wird.

Dazu öffnete ich mir die Config mit nano:

nano /etc/powerdns/pdns.conf

und navigierte zu folgender Zeile: # launch Which backends to launch and order to query them in

Hier fügte ich dann meine zukünftigen Zugangsdaten zur Datenbank hinzu. Außerdem habe ich definiert, welches Backend genutzt werden soll. (Da die Datenbank zu dem Zeitpunkt noch nicht existiert, können die Zugangsdaten frei gewählt werden.)

launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=pdns
gmysql-dbname=powerdns
gmysql-password=euerpasswort

Anschließend schloss und speicherte ich die Datei.

Damit PowerDNS später keine Fehler beim neustart des Daemon ausgibt, habe ich die durch den Download automatisch erstellten Konfigurationen für andere Backends gelöscht. Die Fehler treten auf, wenn mehrere Backends definiert sind.

rm -rf /etc/powerdns/pdns.d/*

Einrichtung der Datenbank

Für die Datenbank, in welche PowerDNS später hineinschreibt, wird ein genaues Schema vorgegeben. Dieses habe ich für meine Installation übernommen.

Ich meldete mich also im DBMS an:

mysql -u root -p

Erstellte eine neue Datenbank namens powerdns:

CREATE DATABASE powerdns;

Erstellte einen Benutzer mit den Zugangsdaten, welche ich vorher in der Konfiguration von PowerDNS hinterlegt habe:

CREATE USER 'pdns'@'localhost' IDENTIFIED BY 'euerpasswort';

Erteilte dem Benutzer komplette Rechte zum verändern der Datenbank:

GRANT ALL PRIVILEGES ON powerdns.* TO 'pdns'@'localhost';

und lud die GRANT Tabellen neu:

FLUSH PRIVILEGES;

Die Vorbereitungen hatte ich somit erledigt. Danach führte ich stumpf die folgenden Befehle zur Erstellung des Datenbank-Schema aus.

USE powerdns;

CREATE TABLE domains (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  VARCHAR(6) NOT NULL,
  notified_serial       INT UNSIGNED DEFAULT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE UNIQUE INDEX name_index ON domains(name);

CREATE TABLE records (
  id                    BIGINT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername             VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX ordername ON records (ordername);

CREATE TABLE supermasters (
  ip                    VARCHAR(64) NOT NULL,
  nameserver            VARCHAR(255) NOT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' NOT NULL,
  PRIMARY KEY (ip, nameserver)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE TABLE comments (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  name                  VARCHAR(255) NOT NULL,
  type                  VARCHAR(10) NOT NULL,
  modified_at           INT NOT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
  comment               TEXT CHARACTER SET 'utf8' NOT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);

CREATE TABLE domainmetadata (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  kind                  VARCHAR(32),
  content               TEXT,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);

CREATE TABLE cryptokeys (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  flags                 INT NOT NULL,
  active                BOOL,
  content               TEXT,
  PRIMARY KEY(id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX domainidindex ON cryptokeys(domain_id);

CREATE TABLE tsigkeys (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255),
  algorithm             VARCHAR(50),
  secret                VARCHAR(255),
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

Anlegen einer Zone und hinzufügen von Records

Nachdem das Datenbank-Schema angelegt wurde, konnte ich damit beginnen, die Tabellen mit Daten zu füllen.

In meinem Beispiel ist der Nameserver für die Domain dyndns.beispiel.de zuständig. Alle DNS Anfragen für dyndns.beispiel.de und alle darunter liegenden Adressen (netz.dyndns.beispiel.de) werden an den Server weitergeleitet. (wurde im ersten Schritt festgelegt)

Damit der Server auf diese Anfragen antworten kann, musste eine Zone für die Domain eingerichtet werden. Der Zone konnte ich anschließend Records hinzufügen. Mit folgendem Befehl legte ich die Zone an:

INSERT INTO domains (name, type) values ('dyndns.beispiel.de', 'NATIVE');

Damit die Zone richtig funktioniert musste anschließend noch ein „SOA“ Record gesetzt werden. Hierbei habe ich mich auch wieder an die Angaben der Doku von PowerDNS gehalten.

INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'dyndns.beispiel.de','localhost admin.beispiel.de 1 10380 3600
604800 3600','SOA',86400,NULL); 

Nun konnte mit dem einpflegen der eigenen Records begonnen werden. Für das Beispiel habe ich einen A record angelegt, welcher die Anfragen an netz.dyndns.beispiel.de nach 1.1.1.1 auflöst.

INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES (1,'netz.dyndns.beispiel.de','1.1.1.1','A',120,NULL);

Die IP ist in diesem Anwendungsfall des DNS erstmal irrelevant, da sich diese nach Fertigstellung des Projektes aller paar Minuten (je nachdem wie oft sich eure öffentliche IP aktualisiert) automatisch aktualisieren soll.

Einrichtung einer automatischen Aktualisierung der Records 

Damit der eben angelegte Record immer mit der IP des Gerätes bzw. des Heimnetzwerks aktualisiert wird, habe ich ein Script angewendet, welches die Daten, welche minütlich von einem Gerät im Netzwerk, oder einer FritzBox gesendet werden, in die Datenbank schreibt und somit den Record aktualisiert. Wie genau die Einrichtung der automatisierten Aktualisierung erfolgt, werde ich nun näher erklären.

Wie bereits erwähnt, setze ich als Bestandteil meiner Anleitung voraus, dass bereits ein Webserver auf dem Gerät eingerichtet ist, welches als DNS Server dient. Um das folgende Script nutzen zu können, muss außerdem noch PHP auf dem Server installiert sein.

Das Script, welches ich genutzt habe, stammt von Simon Lauger. Auf seiner Website hat er dieses veröffentlicht. Für meine Zwecke habe ich das Script minimal angepasst.

Da das Script später per HTTP/HTTPS erreicht werden soll, musste dieses an einer beliebigen Stelle im Webroot erstellt werden. In den meisten Fällen sollte das unter /var/www/ bzw. /var/www/html sein.

nano  /var/www/html/dnsupdate.php

Hier kann nun der folgende Code eingefügt werden.

<?php
/**
DynDNS-Service für PowerDNS
*
@author Simon "cmon2k" Lauger <simon@lauger.name
@date   06.09.2012
*/ 

$dsn     = 'mysql:dbname=powerdns;host=127.0.0.1'; // Datenbank DSN
$user    = 'pdns'; // Name der Datenbank
$pass    = 'euerpasswort'; // Datenbank Passwort

// Auth-String der als GET-Parameter übermittelt werden muss
$auth    = 'eueranderespasswort';
 
// Für alle im Array enthaltenen Records dürfen Updates gefahren werden
$allowed = array('netz.dyndns.beispiel.de'); 

$domain = (isset($_GET['domain'])) ? $_GET['domain'] : null;
$ip     = $_SERVER['REMOTE_ADDR'];

if ((empty($domain) || is_null($domain)) || (empty($ip) || is_null($ip))) {
        die('missing parameter');
        exit;
}

if (!in_array($domain, $allowed)) {
        die('forbidden domain name');
        exit;
}

if (!isset($_GET['passwd']) || $_GET['passwd'] != $auth) {
        die('authentification failed');
        exit;
}

try {
    $dbh = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    echo 'Connection failed: ' . $e->getMessage();
}

$check = $dbh->prepare('SELECT id FROM records WHERE name = :name AND type = :type');
$check->bindParam(':name', $domain);
$check->bindValue(':type', 'A');
$check->execute();
$result = $check->fetch(PDO::FETCH_ASSOC);

if (empty($result)) {
        die('record not found');
        exit;
} else {
        $update = $dbh->prepare('UPDATE records SET content = :content WHERE id = :id LIMIT 1');
        $update->bindParam(':content', $ip);
        $update->bindParam(':id',      $result['id']);
        if ($update->execute()) {
                die('update successful (' . htmlentities($ip, ENT_QUOTES) . ')');
                exit;
        }
        die('update returned false');
        exit;
}

?>

Die Variablen $dsn, $user und $pass müsst ihr mit euren eigenen Daten, welche ihr im Schritt „Einrichten der Datenbank“ festgelegt habt, befüllen.

In der Variable $auth müsst ihr ein Kennwort definieren, welches später von dem Gerät übermittelt wird, welches den DNS Record updaten soll. (würde das nicht entsprechend implementiert sein, könnte ja jeder den Record updaten)

In der Variable $domain wird noch der Record angegeben, der aktualisiert werden soll. (Beispielsweise für den weiter oben angelegten Record – netz.dyndns.beispiel.de)

Information: Von hier an werden die Schritte an dem Gerät durchgeführt, welches für das Updaten des Records zuständig ist.

Nachdem das Skript entsprechend angepasst wurde, kann man testen, ob es wie gewünscht per HTTP/HTTPS erreichbar ist. Je nachdem wo sich das Skript im Webroot befindet, kann dieses einfach aufgerufen werden.

Beispielsweise https://beispiel.de/dnsupdate.php

Der Text „missing parameter“ sollte erscheinen.

Um nun zu testen, ob sich der Record auch über das Skript updaten lässt, müssen beim Aufruf des Skriptes die Parameter $auth und $domain übermittelt werden, welche im Skript definiert wurden.

Mit den von mir weiter oben gemachten Angaben, würde das also wie folgt aussehen:

https://beispiel.de/dnsupdate.php?domain=netz.dyndns.beispiel.de&passwd=eueranderespasswort

Hier sollte der Text „update successful“ erscheinen.

Nun wurde in den Record die IP eingetragen, von welcher die Anfrage gesendet wurde. Im Idealfall, wenn kein VPN oder ähnliches genutzt wird, sollte diese die IP des Heimnetzes sein.

Um das Update zu automatisieren kann nun zwischen zwei Varianten gewählt werden. Die aktuelle IP kann dem DNS Server entweder über ein beliebiges Gerät im Netzwerk mitgeteilt werden, oder aber durch einen Router, welcher sowieso immer eingeschaltet ist. Nachfolgend werde ich beide Varianten erläutern.

Update durch beliebiges Gerät

Um das Update durch ein Gerät im Netz durchzuführen, kann ein Cronjob eingerichtet werden. Dieser ruft die URL mit den entsprechenden Variablen aller paar Minuten auf.

crontab -e

Mit folgendem Job wird realisiert, dass die aktuelle IP jede Minute an den DNS Server gesendet wird.

* * * * * curl 'https://beispiel.de/dnsupdate.php?domain=netz.dyndns.beispiel.de&passwd=eueranderespasswort >/dev/null 2>&1

Sollte sich die Adresse des Heimnetzes nun ändern, dauert es höchstens eine Minute, bis der DNS Server die aktuelle IP hat.

Update durch Router

Einige Router haben bereits vorgefertigte Masken, welche nur mit den entsprechenden Informationen des DNS Dienstes ausgefüllt werden.

Die Konfiguration für eine Fritzbox würde wie folgt aussehen.

Wenn dem Anschluss nun eine neue IP vom Provider zugewiesen wird, teilt die Fritzbox dem DNS Dienst die aktuelle IP mit.

Abschluss

Ich hoffe, dass ich mit meinem Artikel auch andere dabei unterstützen konnte, ihren eigenen DynDNS Dienst einzurichten.

Solltet ihr etwas nicht ganz verstanden haben, bin ich natürlich gerne bereit eure Fragen in den Kommentaren zu beantworten.

11 Comments

Add a Comment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert