Ausgangslage
Ich moechte also meinen eigenen dynamischen DNS-Server betreiben. Gratulation! Und wie macht man das nun am geschicktesten?
Ich habe die Domain ad001.de. Ich kann mir einen Subdomain delegieren lassen -- zum Beispiel dynamic.ad001.de. Nun muss ich nur noch einen DNS-Server fuer diese delegierte Zone aufsetzen. Und ihn irgendwie dazu bringen, name.dynamic.ad001.de auf Zuruf einer IP-Adresse zuzuordnen. Also einen A-Record (um ins Zonefile-DNS-Sprech zu fallen) zu setzen. Wenn mir danach ist, auch einen MX-Record.
Organisatorische und Softwarevoraussetzungen
Was braucht es also? Schauen wir auf die Zutatenliste:
- Einen Ort im Internet, wo ein DNS-Server laufen kann, auf der gleichen (virtuellen) Maschine sollte auch irgendwas sein, worueber sich die Aenderungen einspielen lassen. Ich habe mich fuer einen Apachen und PHP entschieden. Die Wahl eines Webdienstes hat einen einfachen Grund -- dies ist der am seltensten geblockte Dienst. Ein beliebiger Port waere zwar vielleicht einfacher geworden, muss dafuer aber eher damit rechnen, an Firewalls zu scheitern. Das wollen wir ja nicht, insbesondere in Hinblick auf den folgenden Abschnitt...
- Eine eigene Domain, von der wir zumindest (wenn schon nicht die ganze Domain) eine Subdomain auf unseren eigenen kleinen DNS-Server delegieren koennen.
Geschenkte Zusatzfunktionalitaet
Einen kleinen Moment mal. Ich werde am Ende meiner Bastelei einen Dienst haben, der sich regelmaessig durch Abrufe einer vorgegebenen URL auf einem definierten Server zu erkennen geben wird. Das bedeutet im Umkehrschluss natuerlich auch, dass dieser Server mitbekommt, welcher Rechner wann wo und von welcher IP aus online ist. Dazu habe ich noch die Moeglichkeit implementiert, einen kurzen Hinweistext zu uebergeben. Wer will kann hier uebergeben, ob das System gerade hochgefahren wurde, eine Nutzeranmeldung stattfand oder ob es ein rein zeitgesteuerter Heartbeat sein soll.
Damit tut mein Dienst natuerlich nichts anderes, was nicht auch "Facebook", "Google" und "Spiegel Online" tun -- ein Bewegungsprofil anlegen. Also eigentlich langweilig. Ausser in einem Fall: Diebstahl. Oder "Abhandenkommen", wenn der Verteidiger spricht. Denn dann ist es hoechst interessant, von welcher IP aus sich mein Geraet das naechste Mal beim Server meldet. Und dann gibt es zwei Interessen, die ich verfolgen kann:
- Zum einen kann ich mich gegebenenfalls Remote an meinem System anmelden (gelobt seien Multi-User-Systeme, bei denen der Dieb nicht in diesem Fall abgemeldet wird, sondern weiter froehlich meinen Rechner inspizieren kann) und wahlweise Dateien zu mir "nach Hause" transferieren -- oder aber, weil schneller, aus meinem Home loeschen, bevor sie in den falschen Haenden angekommen (und entdeckt!) sind.
- Zum anderen ist es natuerlich auch hoechst hilfreich, wenn man auf diesem Weg an die aktuelle IP-Adresse des Standorts des Notebooks gelangt. Besser noch, mitsamt eines regelmaessigen Timestamps. Ueber die IP wird man (wird die Polizei/Staatsanwaltschaft) recht schnell zu einem Namen und einer Adresse gelangen koennen... und das Vorhandensein eines Timestamps macht aus der vagen Vermutung einen recht konkreten Hinweis.
Ein Ausbau des Dienstes zu einem "Selbstzerstoerungsmechanismus" sei dem fortgeschrittenen Leser als Uebungsaufgabe ueberlassen. Im Anschluss an die Vernichtung seines Home-Verzeichnisses erwarte ich einen zehnseitigen Aufsatz zum Titel "Meine groesste Dummheit der letzten 32 Stunden" an ad001@uni-rostock.de.
Und wer nun irrtiert ist oder sich gar beschweren will, dass da quasi ein gesamtes Bewegungsprofil entsteht, der moege sich einmal ueberlegen, wo dieses Bewegungsprofil ansonsten noch herumliegt. Ich hab da oben schon ein paar Verdaechtige genannt. Und da waren noch gar keine Seiten mit pornographischem Inhalt dabei...
nsd einrichten
Ich habe mich als Nameserver fuer den nsd entschieden, da ich mit ihm schon aus einem anderen Kontext Erfahrung habe und ihn als den netten kleinen Bruder des Ueberfliegers BIND in Erinnerung habe. Als kurzerhand installiert und ein minimales Config-File angelegt:
[ad001@ad001 ~]$ cat /usr/local/etc/nsd/nsd.conf
server:
# don't answer VERSION.BIND and VERSION.SERVER CHAOS class queries
hide-version: yes
# listen only on IPv4 connections
ip4-only: yes
# listen only on IPv6 connections
ip6-only: no
username: bind
zone:
name: "dynamic.ad001.de"
zonefile: "forward/dynamic.ad001.de"
Damit weiss der nsd, dass sich alles fuer die DNS-Zone "dynamic.ad001.de" im genannten Zonefile abspielen wird. Und dieses Zonefile sieht im Grundstock so aus:
[ad001@ad001 ~]$ cat /usr/local/etc/nsd/forward/dynamic.ad001.de
$TTL 10
@ IN SOA ad001.is.some.where. hostmaster.dynamic.ad001.de. (
2011110503 ; Serial
10 ; Refresh
10 ; Retry
10 ; Expire
600 ) ; Negative Cache TTL
IN NS ad001.is.some.where.
IN A 1.2.3.4
update IN A 1.2.3.4
blubb IN A 127.0.0.1
blubb IN MX 10 127.0.0.1.
Das war's auch schon. Zugegeben, man muss ein paar Ersetzungen machen. Das war's dann aber schon, nun gilt weltweit (hier auf einer Maschine des RZ der Uni Rostock):
ad001@triton:~> host -a blubb.dynamic.ad001.de Trying "blubb.dynamic.ad001.de" ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5853 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;blubb.dynamic.ad001.de. IN ANY ;; ANSWER SECTION: blubb.dynamic.ad001.de. 10 IN MX 10 127.0.0.1. blubb.dynamic.ad001.de. 10 IN A 127.0.0.1 Received 77 bytes from 139.30.8.7#53 in 51 ms ad001@triton:~>
Das war soweit ja schonmal angenehm einfach. Nun muss sich der Spass nur noch auf Zuruf aendern lassen.
Aktualisierung
Hier kommt nun wieder ein wenig geskripteter Frickelkram. Ich habe mich fuer folgendes Design entschieden:
- eine
update.phpnimmt die Aenderungen (und Trace-Notices) entgegen und legt sie in Dateien (einer Datenbank) ab. Wesentliche Aufgabe: Eingabeentschaerfung, also dafuer sorgen, dass die uebergebenen Daten in in einer Form vorliegen, die keinen der folgenden Schritte gefaehrlich erscheinen laesst - ein zweites Skript prueft, ob es Aenderungen gibt, die beruecksichtigt werden muessen. Diese werden bei Bedarf eingebaut.
update.php dazu fuehrt, dass auch der Nameserver jedes Mal behelligt wird. Einmal pro Minute ist fuer DNS-Verhaeltnisse schon sehr schnell... bedenkend, dass DNS auch gerne einmal mit Update-Zeiten von 24 Stunden arbeitet.
Zunaechst nun also die update.php. Sie wird von einem Apache mit PHP-Modul ganz normal ueber Port 80 angeboten. Dabei koennen folgende Parameter uebergeben werden: update.php?host=hostname[&trace=1 [¬e=notice] ]. Dabei ist host der Hostname, der hinzugefuegt oder aktualisiert werden soll. Er wird vom Skript von saemtlichen nicht-alphanumerischen Zeichen bereinigt und auf 30 Zeichen begrenzt. trace ist eine optionale Angabe -- wenn hier eine "1" uebergeben wird, wird dieser Aufruf ins tracefile eingetragen und hinterlaesst Fusstappsen (jenseits derer im Webserverlog ;). Mit note kann schliesslich noch eine Notiz hinzugefueht werde, die auf 50 Zeichen begrenzt ist (hier ist mir relativ egal, was da hineingeschieben wird, nur bitte keine Anf"uhrugszeichen oder Semikola. Die IP-Adresse des einzutragenden Hosts hingegen wird direkt aus dem Aufruf ermittelt, um Spielkindern das Vergnuegen zu nehmen, beliebige Aliase zu definieren. Ausserdem ist ein Parameter weniger im Aufruf immer auch ein Parameter weniger, den ein Aktualisierungsclient kennen muss...
<?php
$ip = getenv('REMOTE_ADDR');
$host = $_REQUEST['host'];
$trace = ($_REQUEST['trace'] == '1');
$host = preg_replace('/[^0-9a-zA-Z]/', '', $host);
$host = substr($host, 0, 30);
if ($host == "update") {
print "sorry. this hostname is reserved for this service :)\nplease try another one.\n";
exit(0);
}
$file = fopen("../data/$host", "w");
fwrite($file, $ip);
fclose($file);
print "assigning $ip to $host.\n";
if ($trace) {
if (! is_dir('traces')) {
mkdir('traces');
touch('traces/index.html');
}
$rev = gethostbyaddr($ip);
$file = fopen("traces/$host", "a");
date_default_timezone_set("Europe/Berlin");
$date = date("Ymd;His");
$note = $_REQUEST['note'];
$note = preg_replace('/[;"\']/', '', $note);
$note = substr($note, 0, 50);
fwrite($file, "$date;$ip;$rev;$note\n");
fclose($file);
print "trace noted at $date with reverse entry $rev and notice \"$note\".\n";
}
?>
wie zu sehen, werden die Daten einfach erstmal in Textdateien gekippt. Klar, man koennte auch eine Datenbank benutzen, wenn man es denn fein, saeuberlich und komplett ordentlich machen wollte -- das hier soll aber einfach nur funktionieren und zeigen, dass man wirklich so einfach einen dynamischen DNS-Dienst auf die Beine bekommt...
Der zweite Teil ist dann das Aktualisierungsskript compare.pl. Es nimmt die gewonnenen Adressdaten und prueft, ob es Veraenderungen zum bestehenden Stand gibt. Hier habe ich zu meinem schweizer Allzweckmesser PERL gegriffen. Der Code ist wahrscheinlich nicht schoen, aber er tut genau das, was er soll. Was macht der Code? Zunaechst ermittelt er, welche Eintraege neu sind oder sich geaendert haben. Im Anschluss wird das Zonefile bearbeitet (nun ja, gelesen und modifiziert ausgegeben), so dass es mit den neuen Eintraege uebereinstimmt.
Ein wichtiges Detail: Der Timestamp. Weit verbreitet ist ja yyyymmddrr. Da ich mir vorstellen kann, an einem Tag auf mehr als 99 Aenderungen zu kommen (man weiss ja nie...), habe ich mich fuer yymmddHHMM (mit HHMM als Stunde und Minute) entschieden. Wenn das Skript nicht mehr als einmal pro Minute eine Aenderung durchfuehrt, ist das sicher. Wenn es hingegen zweimal mit dem gleichen Timestamp liefe waere die letzte Aenderung bis zur naechsten Aenderung in einer anderen Minuten potentiell unsichtbar.
use strict;
my @infiles = </usr/local/www/vhosts/de.ad001.dynamic.update/data/*>;
my $OLDPATH = "/usr/local/www/vhosts/de.ad001.dynamic.update/data.current";
my $filename;
my @changes;
my %hosts;
foreach $filename (@infiles) {
my $hostname = $filename;
$hostname =~ s!.*/(.*)$!$1!;
my $new_ip = `cat $filename`;
my $old_ip = `cat $OLDPATH/$hostname 2>/dev/null`;
if ($new_ip ne $old_ip) {
push @changes, $hostname;
$hosts{$hostname} = $new_ip;
$hosts{$hostname . ".done"} = "0";
`echo -n $new_ip > $OLDPATH/$hostname`
}
}
open(f, '<', "/usr/local/etc/nsd/forward/dynamic.ad001.de");
my @zonefile = <f>;
close(f);
for (@zonefile) {
my $ln = $_;
chomp($ln);
if ($ln =~ m/^[^0-9]*([0-9]*).*Serial.*$/) {
my $oldser = $1;
my $newser = `date +%y%m%d%H%M`;
chomp($newser);
while (!($newser > $oldser)) {
$newser++;
}
$ln = "\t\t\t$newser\t; Serial";
}
for (@changes) {
my $hostname = $_;
if ($ln =~ m/$hostname.*/) {
my $ip = $hosts{$hostname};
$hosts{$hostname . ".done"} = "1";
$ln =~ s/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/$ip/g;
last;
}
}
print $ln . "\n";
}
for (@changes) {
my $host = $_;
if ($hosts{$host . ".done"} ne "1") {
print "$host\t\tIN\tA\t" . $hosts{$host} . "\n";
print "$host\t\tIN\tMX\t10\t" . $hosts{$host} . ".\n";
}
}
Wie man im Code sieht, wird mit der uebergebenen IP-Adress sowohl ein A- als auch ein MX-Record gefuettert. Warum der MX-Record? Weil ich kann :)
...und schliesslich die update, die von einem Cronjob minuetlich aufgerufen wird und den Nameserver aktuell haelt:
#! /bin/sh export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/home/ad001/bin TEMPFILE=/tmp/zone.$$$ cd /usr/local/www/vhosts/de.ad001.dyn.update/bin/ /usr/local/bin/perl compare.pl > $TEMPFILE mv $TEMPFILE /usr/local/etc/nsd/forward/dyn.ad001.de /usr/local/sbin/nsdc rebuild >/dev/null 2>/dev/null /usr/local/sbin/nsdc reload >/dev/null 2>/dev/nullHier gaebe es natuerlich noch deutliches Optimierungspotential -- der Nameserver wird momentan bei jedem Aufruf aktualisiert, egal ob es noetig ist oder nicht.
Aktualisierungsclient Mac OS / FreeBSD
Da ein einfacher HTTP-Aufruf ausreicht, reicht es voellig, curl zu benutzen. Das ist klein und auf zimlich vielen Systemen vorhanden. Erfreulicherweise sogar auch auf einem Mac OS X 10.4 (Tiger). Hier also meine Aktualisierungszeile:
#! /bin/sh /usr/local/bin/curl "http://update.dynamic.ad001.de/update.php?host=`/bin/hostname`&trace=1¬e=heartbeat" >/dev/null 2>/dev/null || echo -nDas ist das Skript in der FreeBSD-Geschmacksrichtung. Fuer Mac OS sieht es wie folgt aus:
#! /bin/sh /usr/bin/curl "http://update.dynamic.ad001.de/update.php?host=`/bin/hostname`&trace=1¬e=heartbeat" >/dev/null 2>/dev/null || echo -n...und unterscheidet sich folglich nur im Pfad fuer das Hilfsprograemmchen
curl.Als naechstes muss noch dafuer gesorgt werden, dass die Aktualisierungen irgendwie regelmaessig durchgefuehrt werden. Dazu gibt es den allseits beliebten
cron-Daemon. Also kurz einen Zeile der Form
*/5 * * * * /Users/ad001/heartbeatmit absolutem Pfad zum Skript in die
crontab eingetragen -- voila. Und schon meldet er sich alle fuenf Minuten zur Aktualisierung :)
Aktualisierungsclient Windows (getestet mit Windows 2000)
Unter Windows... wird das etwas kitzeliger. Grund ist das Fehlen der vielen kleinen Helferlein, die man aus der Unixwelt einfach zu haben gewoehnt ist. Wie skriptet man unter Windows? Klar, ich koennte mit mit Batchfiles im cmd-Stil umherplagen (...ich schaue dezent zu http://hypftier.de...
und wieder weg...). Dann kommt noch die Tatsache dazu, dass ich mit einem Windows 2000 arbeite. Damit fliegt Powershell auch gleich wieder aus der Liste der Optionen raus. Das von Windows mitgebrachte ftp ist im Gegensatz zu dem von den meisten Unix-Derivaten mitgebrachten nicht zum Ausfuehren von HTTP-Requests in der Lage. Und ansonsten bleibt nur die Frage, welches kleine Zusatzprogramm von allen unertraeglichen das am wenigsten unertraegliche ist.
Nein, das kann es doch nicht sein. Das ist es auch nicht. Denn es gab auch vor Powershell schon recht brauchbare Moeglichkeiten, Windows zu skripten. Das ist heute nur ein wenig in Vergessenheit geraten. Es geht um VBScript. Zugegeben, seit der ILOVEYOU-Sache (http://de.wikipedia.org/wiki/ILOVEYOU) hat VBScript auch einen etwas schweren Stand.
Trotzdem, genau das, was ich brauche. Ein kurzes Googeln fuehrte mich zu http://stackoverflow.com/questions/204759/http-get-in-vbs, wo ich den Grundstock fuer das folgende Aktualisierungsskript serviert bekomme:
Dim o
Set o = CreateObject("MSXML2.XMLHTTP")
o.open "GET", "http://update.dynamic.ad001.de/update.php?host=wpcdn&trace=1¬e=heartbeat", False
o.send
Das nun in eine kleine Datei mit der Endung .vbs und schon kann ich es wie ein ganz normales Programm nutzen. Damit es nun noch zur Ausfuehrung gelangt (der Teil ist unter -- gerade den fruehen Windows-Versionen -- etwas kitzeliger) nehme man sich die Geplanten Tasks, die meist zimlich unbeobachtet in der Systemsteuerung herumliegen. Damit kann man das Skript wahlweise an den Systemstart, Logon oder einen feste Zeit pinnen. Mit neueren Windows-Varianten vielleicht auch regelmaessig ausfuehren (Windows 2000 kann das noch nicht).
Sicherheitsbedenken (und Faulheit)
Natuerlich habe ich keinen Gedanken an die Sicherheit verschwendet. Naja, okee, vielleicht doch den ein oder anderen, der aber in dieser prototypischen Umsetzung nicht zum tragen gekommen ist.
Die Eingabe wurde streng validiert. Wenn ich mich nicht geirrt habe, sollte das Zonefile vom Web-Interface aus nicht zu zerschiessen sein.
Es findet keine Authentifikation statt. Das ist momentan wirklich so. Jeder koennte die update.php aufrufen und die wildesten Eintraege hinterlassen. Und ueberschreiben. Und die Traces koennte auch jeder auslesen, der will, denn auch die liegen einfach so auf dem Webserver umher. Wenn man nur den Hostnamen kennt... -- um es kurz zu machen: Ja, ich weiss, dass das so ist. Ja, das koennte man alles noch aendern, wenn man das ganze wirklich professionell oder gar produktiv nutzen wollen wuerde. Hier war es aber mehr das Proof-of-concept, dass im Mittelpunkt stand, also waren diese Aspekte sekundaer.
Die Authentifikation gelaenge wohl am einfachsten, indem man den Webserver hierzu hinzuzieht: HTTP-Auth, das ganze noch ein SSL gepackt. Die Frage ist dann natuerlich, wie die Aktualisierungsskripte damit umgehen...
Um es nochmal klar zu sagen: Das hier ist ein Proof-Of-Concept. Kein Code fuer produktive Systeme!.
Dank
Danken moechte ich dem lando (http://lando.cc), der sich selten zu schade ist, meine wuseligen Ideen mit Hardware und Infrastruktur zu untermauern und Realitaet werden zu lassen.