Routing unter Ubuntu

Einen Text mit viel „Bla-Bla“ hatte ich heute schon, jetzt gibt es Technisches!

Will man unter Ubuntu einen „echten“ Router betreiben, also dynamisch auf die Netzwerkumgebung reagieren, stehen zwei Routing-Suites zur Verfügung: BIRD und Quagga. Beide unterstützen die wichtigsten Routing-Protokolle: BGP, RIP, OSPF

Wichtiger Hinweis vorweg: in Ubuntu 10.04 wird BIRD noch in Version 1.1.5 ausgeliefert. Die ist mal gut zwei Jahre alt und Unterstützt noch nicht alle Funktionen. Entweder akzeptiert man diese Schwächen oder nutzt das PPA des CZ.NIC Labs.

Die Quagga-Suite ist schon seit einiger Zeit am Markt. Sie bietet ein Config-Interface das einem CISCO Router gleicht. Man kann sowohl Config Files hinterlegen als auch „on the fly“ Config Befehle geben und die entsprechende Config dann raus schreiben (ganz wie bei CISCO Routern). Im Hintergrund werden bei Quagga für jedes Routing-Prokoll ein eigener Dienst gestartet und jeder dieser Dienste macht einen TELNET Port auf. Diesen kann man auf den Localhost beschränken oder so konfigurieren, dass er jede Anfrage ablehnt (kein Password angeben).

Bird befindet sich aktuell noch in der (Weiter-)Entwicklung. Zudem wirkt es auf mich schlanker. Es wird nur ein Dienst gestartet, die Config befindet sich in einer Datei und wirkt übersichtlicher. Neben diesen „Geschmacksfragen“ soll BIRD leistungsschonender sein.[1. Quelle: Vergleich BIRD/Quagga] Laut Wiki kommt er vor allem deswegen in den großen „Verteilerknoten“ zum Einsatz. [2. Quelle: Wikipedia] Dafür spricht auf jeden Fall, dass bei DD-WRT Firmwares für wenig Speicher der BIRD zum Einsatz kommt und bei viel Speicher die Quagga-Suite.

Ich habe beide Implementierungen gezwungenermaßen durch meine Routern im Einsatz. Beim Ubuntu-Router hatte ich die Wahl und mich nach einem kleinen Quagga Test für BIRD entschieden.

Wichtig: Die meisten Routing-Protokolle nutzten MultiCast-Addressen für ihre Kommunikation. Damit das funktioniert muss man eine entsprechende Route hinterlegen.

route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0

Beispiel – Konfiguration BIRD

Einfach über apt-get oder aptitude bird installieren und schon kann es losgehen.

aptitude install bird

Danach kann man die /etc/bird.conf bearbeiten.

log syslog all;
router id 10.1.0.1;

protocol kernel {
        export all;
        learn;
}

protocol device {
        scan time 60;
}

protocol ospf {
        debug { states, routes, interfaces };
        area 0 {
                interface "eth0" 10.1.0.0/16 {
                        cost 10;
                        stub;
                        check link;
                };
                interface "eth1" 10.2.0.0/16 {
                        priority 10;
                        cost 100;
                        type broadcast;
                        hello 30; retransmit 5; wait 10; dead 120;
                        authentication none;
                        check link;
                };
                interface "eth2" 10.3.0.0/16 {
                        priority 10;
                        cost 10;
                        type broadcast;
                        hello 30; retransmit 5; wait 10; dead 120;
                        authentication none;
                        check link;
                };
        };
}

Die Config dürfte selbsterklärend sein. Ein paar Hinweise zum Umgang mit BIRD:

  • Das Kernel-Protokoll wird gebraucht. Ohne die „export all;“ Regel werden keine Routen an das OS bekanntgegeben.
  • Hat man Default-Routen oder pflegt händisch welche über den „route“-Befehl nach, sollte die über das „learn“-Statement von BIRD ermittelt werden.
  • Alle lokalen Netze, die den Nachbarn mitgeteilt werden sollen, benötigen ein interface-Statement. Das stub-Statement markiert Interfaces die nur „announced“ werden sollen aber sonst keine Funktion haben.
  • Wenn man einen PPPoE-DSL-Uplink als Default-Route verwendet, sollte man das PPPoE Interface nicht als Area-Interface hinterlegen.
  • „check link“ funktioniert nur, wenn man neuerere BIRD-Versionen benutzt. BIRD 1.1.5 meckert einen Syntax-Fehler an.

 Beispiel – Konfiguration Quagga

