Website-Icon .: blog cscholz.io :.

Betrieb eines privaten dyndns Dienstes

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:

2. Was brauche ich dafür?

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

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“.

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.

 

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:

$ dig +short A fritzbox.ddns.domaint.tld
1.2.3.4
$ 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.

 
Die mobile Version verlassen