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