1. Warum ein privater DynDNS-Dienst?

    Ein privater DynDNS-Dienst bietet zahlreiche Vorteile gegenüber öffentlichen Diensten. Hier sind einige Gründe, warum du einen eigenen Dienst in Betracht ziehen solltest:

    • Nutzung mit eigener Domain: Du kannst deine eigene Domain verwenden und hast die volle Kontrolle über die Subdomains und DNS-Einträge.
    • Keine Nutzung öffentlicher Dienste: Du bist nicht auf externe Anbieter angewiesen und vermeidest damit mögliche Einschränkungen oder Ausfälle von Dritten.
    • Mehr Flexibilität: Du kannst die Aktualisierungsintervalle, Sicherheitsmechanismen und andere Parameter nach deinen eigenen Wünschen anpassen.

    2. Was brauche ich dafür?

    Um einen privaten DynDNS-Dienst einzurichten, benötigst du folgende Infrastruktur und Software:

    • Einen eigenen (v)Server: Dieser Server kann lokal bei dir zuhause stehen oder bei einem Hosting-Anbieter gemietet werden.
    • Einen Webserver mit PHP: PHP-Skripte werden benötigt, um die Anfragen zu verarbeiten und die DNS-Einträge zu aktualisieren.
    • Bind als autoritativen DNS-Server: Bind wird verwendet, um als autoritativer DNS-Server für die Domain (z. B. ddns.domain.tld) zu fungieren.

    3. Was muss ich einrichten?

    Damit dein privater DynDNS-Dienst reibungslos funktioniert, sind folgende Schritte erforderlich:

    3.1 Eine DNS-Zone für ddns.domain.tld

    Der einfachheitshalber gehe ich davon aus, dass die domain.tld bereits auf dem System sprich bind funktional konfiguriert ist. Zur Einrichtung der Sub-Domain ist mind. ein DNS Server Eintrag zu setzten. In dem Beispiel heißt die Zone ddns.domain.tld.

    /etc/bin/domain.tld.zone

    [...]
    
    @          IN       A       1.2.3.4
    ns         IN       A       1.2.3.4
    
    ; Subzone ddns.cscholz.io
    ddns       IN       NS      ns
    
    [...]

    Die Konfiguration der Sub-Domain sieht dann wie folgt aus:

    /etc/bin/named.conf.local

    [...]
    zone "ddns.domain.tld" {
       type master;
       file "/etc/bin/ddns.domain.tld.zone";
       allow-update { 127.0.0.1; };
    };
    [...]

    In dieser Zone wird festgelegt, dass der DNS-Server autoritativ für ddns.domain.tld ist und dynamische Updates von localhost akzeptiert werden.

    3.2 Konfiguration der Zone-Datei

    Erstelle oder bearbeite die Zonendatei unter /etc/bind/ddns.domain.tld:

    $ORIGIN .
    $TTL 300 ; 5 minutes
    ddns.domain.tld      IN    SOA ns.domain.tld. postmaster.domain.tld. (
                                   2023034158 ; serial
                                   3600 ; refresh (1 hour)
                                   900 ; retry (15 minutes)
                                   1209600 ; expire (2 weeks)
                                   180 ; minimum (3 minutes)
    )
    $TTL 180 ; 3 minutes
                         NS        ns1.domain.tld.
                         A         1.2.3.4
    $ORIGIN ddns.domain.tld.

    Der Serial muss bei jedem Update inkrementiert werden. 

    3.3 Bind muss dynamische DNS-Updates erlauben

    Stelle sicher, dass der Bind-Dienst nach jeder Änderung neu gestartet wird:

    sudo systemctl restart bind9

    3.4 Das Update-Script

    Das Script stammt nicht von mir, sondern von saudiqbal.github.io. Ich habe lediglich die Änderung eingefügt, dass mit jedem Update auch ein TXT Record geschrieben wird. Warum kommt später.

    Entscheident im Script sind die Zeilen: 

    'fritzbox.ddns.domain.tld'=>['redcvbjko987654esdcvbjo0pl','ddns.domain.tld'],
    'unifi.ddns.domain.tld'=>['987ztfrt6z7ujko98u','ddns.domain.tld']

    Hier wird festgelegt, welcher hostname (fritzbox) mit welchem Kennwort (redcvbjko987654esdcvbjo0pl) in welcher DNS Zone (ddns.domain.tld) aktualisert werden soll. Weitere Einträge können wie im Beispiel ergänzt werden.

    <?php
    header('Content-Type: text/plain');
    
    $hostname = $_GET['hostname'] ?? null;
    $ipV4Address = $_GET['myip'] ?? null;
    $ipV6Address = $_GET['myipv6'] ?? null;
    $dnsKey = $_GET['password'] ?? null;
    $action = $_GET['action'] ?? null;
    // DNS Time to live in seconds
    $dnsttl = $_GET['ttl'] ?? '60';
    //date_default_timezone_set('Europe/Berlin');
    date_default_timezone_set('Europe/Berlin');
    assert(date_default_timezone_get() === 'Europe/Berlin');
    
    if (isset($dnsKey) && preg_match('/[^a-zA-Z0-9]/', $dnsKey))
    {
    echo 'badauth';
    exit;
    }
    
    if (isset($hostname) && preg_match('/[^a-z0-9\.\-\_]/', $hostname))
    {
    echo 'notfqdn';
    exit;
    }
    
    if (isset($ipV4Address) && preg_match('/[^0-9\.]/', $ipV4Address))
    {
    echo 'badsys';
    exit;
    }
    
    if (isset($ipV6Address) && preg_match('/[^a-f0-9\:]/', $ipV6Address))
    {
    echo 'badsys';
    exit;
    }
    
    // Credintials format: hostname (homevpn.dns.example.com), password (rUOrbg8R2RvVmOhcVoOGnIRIgWCY0W5x), name of DNS zone to update (ddns.domain.tld)
    $login = false;
    $user_info=[
    'fritzbox.ddns.domain.tld'=>['redcvbjko987654esdcvbjo0pl','ddns.domain.tld'],
    'unifi.ddns.domain.tld'=>['987ztfrt6z7ujko98u','ddns.domain.tld']
    ];
    foreach ($user_info as $key => $value) {
    $login = ($key == $hostname && $value[0] == $dnsKey) ? true : false;
    if($login) break;
    }
    if(!$login) {
    echo 'badauth';
    exit;
    }
    
    if ($hostname == null) {
    header('HTTP/1.1 400 Bad Request');
    echo 'notfqdn';
    exit;
    }
    
    if ($action == 'delete') {
    $descriptorspec = array(
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w')
    );
    $process = proc_open('nsupdate', $descriptorspec, $pipes, NULL, NULL);
    fwrite($pipes[0], "server 127.0.0.1\n");
    fwrite($pipes[0], "zone $value[1]\n");
    fwrite($pipes[0], "update delete $hostname.\n");
    fwrite($pipes[0], "send\n");
    fclose($pipes[0]);
    echo stream_get_contents($pipes[1]);
    proc_close($process);
    echo 'good';
    exit;
    }
    
    if (!$ipV4Address and !$ipV6Address) {
    $ip = $_SERVER['REMOTE_ADDR'];
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    // Use client IP address if no IPv4 given
    $ipV4Address = $ip;
    } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
    // Use client IP address if no IPv6 given
    $ipV6Address = $ip;
    }
    }
    
    // Check whether the given IPv4 address is valid
    if ($ipV4Address and !filter_var($ipV4Address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    header('HTTP/1.1 400 Bad Request');
    echo 'badsys';
    exit;
    }
    
    // Check whether the given IPv6 address is valid
    if ($ipV6Address and !filter_var($ipV6Address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
    header('HTTP/1.1 400 Bad Request');
    echo 'badsys';
    exit;
    }
    
    
    if ($ipV4Address) {
    ipv4_update();
    }
    if ($ipV6Address) {
    ipv6_update();
    }
    
    function ipv4_update()
    {
    global $hostname, $ipV4Address, $dnsttl, $value;
    $descriptorspec = array(
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w')
    );
    
    $timestamp = time();
    // UTC
    $timestamp = "\"" . gmdate('d.m.Y, H:i:s T (O)', $timestamp) . "\"";
    
    // CEST
    $timestamp = "\"" . date('d.m.Y, H:i:s T (O)') . "\"";
    $process = proc_open('nsupdate', $descriptorspec, $pipes, NULL, NULL);
    fwrite($pipes[0], "server 127.0.0.1\n");
    fwrite($pipes[0], "zone $value[1]\n");
    fwrite($pipes[0], "update delete $hostname. A\n");
    fwrite($pipes[0], "update add $hostname. $dnsttl A $ipV4Address\n\n");
    fwrite($pipes[0], "update delete $hostname. TXT\n");
    fwrite($pipes[0], "update add $hostname. $dnsttl TXT " . $timestamp . "\n\n");
    fclose($pipes[0]);
    echo stream_get_contents($pipes[1]);
    proc_close($process);
    }
    
    function ipv6_update()
    {
    global $hostname, $ipV6Address, $dnsttl, $value;
    $descriptorspec = array(
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w')
    );
    $process = proc_open('nsupdate', $descriptorspec, $pipes, NULL, NULL);
    fwrite($pipes[0], "server 127.0.0.1\n");
    fwrite($pipes[0], "zone $value[1]\n");
    fwrite($pipes[0], "update delete $hostname. AAAA\n");
    fwrite($pipes[0], "update add $hostname. $dnsttl AAAA $ipV6Address\n\n");
    fclose($pipes[0]);
    echo stream_get_contents($pipes[1]);
    proc_close($process);
    }
    
    echo 'good';
    ?>

    Speichere dieses Skript z. B. unter /var/www/html/ddns.php. Dieses PHP-Script wird die dynamischen Updates verarbeiten und die IP-Adresse in den DNS-Eintrag einfügen.

    4. Wie aktualisiere ich die Einträge?

    Je nach Gerät, das du verwendest, gibt es unterschiedliche Methoden, die dynamischen DNS-Einträge zu aktualisieren.

    4.1 Manuell per Fritzbox

    Dazu per Browser folgende URL aufrufen:

    https://domain.tld/ddns.php?password=redcvbjko987654esdcvbjo0pl&hostname=fritzbox.ddns.domain.tld

    4.2 Ein Beispiel für Fritzbox

    Gehe in die Fritzbox-Einstellungen unter „Internet“ → „Freigaben“ → „DynDNS“.

    • Update URL: https://domain.tld/ddns.php?password=<pass>&hostname=<domain>
    • Domain Name: fritzbox.ddns.domain.tld
    • Username: none
    • Passwort: redcvbjko987654esdcvbjo0pl

    Das Script nutzt die IP Adresse, mit der die Fritzbox sich gegenüber dem Webserver ausgibt.
    Sollte die Fritzbox also doppeltes natting nutzen, würde das script dennoch funktionieren.

    4.3 Ein Beispiel für Unifi (Link zum Forum)

    Für Unifi-Geräte kannst du in den Netzwerkeinstellungen einen benutzerdefinierten DynDNS-Dienst anlegen und die URL zu deinem Update-Skript hinterlegen.

    • Service: dyndns
    • Hostname: fritzbox.ddns.domain.tld
    • Username: none
    • Password: redcvbjko987654esdcvbjo0pl
    • Server: https://domain.tld/ddns.php?password=%p&hostname=%h

     

    5. Wie kann ich prüfen, ob es geklappt hat?

    Um sicherzustellen, dass die DNS-Einträge korrekt aktualisiert wurden, kannst du mehrere Schritte zur Überprüfung durchführen:

    • Aktuelle eigene IP prüfen: Rufe z.B. checkip.cscholz.io im Browser auf
    • DNS A Record Lookup:
    $ dig +short A fritzbox.ddns.domaint.tld
    1.2.3.4
    • DNS TXT Record Lookup:
    $ dig +short TXT fritzbox.ddns.domaint.tld
    "20.09.2024, 12:13:16 CEST (+02)"

    Der TXT Record enthält als den Zeitstempel des letzten erfolgreichen Updates des DNS Eintrags.

     

    Leave A Reply