Fällt ein Internet-Service-Provider aus, ist tote Hose auf den Computern im Haushalt. Es sei denn, man schaltet auf die Konkurrenz um. Ein Perlskript modifiziert Konfigurationsdateien, erweckt neue Router zum Leben und nordet beteiligte Rechner auf die neuen Routen ein.
In Kalifornien gibt es derzeit zwei große Konkurrenten für Internetanschlüsse in Privathaushalten: Die Telefongesellschaft AT&T mit ihrem DSL und die Kabelfernsehfirma Comcast, die durch das Fernsehkabel ebenfalls Breitband-Internet anbietet. Ein DSL- oder Kabelmodem zapft die Daten aus der Leitung, ein davorgeschalteter Router verbindet ruckzuck sämtliche Rechner des Haushalts mit dem Internet.
Beide Verfahren haben Vor- und Nachteile. So leidet die Kabelverbindung, wenn zuviele Kinder in der Nachbarschaft ``World of Warcraft'' spielen, da die Bandbreite mit der Zahl der Nutzer abnimmt. Der legendäre Werbespot ``Web Hog!'' [2] des DSL-Anbieters Southern Bell illustriert auf humorvolle Weise, wie sich friedfertige Nachbarn einer Kleinstadt über Nacht in Berserker verwandeln, weil sie sich angeblich gegenseitig das Internet lahmlegen. Das DSL der Telefongesellschaft ist zumindest in der langsamen Billigversion preisgünstiger und jeder bekommt die gleiche Bandbreite zugeteilt. Allerdings hängt die Verfügbarkeit und der tatsächliche Durchsatz von der Entfernung zum nächsten Knotenpunkt ab.
|
| Abbildung 1: Haushaltsnetzwerk der Perlmeister-Studios |
Keines der beiden Verfahren ist jedoch hundertprozentig zuverlässig, hie und da treten Probleme auf. Hier ein Stromausfall, da ein schlafender Sysadmin, dort bohrt ein Bauarbeiter ein Kabel an, und schon fällt das Internet aus. Unschön, wenn man es gerade dringend braucht. Nachdem aber sowohl DSL als auch Kabel-Internet im Sonderangebot nur etwa 20 Dollar im Monat kosten, habe ich mir einfach beide bestellt. Und wenn ein Anbieter nicht funktioniert, schalte ich kurzerhand auf den anderen um.
Das Skript isp-switch nimmt als Parameter entweder ``cable'' oder ``dsl''
auf der Kommandozeile entgegen und führt die zum Umschalten notwendigen
Einzelschritte durch.
Im Skript isp-watch bestimmt Zeile 21 die möglichen Werte dieser
Parameter. Bei unbekannten Providernamen bricht das Skript mit
pod2usage() ab, das eine Fehlermeldung und
die verkürzte Bedienungsanleitung aus dem hinten angehängten POD-Bereich
ausgibt. Die 'weiche' Referenz $switch_to->() in Zeile 31 ruft
dann dementsprechend die weiter unten definierten Funktionen cable()
oder dsl() auf. Damit der Perlinterpreter im strict-Modus
bei diesem schmutzigen Trick nicht ausflippt, muss das Skript
die Strenge mit no strict 'prefs' etwas abmildern. Das umschließende
eval-Konstrukt fängt etwaige Fehler ab und lässt das Skript durchlaufen,
auch wenn nicht alle Schritte erfolgreich ablaufen.
Das Skript nutzt Log::Log4perl für Status- und Fehlermeldungen, das in Zeile 48 verwendete Makro ALWAYS kam erst mit Version 1.13 hinzu, deshalb fordert Zeile 9 mindestens diese Version an.
Mein Linux-Rechner hat eine statische 192.er-IP im lokalen Netzwerk,
nutzt also
kein DHCP. Unter Fedora konfiguriert er in der Datei
/etc/sysconfig/network sein ``Default-Gateway'', also den Router, der
Anfragen an alle Rechner des Internets weiterleitet. Abbildung 2
zeigt die Datei im Originalzustand.
Das Gateway ist ein Gerät der Firma Buffalo vom Typ G54, das einmal 30 Dollar gekostet hat und seit Jahren den internen Netzwerkverkehr der Perlmeister-Studios zuverlässig mit dem DSL-Modem und damit dem Internet verbindet.
Kommt wegen eines DSL-Ausfalls jedoch statt des DSL-Anschlusses die Kabelverbindung zum Einsatz, ist ein anderer Kabel-Router das ``Gateway''. Er stammt ebenfalls von der Firma Buffalo stammt und arbeitet unter der IP 192.168.10.98. Wie verändert man nun Konfigurationsdateien und speichert gleichzeitig ihren Originalzustand, so dass sie sich jederzeit wieder in den Ursprungszustand zurückversetzen lassen?
Das CPAN-Modul Config::Patch fügt Patches in Systemkonfigurationsdateien ein und merkt sich den Originaltext als Base-64-kodierte Kommentare. Möchte der User den Patch wieder entfernen, liest es die Base-64-Kodierung, extrahiert den Originaltext und restauriert ihn.
Abbildung 3 zeigt die Datei network, nachdem der
Patch eingespielt wurde.
Die ab Zeile 60 definierte Funktion gateway_patch nimmt als Parameter
die IP-Addresse des Gateways entgegen. Der Konstruktor des
Config::Patch-Objektes erhält den Namen der zu patchenden Datei und
einen Schlüssel. Diese Zeichenkette ist typischerweise der Projekt-
oder Applikationsname und dient dazu, dass das Modul
Patches ein und derselben Datei durch verschiedene Schlüssel
auseinanderhalten und getrennt bearbeiten kann.
Bereits gepatchte Bereiche markiert das Modul als verbotene Zonen und
verhindert so wirksam überlappende Patches.
|
| Abbildung 2: Ursprüngliche Version von /etc/sysconfig/network |
|
| Abbildung 3: Mit Config::Patch modifizierte Konfigurationsdatei |
001 #!/usr/bin/perl -w
002 use strict;
003 use Getopt::Std;
004 use Pod::Usage;
005 use Sysadm::Install qw(:all);
006 use Config::Patch;
007 use Buffalo::G54;
008 use X10::Home;
009 use Log::Log4perl 1.13 qw(:easy);
010
011 my @isps = qw(cable dsl);
012 my($switch_to) = @ARGV;
013
014 Log::Log4perl->easy_init($INFO);
015
016 if(! defined $switch_to) {
017 pod2usage(
018 "Specify what ISP to switch to");
019 }
020
021 if(! grep { $_ eq $switch_to } @isps) {
022 pod2usage("Unknown isp, use ",
023 join(", ", @isps));
024 }
025
026 my $x10 = X10::Home->new();
027
028 my $P = password_read("Router password: ");
029
030 no strict 'refs';
031 eval { $switch_to->(); };
032 network_restart();
033
034 ###########################################
035 sub dsl {
036 ###########################################
037 gateway_patch("192.168.10.1");
038 $x10->send("bridge", "off");
039 dhcp("on");
040 }
041
042 ###########################################
043 sub cable {
044 ###########################################
045 gateway_patch("192.168.10.98");
046 $x10->send("bridge", "on");
047 dhcp("off");
048 ALWAYS "Waiting for bridge to start up";
049 sleep 20;
050 }
051
052 ###########################################
053 sub network_restart {
054 ###########################################
055 tap "sudo", "/etc/rc.d/init.d/network",
056 "restart";
057 }
058
059 ###########################################
060 sub gateway_patch {
061 ###########################################
062 my($ip) = @_;
063
064 my $patcher = Config::Patch->new(
065 file => "/etc/sysconfig/network",
066 key => "isp-switch",
067 );
068
069 if($patcher->patched()) {
070 # patched already? Remove old patch
071 $patcher->remove();
072 }
073
074 $patcher->replace(qr(^GATEWAY=.*)m,
075 "GATEWAY=$ip");
076 }
077
078 ###########################################
079 sub dhcp {
080 ###########################################
081 my($onoff) = @_;
082
083 DEBUG "Setting dhcp to $onoff";
084
085 my $b = Buffalo::G54->new();
086 DEBUG "Connecting";
087 $b->connect(password => $P);
088
089 if(defined $onoff) {
090 INFO "Setting DHCP to $onoff";
091 $b->dhcp($onoff);
092 }
093
094 INFO "DHCP is now ", $b->dhcp() ?
095 "on" : "off";
096 }
097
098 __END__
099
100 =head1 NAME
101
102 isp-switch - Cable or DSL?
103
104 =head1 SYNOPSIS
105
106 isp-switch [dsl|cable]
107
108 =head1 DESCRIPTION
109
110 isp-switch switches between Comcast cable
111 and Pacbell DSL.
112
113 =head1 AUTHOR
114
115 2007, Mike Schilli <cpan@perlmeister.com>
Config::Patch kann Patches am Anfang einer Datei oder zwischen
zwei Zeilen einfügen, oder sie am Ende anhängen. Die Methode
replace ersetzt gar eine mittels eines regulären Ausdrucks spezifizierte
Zeile oder einen Zeilenbereich mit der angegebenen Ersatzzeile. Der Aufruf
$patcher->replace(
qr(^GATEWAY=.*)m,
"GATEWAY=$ip");
sucht nach einer Zeile, die mit GATEWAY= beginnt.
Da der Regex über mehrere Zeilen greift, ist der Modifizierer /m
relevant, damit das Metazeichen ^
tatsächlich sämtliche Zeilenanfänge erkennt und nicht nur
den der ersten Zeile.
Die gefundene Gateway-Zeile
kodiert Config::Patch zu einem
Merker, kommentiert diesen anschließend aus und ersetzt die Zeile
mit dem Gateway mit der neuen IP-Adresse.
Um die Datei wieder in den Ursprungszustand
zurück zu versetzen, genügt der Aufruf $patcher->remove(), denn
der Patcher benötigt nur den Dateinamen und den Key, die bereits im
Konstruktor des Objekts vorliegen. Die Methode patched() stellt fest,
ob eine Datei bereits mit dem angegebenen Schlüssel gepatcht wurde und
liefert in diesem Fall einen wahren Wert zurück.
Der Kabel-Router ist im DSL-Betrieb abgeschaltet. Mittels der in [3]
besprochenen X10-Schnittstelle schaltet ihn das CPAN-Modul X10::Home
über das Stromnetz ein. Abbildung 4 zeigt den Eintrag in /etc/x10.conf,
der die Ansteuerung des Kabelrouter über den Namen cable_router erlaubt.
Beide Router sind baugleich und agieren
auch als DHCP-Server. Hängt sich ein Computer ins Hausnetz, bekommt
er so eine dynamische IP zugewiesen und weiß, an welchen DNS-Server
er sich wenden muss, um Hostnamen aufzulösen. Zwei DHCP-Server, die
zeitgleich den gleichen Service anbieten, lösen allerdings Chaos
aus und schließlich sollen die Rechner im Kabelbetrieb nicht mehr den
alten, sondern den neuen DHCP-Server mit dessen Kabel-Gateway nutzen.
|
|
Abbildung 4: Der Eintrag C |
X10::Home, den Kabelrouter mit Namen anzusprechen.
Deswegen kontaktiert das CPAN-Modul Buffalo::G54 die
Konfigurationsoberfläche des Routers und fummelt mittels Screen-Scraping
über das Modul WWW::Mechanize intern
solange daran herum, bis der DHCP-Server des DSL-Routers abgestellt ist.
Allerdings braucht es dazu das Administrationspasswort des Routers
welches das Skript isp-switch mit der Funktion
password_read() aus dem CPAN-Modul Sysadm::Install
beim User abfragt.
Im umgekehrten Fall, wenn durch das Kommando dsl wieder zurück ans
DSL geht, wird der DHCP-Server des DSL-Routers wieder aktiviert und dem
Kabelrouter per X10 rigoros der Strom abgedreht.
Da es eine zeitlang dauert, bis der neue Router vollständig hochgefahren
ist, wartet isp-switch mit sleep() zwanzig Sekunden, bevor es
weitere Aktionen einleitet.
Rechner mit dynamischen Adressen holen sich nach einem Reboot oder einem Restart des Netzwerksystems eine neue IP-Addresse vom neuen DHCP-Server. Die Linux-Box mit der statischen IP muss ebenfalls ihr Netzwerk-Initialisierungsskript neu starten, damit Requests an das neue und nicht mehr an das alte Gateway geschickt werden.
|
| Abbildung 5: Ein Eintrag in /etc/sudoers erlaubt dem User mschilli, das Netzwerk rauf- und wieder runter zu fahren. |
Hierzu lässt isp-switch das Linux-Startup-Skript
/etc/rc.d/init.d/network mit dem Parameter ``restart'' laufen. Hierzu sind
Root-Rechte erforderlich, aber ein Eintrag mit NOPASSWD in /etc/sudoers
(Abbildung 5)
für das Skript erlaubt es auch dem nichtpriviligierten User mschilli,
das Netzwerk neu zu initialisieren. Die Funktion tap aus dem
unerschöpflichen Fundus des CPAN-Moduls Sysadm::Install führt das
Kommando aus und schluckt dessen Ausgabe.
Mit diesen drei Maßnahmen schaltet isp-switch zwischen den beiden
Internetanbietern hin und her. Der Internetanschluss wird zwar so
auf einmal doppelt so teuer, ist aber auch doppelt so zuverlässig.
Als Alternative könnte man einen dritten Router einsetzen, der als
Gateway arbeitet und Requests wahlweise an den Kabel- oder den DSL-Router
weiterleitet. Er könnte auf einem Linux-PC oder einem ebenfalls
unter FreeWRT laufenden Linksys-Router des Typs WRT54GL.
Wer möchte, kann noch einen Nagios-Plugin schreiben, der regelmäßig die Internetverbindung prüft und bei Problemen automatisch auf die Konkurrenz umschaltet. Hoffentlich ohne dass der Enduser das überhaupt mitbekommt.
![]() |
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |