Da die Firma DynDNS aufgrund eines Versäumnisses meinerseits diverse meiner kostenlosen Accounts rückstandsfrei eliminiert hat, habe ich mich gestern spontan entschlossen, den Service einfach selbst nachzubauen. Zumindest mit so viel (bzw. wenig) Funktionalität, wie ich wirklich benötige.
Man braucht folgendes:
- Sowas wie einen „Root-Server“ mit einer öffentlichen IP-Adresse (z.B.
123.234.231.132
) und einer Domain (z.B. meinedomain.de
- Die Möglichkeit, eigene DNS-Einträge in der Domain vorzunehmen (genauer gesagt NS-Einträge für eigene Nameserver)
- Auf dem „Root-Server“:
- MySQL
- PowerDNS
- Apache mit mod_cgi
In meinem Fall war auf dem Server bereits Debian 6 (squeeze) mit MySQL und Apache installiert. PowerDNS fehlte noch.
Als erstes habe ich PowerDNS inklusive seinem MySQL-Backend installiert. Bei der Gelegenheit kann man auch direkt die später nötigen Perl-Module mit installieren:
aptitude install pdns-server pdns-backend-mysql libdbd-mysql-perl libcgi-pm-perl
PowerDNS wird die Frage stellen, an welches Interface (IP-Adresse) er sich binden soll. Das muss jeder selbst entscheiden. Es sollte nur ein DNS-Name (A-record) auf diese IP-Adresse zeigen, den müssen wir nämlich später in den SOA-Records verwenden.
Anschließend muss auf dem MySQL-Server eine Datenbank pdns
mit ein paar Tabellen erzeugt werden. Das geht mit folgendem SQL-Schnipsel:
CREATE DATABASE pdns;
USE pdns;
CREATE TABLE domains (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
master varchar(20) DEFAULT NULL,
last_check int(11) DEFAULT NULL,
type varchar(6) NOT NULL,
notified_serial int(11) DEFAULT NULL,
account varchar(40) DEFAULT NULL,
change_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY name_index (name)
) ENGINE=InnoDB;
CREATE TABLE records (
id int(11) NOT NULL AUTO_INCREMENT,
domain_id int(11) DEFAULT NULL,
name varchar(255) DEFAULT NULL,
type varchar(6) DEFAULT NULL,
content varchar(255) DEFAULT NULL,
ttl int(11) DEFAULT NULL,
prio int(11) DEFAULT NULL,
username varchar(12) DEFAULT NULL,
password varchar(64) DEFAULT NULL,
change_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY rec_name_index (name),
KEY nametype_index (name,type),
KEY domain_id (domain_id)
) ENGINE=InnoDB;
CREATE TABLE supermasters (
ip varchar(25) NOT NULL,
nameserver varchar(255) NOT NULL,
account varchar(40) DEFAULT NULL
);
GRANT SELECT ON supermasters TO pdns@localhost IDENTIFIED BY 'SuperGehaimesPasswort';
GRANT ALL ON domains TO pdns@localhost IDENTIFIED BY 'SuperGehaimesPasswort';
GRANT ALL ON records TO pdns@localhost IDENTIFIED BY 'SuperGehaimesPasswort';
Anschließend legen wir eine Domain an, z.B. dyn.meinedomain.de
, und darin dann ein paar nötige Records:
INSERT INTO domains (name, type) VALUES ('dyn.meinedomain.de', 'NATIVE');
INSERT INTO records (domain_id, name, type, content) VALUES
(1, 'dyn.meinedomain.de', 'SOA', 'dns.meinedomain.de hostmaster.meinedomain.de 2013121900 1800 300 604800 3600'),
(1, 'dyn.meinedomain.de', 'NS', 'dns.meinedomain.de'),
(1, 'dyn.meinedomain.de', 'A', '123.234.231.132');
Dabei sollte dns.meinedomain.de
durch den DNS-Namen der IP-Adresse ersetzt werden, auf der der vorhin installierte PowerDNS zu erreichen ist. dyn.meinedomain.de
ist die Subdomain, unter der später die dynamisch zu erreichenden Hosts angelegt werden.
Der A-Record (letzter Datensatz) ist später für den Apache nötig und sollte daher auch auf die IP-Adresse zeigen, auf der der Apache sitzt.
Nun muss PowerDNS noch konfiguriert werden, so dass er auch das MaSQL-Backend benutzt. Das geht, indem in der Datei /etc/powerdns/pdns.d/pdns.local
folgende Zeilen ergänzt werden:
# MySQL Configuration
launch=gmysql
gmysql-host=localhost
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=SuperGehaimesPasswort
Nach einem PowerDNS-Neustart mit service pdns restart
sollte MySQL mit SHOW PROCESSLIST;
ein paar Verbindungen des Users pdns
anzeigen:
mysql> show processlist;
+-------+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-------+------+-----------+------+---------+------+-------+------------------+
| 7104 | pdns | localhost | pdns | Sleep | 160 | | NULL |
| 7105 | pdns | localhost | pdns | Sleep | 64 | | NULL |
| 7106 | pdns | localhost | pdns | Sleep | 37 | | NULL |
| 7108 | pdns | localhost | pdns | Sleep | 129 | | NULL |
| 30246 | root | localhost | NULL | Query | 0 | NULL | show processlist |
+-------+------+-----------+------+---------+------+-------+------------------+
5 rows in set (0.00 sec)
mysql>
So. Wenn das geklappt hat, sollte man mit dem Kommando dig @123.234.231.132 dyn.meinedomain.de any
die in der Datenbank angelegten Einträge zu sehen bekommen.
# dig @123.234.231.132 dyn.meinedomain.de any
; <<>> DiG 9.7.3 <<>> @123.234.231.132 dyn.meinedomain.de any
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6595
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;dyn.meinedomain.de. IN ANY
;; ANSWER SECTION:
dyn.meinedomain.de. 3600 IN NS dns.meinedomain.de.
dyn.meinedomain.de. 3600 IN SOA dns.meinedomain.de. hostmaster.meinedomain.de. 2013121900 1800 300 604800 3600
dyn.meinedomain.de. 3600 IN A 123.234.231.132
;; Query time: 8 msec
;; SERVER: 123.234.231.132#53(123.234.231.132)
;; WHEN: Thu Dec 19 14:27:43 2013
;; MSG SIZE rcvd: 140
Wenn das so weit klappt, können wir in die DNS-Konfiguration des Providers gehen und die Subdomain an unseren Server delegieren. Das geht ganz einfach, indem man im DNS-Zonefile der Zone meinedomain.de
folgenden Record hinzufügt:
dyn IN NS dns.meinedomain.de.
Jetzt (bzw. nachdem die TTL beim angefragten Cache abgelaufen ist) werden DNS-Anfragen für [irgendwas].dyn.meinedomain.de
an unseren PowerDNS-Server dns.meinedomain.de
weiter geleitet.
Wir gehen nochmal in die Datenbank und legen dort einen A-Record für den ersten DynDNS-Host an, z.B. so:
INSERT INTO records (domain_id, name, type, content, username, password) VALUES
(1, 'test.dyn.meinedomain.de', 'A', '127.0.0.1', 'benutzer', 'Gehaim');
Benutzername und Passwort werden später im DynDNS-Client angegeben. Die IP-Adresse ist hier erstmal 127.0.0.1
. Man sollte den Record dann auch mit dig test.dyn.meinedomain.de
abfragen können.
Jetzt brauchen wir ein Update-Script. Ich habe das Verzeichnis /opt/dyn
erzeugt und darin die Datei update
, ein Perl-Script mit folgendem Inhalt:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use DBI;
use utf8;
binmode STDOUT, ':encoding(UTF-8)';
my $q = new CGI;
my $ip = $q->param('ip');
my $user = $q->param('user');
my $pass = $q->param('pass');
my $host = $q->param('host');
unless (
$ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ and
$user =~ /^[a-zA-Z0-9\-\._]+$/ and
$pass !~ /['"\t]/ and
$host =~ /^\S+\./
) {
print $q->header( -status => '400 Bad Request' );
exit;
}
my $dbh = DBI->connect("DBI:mysql:database=pdns;host=localhost", 'pdns', 'SuperGehaimesPasswort', {'RaiseError' => 1, 'mysql_enable_utf8' => 1});
my $db_update = $dbh->prepare('UPDATE records SET content = ? WHERE name = ? AND TYPE = ? AND username = ? AND password = ?');
eval {
$db_update->execute($ip, $host, 'A', $user, $pass);
};
if ($@) {
warn 'SQL Error: ' . $@;
print $q->header( -status => '500 Internal Server Error' );
$dbh->disconnect;
exit;
}
$dbh->disconnect;
print $q->header( -status => '200 OK' );
Die Datei muss ausführbar sein, also sollte man nach dem Anlegen chmod +x update
machen!
Anschließend wird der Apache konfiguriert, indem man die Datei /etc/apache2/conf.d/dyn
mit folgendem Inhalt anlegt:
Alias /dyn /opt/dyn
<Directory /opt/dyn>
<FilesMatch ^(update)$>
SetHandler cgi-script
Options +ExecCGI
</FilesMatch>
</Directory>
Das sorgt dafür, dass das Update-Script unter der URL http://dyn.meinedomain.de/dyn/update
erreichbar ist. Es erwartet vier Parameter:
- IP-Adresse
- Benutzername
- Passwort
- Hostname
Folgendermaßen sieht also ein Aufruf aus:
http://dyn.meinedomain.de/dyn/update?ip=127.0.0.2&user=benutzer&pass=Gehaim&host=test.dyn.meinedomain.de
Ein Aufruf dieser URL sollte dafür sorgen, dass der A-Record für test.dyn.meinedomain.de
aktualisiert wird und ab sofort auf 127.0.0.2
zeigt.
In meiner Fritz!Box 7390 kann ich als DynDNS-Provider "Benutzerdefiniert" wählen und dann die entsprechende URL als Update-URL mit Platzhaltern eintragen. Das sieht dann so aus:
http://dyn.meinedomain.de/dyn/update?ip=<ipaddr>&user=<username>&pass=<pass>&host=<domain>
Das Feld "Domainname" wird mit test.dyn.meinedomain.de
gefüllt, Benutzername und Passwort mit benutzer
und Gehaim
.
Ein anderer Router im Dunstkreis meiner Administration hat ein altes FreeWRT. Dort habe ich den veralteten ez-ipupdate
einfach abgeschaltet und durch das Skript /etc/ppp/ip-up.d/zzz_dyn_dns
ersetzt, was folgenden Inhalt hat:
#!/bin/sh
# pppd provides $IFNAME and $IPREMOTE amd $IPLOCAL
wget -O /dev/null -q http://dyn.meinedomain.de/dyn/update?ip=$IPLOCAL\&user=benutzer\&pass=Gehaim\&host=test.dyn.meinedomain.de
Fertig.
Man kann in der Datenbank im Feld change_date
ablesen, wann der Client das letzte Mal aktualisiert hat. Die Fritz!Box 7390 kann auch per HTTPS aktualisieren, der wget
Client im FreeWRT will das nicht. Damit kann ich aber leben. Benutzername und Passwort werden ohnehin als GET-Parameter in der URL übertragen. Wobei es dem Perl-Script update
eigentlich egal sein dürfte, ob die Parameter als GET oder POST kommen.
Was noch fehlt ist ein schickes kleines Web-Interface. Vielleicht kommt ja eines über die Feiertage... 😉