Installation:

aptitude install quagga

Quagga ist anfangs etwas komplizierter. Anfangs muss man erst mal die benötigten Dienste unter /etc/quagga/daemons freischalten. Will man, dass diese Dienste per TelNet auch Remote konfigurierbar sind muss man noch in der /etc/quagga/debian.conf Anpassungen vornehmen.

Auf jeden Fall benötigt man den zebra-Dienst. Dieser ist das „protocol kernel“ Pendant. Zusätzlich muss man noch den Dienst für das gewünschte Protokoll aktivieren. Für den einfachen Einstieg findet man Beispiel-Konfigurationen unter /usr/share/doc/quagga/examples.

zebra.conf

hostname deadend-router

interface br0
  link-detect
interface ath1
   link-detect

in der zerba.conf werden die zu nutzenden interfaces angegeben.

ospf.conf

interface br0
  ip ospf cost 10
  ip ospf dead-interval 120
  ip ospf hello-interval 30
  ip ospf retransmit-interval 5
  ip ospf network broadcast
interface eth0
  ip ospf cost 10
  ip ospf dead-interval 120
  ip ospf hello-interval 30
  ip ospf retransmit-interval 5
  ip ospf network broadcast

router ospf
  ospf router-id 10.3.0.10
  network 10.3.0.0/16 area 0
  network 10.4.0.0/16 area 0
    redistribute kernel
    redistribute connected

In der ospf.conf wird diesen Interfaces erst Eigenschaften zugewiesen und anschließend das Protokoll zugeschaltet. Wichtig ist die Syntax. Das Einrücken dient nur zur Optik und hat kein Einfluss. Die Reihenfolge der Befehle hingegen hat Einfluss. Gewisse Befehle „öffnen“ eine SubConfig-Ebene. Ein „ip ospf cost“ ist nur gültig, wenn vorher eine interface-Direktive stand. Was bei den „interface“-Direktiven noch übersichtlich erscheint, kann bei den area-direktiven unübersichtlich werden, da diese ineinander geschachtelt sein können. Angenehm sind hingegen die redistribute -Direktiven. So muss man nur die Interfaces auflisten, auf den auch wirklich ein weiterer Router lauscht.

DD-WRT im Einsatz – ein Fazit

Vor gut einem Monat durfte ich meine lokale Netzwerkstruktur ein wenig wenig umbauen. Dabei habe ich meinen alten Netgear Router rausgeschmissen. Ich wollte ein bisschen was „professionelleres“. Nach kurzer Suche bin ich auf Geräte-Serien gestoßen, die nativ mit DD-WRT unterstützen (ein kommerzieller Ableger der OpenWRT-Firmware). Hängen geblieben bin ich bei zwei Buffalo-Geräten:

  • WHR-HP-G300N (400 MHz/32MB Ram)
  • WZR-HP-AG300H (680 MHz/128MB Ram)

Beide Geräte werden ab Werk mit DD-WRT ausgeliefert. Die Hardware-Ausstattung ist für meine Verhältnisse üppig und reicht für den Home und Medium-Office Bereich. Auch wenn beide „eigentlich“ DD-WRTv24Sp2 einsetzten unterscheiden sich die tatsächlich eingesetzten Firmwares doch stark. Sie unterscheiden sich vor allem im Release-Date und den verfügbaren Features. Der kleinere G300N vermisst zum Beispiel CIFS Support, OpenVPN, beschreibbaren Speicher (JFFS2) usw.  Dazu kommen Unterschiede in den ausgelieferten Software-Versionen. Beim G300N kommt BIRD als Routing-Daemon zum Einsatz, beim AG300H wird Quagga angeboten. Man könnte beide Geräte auch auf die offizielle DD-WRT Version zurück flashen, diese hingt der „Buffalo“ Version jedoch im Release hinterher (keine Ahnung was das für Auswirkungen hat). Leider bietet Buffalo die Sourcen der DD-WRT Software nicht zum download (oder ich hab den Link noch nicht gefunden) so muss man mit einigen „Eigenheiten“ leben. Dazu gleich mehr. Neben einem Farbbranding und ein paar Funktionseinschränkungen entspricht die Buffalo Version von DD-WRT der frei verfügbaren DD-WRT-Version.

Als erstes fällt beim Einsatz von DD-WRT auf, dass man nicht für einen Idioten gehalten wird. Man kann den Assistenten wegklicken! Nach der Änderung des Passwords  darf man auf Wunsch gleich loslegen ohne sich durch einen Nerf-Dialog zu klicken. Standardmäßig ist Telnet und HTTP als „Fernwartung“ aktiviert. Das kann man aber einfach umkonfigurieren. SSH und HTTPS werden angeboten. Beide Varianten haben kleine Haken. HTTPS verwendet „fest“ eingebrannte selbst signierte Zertifikate (und ich würde wetten, dass die priv Keys nur pro Version-Build unterschiedlich sind). Will man dieses austauschen muss man gleich eine eigene Firmware einspielen da der /etc Bereich read-only gemounted ist. SSH bietet hingegen die Möglichkeit die Zertifikate zu ändern. Standardmäßig logt man sich dort hingegen als root ein. Da ist Vorsicht bei den Passwörtern geboten!

Neben diesen beiden „Fehlern“ fällt nur noch der Umstand auf, dass die Konfigurationsoberfläche stark auf den Betrieb eines Gateway-Routers ausgelegt ist. Hat man sich damit einmal abgefunden und verstanden, dass das br0-Interface (intern werden Birdges verschaltet) immer „das Interne“-Interface ist, entfaltet sich eine Konfiguationsvielfalt, die man sonst nur bei hochpreisigen Konkurrenten findet. Ein Beispiel: um auf den WAN – Port (eth1) zwei VLANs zu konfigurieren braucht es 3 Klicks. Will man WAN-VLAN1 ins br0-Netz bridgen ist das ein weiterer Klick. Will man nun das WLAN aus dem br0 nehmen und auf WAN-VLAN2 bridgen geht das mit unter 5 Klicks. Ohne auch nur ein mal das SSH-Terminal zu nutzen. Auf diesem Niveau geht es weiter. Statische Routen, sehr detaillierte Interface-Konfigurationen, fast alle Services und diverse Hilfseinstellungen lassen sich über das WebInterface bequem und umfangreich konfigurieren.

Ich hab den SSH-Zugang dennoch häufig genutzt. Das hat drei Gründe:

  • Die Zeit die man zum Konfigurieren braucht: alle Einstellungen erfordern das Schreiben in den NVRam, das anschließende auslesen dieser Informationen und das durchstartet des betroffenen Dienstes. Da DD-WRT aber nicht „weiß“ welcher Dienst betroffen ist, wird alles durchgestartet. Beispiel: ändert man an der PPPoE Einstellung etwas, werden mal ebend alle Dienste durchgestartet auch der Routing-Dienst (BIRD/ZEBRA). Der Router ist erst mal eine Weile beschäftigt. Wird ein Routing-Dienst eingesetzt und befindet man sich außerhalb der direkten Router-Netze muss man erst mal warten, bis  die Routing-Tabellen wieder stehen. Wenn man weiß was mach macht, kann man auf der Konsole in aller Ruhe an der Config-File rum fuschen. nur den betroffenen Dienst durchstartet und wenn alles wie gewünscht funktioniert, alles in den NVRam speichern.
  • Einige exotische Einstellungen kann man „nur“ über die Konsole machen. Das WebInterface bietet zwar die Möglichkeit 3 Skriptes zu hinterlegen (StartUp, FirewallUp,ShutDown) und direkt Konsolen-Befehle auszuführen, mir fehlt hier aber das Feedback. Ich hab die Skriptes immer erst im Terminal ausprobiert und dann als Skript hinterlegt.
  • Einige Eingabemasken führen quasi eins zu eins auf ein Config-File für einen Dienst. Startet man dann durch und hat einen Syntax-Fehler bekommt man keine Rückmeldung. Auch hier gilt: erst auf der Konsole ausprobieren und dann hinterlegen.

Bei den ganzen Konfigurations-Möglichkeiten sollte noch erwähnt werden, dass alle Interfaces frei konfigurierbar sind. Selbst wenn das WebInterface dann ein wenig zickt. Es werden alle Ethernet und WLan Ports einzeln an das OS gemeldet. Im Falle des AG300H (DualBand) muss man den 2.4Ghz Adapter getrennt vom 5GHz konfigurieren (auch im WebInterface). Einzig die internen LAN-Ports werden durch eine nicht konfigurierbaren Hardware-Switch verschaltet. So muss man ein wenig aufpassen was man am eth0-Port macht.

Was bleibt abschließend zu sagen? Ich bin vom Funktionsumfang der Firmware sehr positiv überrascht. Es wird einem fast alles geboten, was man so braucht. Für alles andere gibt es das SSH-Terminal.  Der WNR3500 war wohl mein letzter nicht WRT Router gewesen sein.

ics dhcp hostname direktive wird ignoriert.

Ich hatte bei mir das Problem, dass bei aktiven DNS-Update durch den DHCP Server „illegale“ DNS-Namen in meine dynamische Zone eingetragen wurden. Irgendein Witzbold muss sich bei Google wohl gedacht haben, dass „android_{MAC}“ ein wunderbar einfacher Hostname ist und den ein User nie ändern will.

Dummerweise wird dieser von keinem DNS Server standardmäßig akzeptiert. Man muss zu mindestens das „Sperrverhalten“ abschalten. Ich entschied mich dafür im DHCP Server ein Hostname zu definieren. Der wurde aber geflissentlich ignoriert. Das Snippet das standardmäßig als „on-commit“ Hook hinterlegt ist greift auf „option host-name“ zu anstatt auf „config-option host-name“.

Ich hab es wie folgt angepasst und in meine dhcpd.conf eingefügt

on commit {
  option server.ddns-hostname =
        pick (config-option host-name,option fqdn.hostname, option host-name);
  option server.ddns-domainname = config-option domain-name;
  option server.ddns-rev-domainname = "in-addr.arpa.";
}

OutOfMemory – Exception die Zweite

Während des Feintunings an der GalDroid-App bin ich auf einen interessanten Unterschied zwischen der GridView und der Gallery Komponente gestoßen.

Erstere versorgt die Adapter-Implementierung mit einer Referenz auf vorher genutzte Elemente. Die Gallery Komponente macht dies nicht. Das ist dann blöd, wenn man sich darauf verlässt, dass man über diese Referenz alte Bitmaps aufräumt oder gar Hintergrund-Threads abbrechen kann/muss.

Wer also einen Adapter an die Gallery bindet, muss sich selbst um die „alten“ Referenzen  kümmern. In einem ersten Schritt hab ich dafür gesorgt, dass zu einem Zeitpunkt nur ein Bitmap dekodiert wird (syncronized-Block).  Das gibt dem GC genug Zeit, Platz zu schaffen wen möglich. Daneben nutze ich eine LinkedList/Queue von WeakReferences auf die AsyncTask für das Laden und Dekodieren der Bilder. Überschreitet die Länge dieser Queue einen Schwellwert, werden die ersten Threads abgebrochen.

Dadurch wird keine Bilder mehr unnötig geladen und es spart gleichzeitig Ram.

Sourcecode:

Android – BitmapFactory und OutOfMemory-Exception

OutOfMemory-Exceptions sind immer eine unangenehme Sache. Trehten sie einmal auf, kann man nicht mehr reagieren. Das Programm wird einfach vom Stack geworfen und dem User eine unschöne Fehlermeldung vorgesetzt. Für den Entwickler ist die Fehlersuche langwierig, da das vermeintliche „Opfer“ selten der Verursacher ist. Der produzierte StackTrace ist schlicht unbrauchbar.

Die OOM-Exceptions trehten gehäuft im zusammenhang mit der BitmapFactory auf. Der Grund ist einfach, Bilder brauchen viel Platz…

Wenn man im Web nach Beispielen  für eine Bilder-Galerie sucht, bekommt man eigentlich immer das gleiche Beispiel. Ein paar kleine Bilder, lokale Resourcen, werden über eine Klasse die von BaseAdapter abgeleitet an ein über-gelagertes UI-Element gereicht. um dort zur Anzeige gebracht zu werden.

Bringt man jedoch Bilder auf einem Tablet zur Anzeige, werden schon die Bild-Dimensionen größer, nicht nur die Auflösungen. Auf einem 10 Zoll Tablet mit 1280×800 und mehr Bildpunkten braucht ein Bild in VollBild-Auflösung oder mehr schon mehrere MegaByte Ram, alleine zum Vorhalten der Bildinformationen. Dank der komprimierung brauchen Bilder als File wesentlich weniger Speicher, als die dann Tatsächlich im Ram belegen. Ein JPEG in 1920*1080 Aufgelöst und 500KB Dateigröße braucht mehr als 6MB Ram. Ein paar solcher Bilder im Ram abgelegt und die Dalvik-VM grüßt mit einer OutOfMemory-Exception.

Dazu kommt Fall einer Web Galerie die Bilder nicht lokal vorhanden sind, sondern bei Bedarf von einem WebServer geladen werden. Was einfach klingt, stellte sich schnell als vermeintliches Memory-Leak heraus. Im Grunde dreht sich alles um folgende Methode:

public View getView(int position, View cachedView, ViewGroup parent);

Mit dieser Methode werden die Views/darzustellenden Bilder zur Laufzeit abgefragt. Im einfachsten Fall sind das ImageViews die ein Bitmap auf den Bildschirm zeichnen.

Der augenscheinlichste Ansatzpunkt zum Speicher sparen ist, dass man eine vorher benutze View zur „Wiederverwendung“ übergeben bekommt (cachedView). Die CachedView kann man auch benutzen, der Resourcengewinn ist bei großen Bildern aber nicht „von Interesse“. In Anbetracht der xMB pro Bild, kommt es auf die paar Byte für die ImageView Struktur kaum an. Kommt jedoch ein AsyncTask zum ein Einsatz, um die Bilddaten „im Hintergrund“ herunterzuladen und in ein Bitmap zu decodieren, dann ist diese cachedView immens wichtig. Slided der User schneller durch die Galerie, als die Bilder aus dem Netz herunter geladen werden können, werden AsyncTask erzeugt, die „sinnlos“ Bilder im Hintergrund herunter laden und decodieren. Bei kleinen Bildern und einem Lokalen Cache, ist das nicht mal schlecht. Einmal gecached, werden die Bilder das nächste mal aus der lokalen Quelle geladen. Bei den großen Bildern frisst das parallele Decodieren schlicht zu viel Speicher.

Bei der GalDroid-Entwicklung hat es sich nicht als sinnvoll herausgestellt, die Anzahl der Threads zu kontrollieren. Der overhead, die Downloads zu queuen, stand in keinem Verhältnis zum Nutzen oder der Wartezeit des Users. Ich hab mir in der CachedView einfach den Task gemerkt, der das Bild herunterlädt und diesen bei Bedarf abgebrochen. So sind bei einer VollBild-Anzeigen selten mehr als vier Bilder parallel in Bearbeitung.

Eine Ausnahme gibt es noch: Ist das Layout des UI-Elements nicht statisch fixiert, werden die Dimension geändert, oder hängen die Dimensionen gar von den darzustellenden Inhalt ab, wird das 0-te Element mehrfach abgefragt um die benötigten Dimension zu bestimmen. Das ist in sofern übel, da es 5-7 mal hinter einander passierte, ohne das jeweils eine cachedView übergeben wird. Wieder: bei kleinen Bildern unschön, bei großen Bildern grüßt die OutOfMemory-Exception.

Das Problem kann man umgehen, in dem man mehr speichert. Bei der GalDroid wird einfach ein Feld von WeakReferences angelegt, in dem jedes je angefragte ImageView gespeichert wird. Die Logik ist simpel – wird ein Element mehrfach kurz hinter einander abgefragt, sollte es in diesem Speicher zu finden sein. Wird es länger Zeit nicht genutzt, räumt der GC auf und die WeakReference zeigt auf NULL.

Sollte nach diesen Maßnahmen immer noch OOM-Exceptions auftreten, kann man noch Bitmap.recycle aufrufen, sobald eine ImageView aus dem Sichtbereich des Users verschwindet. Das ist aber „frickelig“, da nicht immer klar ist, wann etwas „nicht mehr gesehen werden kann“. Das Recycle gibt sofort den Speicher des Bitmaps wieder frei, ohne den GC aufzurufen. Allerdings muss man so jedes mal das Bitmap neu laden, wenn es wieder in den Sichtbereich kommt.

Sollte das alles nicht helfen, kann man auf den Allocation Tracker zurückgreifen. Dieses Tool ist in dem ADT für Eclipse enthalten und listet jedes erstellte Objekt zur Laufzeit auf.

GalDroid – Gallery 3 für Tablets

Da es mich ein wenig genervt hat, dass es keine einfach Anbindung der Gallery 3 für Android Honeycomb gibt, hab ich mich selber mal hingesetzt und was zusammen geschustert. Das ganze ist ab heute im Market verfügbar.


Available in Android Market

Die Entwicklung ging relativ einfach von der Hand jedoch bin ich über einige grundlegende Sachen gestolpert, die ich in einigen Beiträgen in den nächsten Tagen vertiefen werde.

Für alle interessierten, den SourceCode gibt es hier: https://github.com/raptor2101/GalDroid