Posts mit dem Label Automation werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Automation werden angezeigt. Alle Posts anzeigen

15.03.2018

Ich bau mir ein Linux wie es mir gefällt - 1

Seit langen Jahren bin ich begeisterter Nutzer einer ganz speziellen Linux-Variante namens "Linux from scratch".

Wie der Name sagt, wird dabei ein Linux-System von Grund auf ("from scratch") komplett neu gebaut. Dazu werden die Quelltexte verwendet, um selbst alle Pakete zu übersetzen und einen PC (oder ein anderes Rechnersystem) mit einem von Festplatte startfähigen Linux auszustatten.

Dabei hat man natürlich ein Henne-Ei-Problem: man braucht ein Linux, um das neue Linux zusammen zu bauen. Für dieses Problem gibt es mehrere Lösungsmöglichkeiten: zum Einen kann man das LFS in einer virtuellen Maschine zusammenbauen. Alternativ kann man auf dem Zielrechner zunächst eine kleine Partition mit einem anderen Linux einrichten, von dort starten und dann das LFS auf den freien Platz der Festplatte werfen. Das Linux zum Bauen kann man dann hinterher als Notfallsystem behalten, oder aus der Partition dann die Swappartition machen oder sie anderweitig verwenden. Für das allererste ("bootstrap") Linux reicht eine Minipartition von 8-16 GB, und dort kann man dann z.B. ein Linux Mint oder Fedora installieren.

"Linux from Scratch" ist nicht nur eine Anleitung für ein Linux zum Selbstbasteln, sondern ist hauptsächlich dazu gedacht, Erfahrung beim Bauen von Linuxprogrammen aus den Quelltexten zu sammeln.

Man lernt dabei eine ganze Menge, nämlich vor allem Zusammenhänge zwischen den verschiedenen Programmen, Konfigurationsdateien, aber auch viel über Netzwerke, und vor allem natürlich über die technischen Voraussetzungen, darunter die Verwendung von Makefiles, und man lernt, Logfiles zu lesen, um die Probleme zu beseitigen.

Bei "LFS" gibt es verschiedene Steigerungsmöglichkeiten, wie man sich selbst das Leben schwer und spannend machen kann. Man kann das Basissystem bauen, dann hat man "nur" eine Linux-Kommandozeile.

Darauf aufbauend ist es mit dem Fortsetzungsbuch "Beyond Linux from scratch" (BLFS) möglich, einen Server zu bauen, mit dem man z.B. eine Firewall, ein NAS, einen Webserver, oder noch viel mehr zusammen bauen kann. Oder man baut sich ein Desktopsystem mit einer grafischen Oberfläche, und am Ende steht dann der selbst kompilierte Firefox. Hört sich das nicht cool an?

Eine Schwierigkeitsstufe darüber steht die Automation des Bau-Vorgangs - und hauptsächlich darüber will ich hier schreiben. Die Anleitung von "LFS" für ein Basissystem und für das "BLFS" sind ziemlich gut. Wenn man sich daran hält, hat man gute Chancen, manuell Schritt für Schritt ein lauffähiges System zu erstellen. Man geht am Browser durch alle Kapitel der Reihe nach durch und führt gemäß Anleitung die einzelnen Schritte durch. Das ist beim ersten Mal ehrlich gesagt mühsam - ich spreche aus Erfahrung ;-). Andererseits ist es spannend zu erleben, wie das Linuxsystem mit jedem Schritt wächst und tatsächlich immer mehr dem ähnelt, was man als Linuxbenutzer kennt.

Die Automation "Automated Linux from scratch" (ALFS) hingegen ist in einer README-Datei nur spärlich beschrieben. Dafür hat man, wenn man das System einmal verinnerlicht hat, eine wunderbare und sehr esoterische Möglichkeit, Updates zu installieren oder das gesamte System auf Knopfdruck noch einmal komplett neu zu erzeugen.

Auf diese Weise habe ich über Nacht ein Linux gebaut - besser gesagt: bauen lassen, das mit dem neuesten Kernel 4.15 und C-Compiler gcc 7.3 gegen Spectre und Meltdown gefeit wäre. - Wenn, ja wenn mein Bastelsystem ein 64-bit-System gewesen wäre (x86_64) und nicht 32 Bit (i686). Und wenn ich das Gefühl hätte, dass ich wirklich von diesen Sicherheitslücken bedroht bin. Aber zum Thema Risikoanalyse muss ich noch mal einen eigenen Artikel schreiben, denke ich.

Und noch eine Stufe darüber verwendet man nicht die "stable" Variante der Bücher, sondern die LFS- und BLFS-Bücher, die gerade in der Entwicklung sind. Die Autoren der Kapitel sind permanent damit beschäftigt, neue Versionen der Pakete zu integrieren und die Artikel anzupassen. Wenn sich z.B. bei einem Paket das Verfahren ändert, wie man das Paket lauffähig kompiliert, wird das enorm schnell in die "development"-Variante von LFS bzw. BLFS übernommen. Genau das ist mir während des Schreibens dieses Artikels passiert: ich habe das Makefile angestoßen, und mittendrin wurde im Buch auf ncurses 6.1 umgestellt. Kaum war ich fertig, veröffentlichen die Entwickler die neue glibc-Version 2.27. So kann's gehen ...

Um noch mal auf "Meltdown" und "Spectre" zurück zu kommen: das LFS-Buch enthält seit kurzem brandneue Kernel- und gcc-Versionen, die diese Sicherheitslücken beheben sollen. Das ist natürlich immer "work in progress". Der aktuelle Stand ist, dass Spectre V2 und Meltdown repariert sind, und für Spectre V1 ist noch einiges an Entwicklerarbeit zu leisten. Die alten 32-bit-Prozessoren hinken hier leider hinterher, für 64 bit (also alles, was grob jünger als 10 Jahre ist) sollten alle Fixes auf jeden Fall in Arbeit sein.

So, jetzt aber wie versprochen eine genauere Beschreibung der Automation für LFS (ALFS). Dazu lädt man sich zunächst das aktuelle Paket herunter. Der Paketname lautet aus historischen Gründen "jhalfs" und ehrt damit den ersten Entwickler des Verfahrens mit seinen Initialen "jh".

Damit jhalfs funktioniert, benötigt es ein paar zusätzliche Linux-Pakete auf dem Gastsystem, die man nachinstallieren muss, weil sie normalerweise nicht für Endbenutzer erforderlich sind. Dazu gehören natürlich der C-Compiler gcc und einige Hilfsprogramme und Bibliotheken wie bison, awk, ncurses etc. Das Schöne ist: wenn man jhalfs startet, kontrolliert es, was noch fehlt und beschwert sich. Für die Verwendung der "development"-Fassung muss man außerdem noch Subversion installieren, um die tagesaktuellen Dateien aus dem Versionskontrollsystem herunter zu laden.

Die Automation setzt tatsächlich schon bei den allerersten Schritten ein, die man gemäß LFS-Buch durchführen würde: dem Erzeugen eines neuen Unixbenutzers "lfs" für das Kompilieren der Pakete, dem Anlegen von Verzeichnissen usw. Der einzige Schritt, den man selbst durchführen muss, ist das Erzeugen einer Festplattenpartition und sie dann so zu mounten, dass die Skripte sie finden können. Traditionell ist das /mnt/lfs, man kann es aber nach Belieben ändern, wenn man weiß, was man tut.

Vorab ein paar Worte zum Design von jhalfs: die Autoren erstellen LFS und die weiteren Bücher in einem speziellen XML-Format namens "DocBook"; daraus werden mit trickreichen XSLT-Transformationen dann lesbare Bücher in HTML, PDF oder sogar TeX. jhalfs verwendet ebenfalls XSLT-Transformationen, um aus dem Buch die Kommandozeilenanweisungen herauszufiltern, und erstellt daraus ein Makefile und Installationsskripte für jedes Kapitel, d.h. jedes zu installierende Paket. Die Automation mit ALFS ist also ein zweistufiger Prozess: zuerst werden aus dem Buch die Befehle zum Bauen herausgefiltert und in ein Makefile mit Hilfsskripten umgewandelt, und in einem zweiten Schritt wird diese Befehlsliste ausgeführt.

Die Autoren liefern außerdem eine komplette Liste der Softwarepakete mit Versionsnummern, die man entweder selbst herunterladen kann oder die automatisch nach Bedarf geholt werden, wenn man eine einigermaßen schnelle Internetverbindung hat. Grob geschätzt muss man für LFS ca. 400 MB an Paketen herunterladen und für BLFS, je nach Umfang, bis zu 2 GB.

Die Pakete sollte man in einem Verzeichnis /mnt/lfs/sources unterhalb der zukünftigen LFS-Partition ablegen, damit während des Ablaufs alle Pakete gefunden werden können. Hierzu wird eine Unix-Technik namens "chroot" (change root) verwendet, um einem laufenden Programm eine andere Festplattenstruktur vorzutäuschen - effektiv wird das verwendet, um so zu tun, als würde es schon in der "richtigen" LFS-Umgebung ausgeführt werden.

Das Makefile für LFS wird in einem neuen Unterverzeichnis erzeugt, in das man nach der Festlegung von ein paar Grundannahmen wechseln muss. Genau wie /sources sollte dieses Verzeichnis auf der zukünftigen LFS-Partition liegen, damit alle Skripte auch in der "chroot"-Umgebung erreichbar sind. Wenn man nun dort "make" startet, sollte nach einigen Stunden (je nach Geschwindigkeit des Rechners*) ein fast schon startfähiges Linux auf der neuen Partition vorliegen. Schritte, die man automatisieren kann, aber nicht muss, sind das Kompilieren eines Kernels und das Festlegen der Partitionen, die beim Starten gemountet werden sollen.

*) Als grobe Richtschnur zwei Vergleichszahlen, die ich mit der aktuellen LFS-Version gemessen habe. Das Paket, das mit großem Abstand am längsten dauert, ist der finale Schritt, den C-Compiler zu kompilieren. Auf einem alten Pentium 4 dauert das 540 Minuten, auf einem etwas aktuelleren i5-3350 immer noch knapp 230 Minuten. Eine SSD statt einer Festplatte ist hierbei eine große Hilfe, Zeit zu sparen.

Der erste Schritt nach dem Auspacken des jhalfs-Pakets ist, in das Verzeichnis zu wechseln und dort "make" einzugeben. Wer schon mal einen Kernel selbst kompiliert hat, wird gleich einen Aha-Effekt erleben: die Konfiguration verwendet dieselbe Textoberfläche für die Menüauswahl wie der Kernel beim "make menuconfig". Hier kann man LFS/BLFS wählen, "stable" oder "development", ob der Kernel auch automatisiert kompiliert werden soll und einiges mehr. Die Grundeinstellungen sind gar nicht so schlecht, und man sollte nur ändern, was man auch versteht ;-)

Nach dem "exit" aus diesem ALFS-Menü kommt die Frage, ob man zufrieden ist mit der Konfiguration. Bei "yes" werden die schon erwähnten Prüfungen durchgeführt, ob alle Developerpakete auf dem Gastlinux vorhanden sind und bestimmte Mindestvoraussetzungen erfüllen, wie z.B. gcc mindestens in Version 4.6 (weil frühere Versionen Fehler enthalten, so dass das LFS nicht funktionieren würde oder Pakete nicht kompiliert werden können usw.). Wenn das alles erfolgreich geprüft wurde, wechselt man (als Benutzer, nicht als root) in das neue Verzeichnis und startet dort erneut "make".

Coole Socken schauen sich im Unterverzeichnis lfs-commands die Dateien unterhalb von chapter040506 und 07 erst mit dem Editor an, bevor sie make starten. Man könnte insbesondere bei den Dateien für Kapitel 7 direkt auf die Idee kommen, dort die Stellen noch zu bearbeiten, die mit **EDITME** markiert sind ;-)

Parallel dazu empfiehlt es sich, dieselbe Version des Buchs in einem Browser zu öffnen und die Kapitel zu lesen, die der Automatismus auch gerade zu bauen versucht. Bei Problemen kann man dann im jeweiligen Kapitel nachlesen, was gerade passiert.

Wahrscheinlich wird es beim ersten Mal nicht gleich komplett erfolgreich funktionieren; das ist aber nicht schlimm: wenn ein Fehler gemeldet wird, kann man sich die Logdatei dieses Schritts aus dem Unterverzeichnis logs anschauen, den Fehler beheben und dann einfach wieder "make" eingeben. Das make-Programm ist so schlau, dass es an derselben Stelle weitermacht. Manche Pakete werden mehrfach gebaut, z.B. der C-Compiler und einige Hilfsprogramme. Dies hat den Zweck, dass man nicht aus Versehen Abhängigkeiten zum Gastsystem einbaut. Wenn ein wichtiges Programm mit der C-Bibliothek des Gastsystems gelinkt wäre, würde das nach dem Booten ja nicht mehr funktionieren. Also wird ein "Zwischensystem" in einem anderen Pfad (/tools) gebaut, das dann das endgültige LFS kompilieren kann.

Ein paar generelle Tipps, wenn das Kompilieren eines Pakets fehlschlägt: gcc 7.3 ist "bleeding edge", also so brandneu, dass man sich bei Verwendung auch mal in die Finger schneiden kann ;-). Ich will damit sagen, dass hier soviele Änderungen drin sind, z.B. Anpassungen an die aktuellen C- und C++-Standards, die in den meisten Paketen und in den Gehirnen der Entwickler noch nicht angekommen sind. Aber: man will gcc 7.3 und aktuelle binutils einsetzen, weil hier die Reparaturarbeiten für Meltdown und Spectre stattfinden. Trotzdem nochmal die Empfehlung: erst mal ein "stable" LFS bauen und danach als Steigerung auf "development" umsteigen.

Typisches Problem beim Kompilieren:
  • ein C-Programm verwendet Datentypen, die jetzt in einer neu eingeführten include-Datei <stdint.h> deklariert werden und nicht mehr in <stdlib.h>. Vorläufig muss man also irgendwo den zusätzlichen include-Befehl unterbringen, bevor das Programm erfolgreich kompiliert werden kann. Freundlicherweise sagt make sehr genau, wo ein Problem aufgetreten ist.
  • beim Korrigieren eines Problems hat man Dateien oder Verzeichnisse als root editiert oder bearbeitet. Das führt zu einem Folgefehler, weil LFS alles mit dem Benutzer "lfs" durchführen will und dann ein Abbruch wegen Berechtigungsproblemen passieren kann (bringe ich laufend fertig).
    chown lfs:lfs is your friend.
Was mir noch untergekommen ist:
  • das gcc-interne Makro __sigemptyset gibt es nicht mehr. Man kann stattdessen sigemptyset (ohne die Unterstriche) verwenden.
  • die Makros major und minor für Devices sind jetzt in <sys/sysmacros.h> zu finden.
  • grub lässt sich mit binutils 2.30 auf 32-bit-Systemen nicht übersetzen, man muss zusätzlich zu den configure-Switches im LFS-Buch noch den Switch "--enable-64-bit-bfd" angeben. Alternativ binutils 2.29.1 statt 2.30 verwenden.
Zwei Schritte sind im LFS-Buch etwas vage beschrieben, weil sie sehr stark vom eigenen PC abhängen: die Konfiguration des Kernels und die bootfähige Festplatte. Wenn man ein Linux-Gastsystem mit "kernelconfig"-Unterstützung hat, kann man sich selbst einen maßgeschneiderten Kernel bauen mit "make oldconfig", das verwendet dann nämlich genau die Einstellungen, die im Moment aktuell sind.

Der einzige wichtige Punkt ist, dass das Filesystemformat des Bootlaufwerks in den Kernel fest kompiliert sein soll. Es nutzt nichts, wenn das Root-Filesystem mit ext4 formatiert ist und der Kernel die ext4-Fähigkeit erst mit einem Modul lernen soll, das auf der ext4-Festplatte liegt - hier beißt sich die Katze in den Schwanz. Ach ja: nach dem Kompilieren von Kernel und Modulen unbedingt in der chroot-Shell "make modules_install" durchführen und in /lib/modules kontrollieren!

Für spätere Bildungs- und Forschungszwecke kann man natürlich auch eine "initiale Ramdisk" (initrd) vorsehen und dort benötigte Module und sonstige Files ablegen, aber wenn man sich einen maßgeschneiderten Kernel für diesen speziellen PC erzeugt, ist das erst mal nicht nötig. Man kann es später machen, um Microcode-Updates per "early load" zu aktivieren, aber lebensnotwendig ist es erst mal nicht. Für intel-Prozessoren gibt es hier die Microcode-Dateien.

Vor dem Reboot sollte man dann im neuen /etc-Verzeichnis (das derzeit noch /mnt/lfs/etc heißt) nach Dateien schauen, die ein "**EDITME**" enthalten. Dort kann man dann Hostname, IP-Adresse, Nameserver etc. einbauen, die zur eigenen Umgebung passen (wenn man keinen eigenen DNS-Server betreibt, die IP-Adresse des DSL-Routers, der als DNS-Forwarder die Anfragen an den Provider weiterreicht). Die Dateien findet man mit einem beherzten "grep -l EDITME /mnt/lfs/etc/*", und wenn man gleich auf einen Schwung alle bearbeiten will, wirft man die Liste der Ergebnisse einfach einem Editor vor: "vi $(grep -l EDITME /mnt/lfs/etc/*)".

Die wichtigste Datei ist hier die /etc/fstab, die beschreibt, welche Partition der Festplatte welchen Zweck hat und wohin im Filesystem gehört. Eine Swappartition sollte man auf jeden Fall vorsehen, selbst wenn der PC gefühlt genug RAM eingebaut hat. Eine Begründung dafür findet sich hier (der Autor des Artikels arbeitet bei Facebook in einem Rechenzentrum). Früher gab es mal eine Regel "doppelt soviel Swap wie RAM", aber ich würde prinzipiell für normale Desktopsysteme zwischen 2 und 8 GB Swap vorsehen.

Was man auf keinen Fall vergessen darf: dem root-User des neuen LFS-Systems vor dem Reboot ein Passwort zu geben. Sonst kann man zwar wunderbar booten, aber sich nicht anmelden ;-)

Ganz ehrlich: das Gefühl ist unbeschreiblich, wenn das selbstkompilierte Linux dann tatsächlich zum ersten Mal bootet und man den Loginprompt sieht ;-)

Wenn das neue System nicht booten will, muss man Ursachenforschung betreiben. Ein "kernel panic" tritt auf, wenn das Root-Filesystem nicht gefunden wurde. Vermutlich fehlt dann das Kernelmodul für das betreffende Filesystem. Oder die Angabe in der /boot/grub/grub.cfg passt nicht, welche Partition das ist. Die Schreibweise von grub und Linux unterscheiden sich hier leider etwas. "sda" für Linux ist "hd0" in grub.

Ein Fehler, den ich mal gemacht habe: alle USB-Treiber als Module deklariert und dann nicht in /etc/modules definiert. In dieser Situation wird es für eine USB-Tastatur schwierig, ihre Eingaben abzuliefern :-).

In solchen Situationen bootet man dann erneut das Gastsystem, mountet die LFS-Partition wieder unter /mnt/lfs und fängt an zu reparieren.

Wenn es bootet und man sich anmelden kann, ist das Reparieren leichter: die Fehlermeldungen z.B. der init-Skripte stehen ja noch auf dem Bildschirm, und man kann Schritt für Schritt alles Nötige korrigieren und das neue, selbstgebaute Linuxsystem einrichten. Das LFS hat schon alles an Bord, um eigenständig Pakete zu übersetzen und zu installieren.

Nach dem erfolgreichen LFS geht es dann mit dem BLFS weiter.
Ein paar Vorschläge, wie man sich das LFS etwas bequemer bedienbar gestaltet:
  • gpm (copy+paste mit Maus in der Textoberfläche)
  • cpio (zum Bauen von initrd)
  • openssl (ssl-Bibliothek)
  • openssh (ssh server und client)
  • dhcpcd (IP-Adresse über DHCP setzen lassen)
  • nfs-utils (wenn man ein NAS hat, z.B. eine Fritzbox, oder selbst bauen will)
  • samba (wenn man selbst ein NAS aufbauen will oder einen Windows-Domaincontroller)
  • lm_sensors (Hardwareüberwachung von CPU und Lüfter)
  • lsusb (list USB devices)
  • lspci (list PCI devices)
  • ghostscript (für PDF)
  • cups (zum Drucken)
  • Firefox (für Katzenvideos im Internet)

14.10.2014

Flash-Update auf Version 15

Ausnahmsweise mal ein normales Update des Flash-Players für Windows, Mac OS und Linux. Nur verwunderlich, wie schnell die Major-Versionen purzeln. Grade erst Version 14, jetzt schon 15.

[Update der Versionsnummern 26.11.2014]

Wie üblich in ihrem freundlichen Service-Blog die passende Automation zum Herunterladen und Installieren.
Falls ein Proxy verwendet wird, das "rem" bzw. "#" entfernen.

Das Tool wget wird bei Windows noch benötigt wie hier beschrieben. Bei Linux sollte es schon vorhanden sein, da es von vielen anderen Programmen intern verwendet wird.

Für Windows wie üblich beide Varianten, ActiveX und Netscape Plugin.
@echo off

rem set http_proxy=http://192.168.100.100:3128/
set VNP=
15.0.0.239
set VAX=15.0.0.239
set H=fpdownload.macromedia.com
set P=/get/flashplayer/current/licensing/win
set AX=install_flash_player_15_active_x.exe
set NP=install_flash_player_15_plugin.exe

wget http://%H%%P%/%AX% -O flash-%VAX%_ax.exe
.\flash-%VAX%_ax -install
wget http://%H%%P%/%NP% -O flash-%VNP%_np.exe
.\flash-%VNP%_np -install

Für Linux 64 bit rpm (als root ausführen oder "sudo rpm" schreiben):
#!/bin/sh

# http_proxy=http://192.168.100.100:3128/

VL=11.2.202.424
H=fpdownload.macromedia.com
PL=/get/flashplayer/current/licensing/linux

DL() { wget -N "$1/$2"; mv "$2" "$3"; }

echo Linux 64 bit rpm ...
DL http://${H}${PL} \
   flash-plugin-${VL}-release.x86_64.rpm \
   flash-${VL}.x86_64.rpm
rpm -F --force
flash-${VL}.x86_64.rpm

[Hier ist noch der Verweis auf die Anleitung für Version 14 und Version 13]

09.04.2014

Flash-Update auf Version 13

Ausnahmsweise mal ein normales Update des Flash-Players für Windows, Mac OS und Linux.

Wie üblich in ihrem freundlichen Service-Blog die passende Automation zum Herunterladen und Installieren.
Falls ein Proxy verwendet wird, das "rem" bzw. "#" entfernen.

Das Tool wget wird bei Windows noch benötigt wie hier beschrieben. Bei Linux sollte es schon vorhanden sein, da es von vielen anderen Programmen intern verwendet wird.

Für Windows wie üblich beide Varianten, ActiveX und Netscape Plugin.
@echo off

rem set http_proxy=http://192.168.100.100:3128/
set VNP=
13.0.0.214
set VAX=13.0.0.214
set H=fpdownload.macromedia.com
set P=/get/flashplayer/current/licensing/win
set AX=install_flash_player_13_active_x.exe
set NP=install_flash_player_13_plugin.exe

wget http://%H%%P%/%AX% -O flash-%VAX%_ax.exe
.\flash-%VAX%_ax -install
wget http://%H%%P%/%NP% -O flash-%VNP%_np.exe
.\flash-%VNP%_np -install

Für Linux 64 bit rpm (als root ausführen oder "sudo rpm" schreiben):
#!/bin/sh

# http_proxy=http://192.168.100.100:3128/

VL=11.2.202.359
H=fpdownload.macromedia.com
PL=/get/flashplayer/current/licensing/linux

DL() { wget -N "$1/$2"; mv "$2" "$3"; }

echo Linux 64 bit rpm ...
DL http://${H}${PL} \
   flash-plugin-${VL}-release.x86_64.rpm \
   flash-${VL}.x86_64.rpm
rpm -F --force
flash-${VL}.x86_64.rpm

[Update 20140514: Anpassung auf Version 13.214 bzw. 11.359]

21.03.2014

Backup ist gut. Oder: Unprofessionelles Verhalten

Neulich ist mir etwas merkwürdiges passiert. Aber der Anlass für diesen Artikel ist dann nur der Auslöser für ein paar Gedanken über Backup und Restore, d.h. Sicherung und Wiederherstellung von Daten.

Ich habe einem Bekannten einen kleinen Gefallen getan und ihn zu einem Termin gefahren, weil er sich wegen einer Erkältung nicht wohl gefühlt hat und deshalb nicht selbst fahren wollte. Da der Termin nur sehr kurz war, bin ich vor Ort geblieben und habe gewartet. Es handelt sich um ein Ärztehaus mit verschiedenen Fachrichtungen, und auf der Etage praktiziert auch ein Psychologe (mein Bekannter ist nicht in psychologischer Behandlung, es ist nur eine zufällige Nachbarschaft der Praxen). Es gibt kein Wartezimmer, sondern einen gemeinsamen Wartebereich im Flur.

Im gegenüberliegenden Raum stand ein eingeschalteter Laptop auf dem Schreibtisch. Während ich nun so vor mich hin wartete, fiel mir auf, dass dieser Laptop von Zeit zu Zeit ungesunde Geräusche von sich gab. So klingt eine Festplatte, kurz bevor sie den Geist aufgibt - das habe ich leider selbst schon mehrfach beruflich und privat erlebt. Außerdem ist der Lüfter des Laptops regelmäßig angesprungen, und auch das ist bei einem unbenutzten Laptop ein eher schlechtes Zeichen. Es bedeutet, dass der Rechner schon stark verstaubt ist und sich deshalb übermäßig erwärmt, weil die Staubschicht die Wärmeabfuhr behindert.

Der Besitzer des Laptops verließ nach einigen Minuten ein anderes Zimmer und ich sprach ihn eher zurückhaltend an, ob dies sein Laptop sei? Als er bejahte, erwähnte ich, dass mir die Geräusche des Laptops ungesund vorkämen. An seinem Gesichtsausdruck bemerkte ich schon gleich, dass ich vermutlich ein Fettnäpfchen erwischt habe. Er antwortete kurz, dass der Laptop schon recht alt sei. Ich sagte, dass meiner Meinung nach die Festplatte kurz vor dem Ausfall steht und ob er eine Sicherung seiner Daten habe. Auf diese Bemerkung hin wurde er merklich unfreundlich, wandte sich ab und antwortete mir im Weggehen, dass er sich darum kümmern werde, und schloss die Tür seines Büros.

Mir ist selbst klar, dass ich ein eher loses Mundwerk habe und drauflosrede; dabei denke ich eher nicht nach, wie empfindlich jemand darauf reagieren könnte. Andererseits nahm ich an, ein sachliches Thema und damit ein objektives Problem anzusprechen.

Ich finde es sehr merkwürdig, dass ein professioneller, ausgebildeter Psychologe auf einen sachlichen Hinweis so patzig reagiert hat. Da wir uns nicht kennen (oder vielleicht deshalb) entfällt völlig, dass es einen persönlichen Grund gibt, ihn anzugreifen. Offensichtlich fühlte er sich aber doch angegriffen von meinem Hinweis. Das wiederum lässt mich auf ein schlechtes Gewissen schließen und jemanden, der sich "ertappt" fühlt.

Möglicherweise verwendet er den Laptop sogar, um Patientendaten zu speichern (Behandlung/Abrechnung), und dann ist es fahrlässig, einen PC ohne Sicherung zu betreiben. Es ist geschäftlich gefährlich, weil ohne Festplatte keine Abrechnung möglich ist, und beruflich, weil die Historie der Patientenbehandlungen verloren geht.
Natürlich muss man bei solchen höchst vertraulichen Daten für eine Absicherung der Daten und der Sicherung sorgen, also eine externe Festplatte für die Sicherung verwenden, die ebenso verschlüsselt ist wie die interne Festplatte, oder die Branchensoftware kann eine verschlüsselte Sicherung auf einem separaten Datenträger ablegen.

Auf jeden Fall muss man bei beruflicher Nutzung unbedingt ein Konzept für Sicherung und Ausfall bereithalten, und man muss in der Lage sein, es zu befolgen. Das bedeutet, dass man nicht nur ein regelmäßiges, am besten automatisiertes, Backup einrichtet, sondern dass man auch mindestens einmal ausprobiert hat, ein vorhandenes Backup wieder zurückzuspielen. Am besten sollte man das mit schriftlicher Anleitung sogar mehrfach üben, damit es im Notfall problemlos durchgeführt werden kann. Eine schriftliche Liste ist wichtig, weil man in einer Notfallsituation richtig unter Streß steht. Da kann so eine schöne Liste zum Abhaken eine gute Sache sein.

Merke: niemand will Sicherungen durchführen, weil sie Aufwand verursachen.
Aber: jeder will eine Sicherung zurückspielen, wenn es einen Ausfall gab ;)
Im englischen klingt das knapp und prägnant: "nobody wants backup, but everybody wants restore".

Obwohl ich sonst nicht viel Gutes über Apple sagen kann, finde ich das Prinzip der "Time machine" ziemlich genial, dass mir ein Arbeitskollege vor einiger Zeit mal beschrieben hat. "Time machine" ist eine Software, die permanent läuft und ein "Journal" von Änderungen auf einer externen Festplatte ablegt. Das ist so etwas ähnliches wie ein Versionskontrollsystem (CVS, Subversion, Git, ...), aber es arbeitet permanent im Hintergrund und protokolliert alle Änderungen an Dateien auf der Festplatte mit. Wenn nun etwas passiert, kann man mit Hilfe des Journals schrittweise rückwärts jede beliebige Version einer Datei wiederherstellen.

Im Gegensatz zu einem redundanten Festplattensystem (RAID mit mehreren Festplatten, z.B. als Spiegel raid1 oder Array raid5) stellt "Time machine" also wirklich ein effektives Sicherungssystem dar. Wenn man in einem RAID eine Datei löscht, ist sie wirklich weg, weil der Löschbefehl natürlich "offiziell" ist und deshalb auch die redundanten Kopien entfernt. Ein RAID hilft nur bei einem Hardwareausfall, aber nicht bei absichtlichem Löschen. Trotz RAID (z.B. wie bei uns mit FreeNAS) ist ein Backupkonzept unabdingbar.

Ich bin auch ein gebranntes Kind, was Festplattenausfälle angeht: die erste Computerinstallation in der Tierarztpraxis umfasste drei PCs und einen Server. Alle vier Geräte waren damals baugleich. Simple PCs, viermal dasselbe Mainboard,  Gehäuse, RAM, CPU, und insbesondere in jedem PC dieselbe Baureihe der Festplatte (damals Seagate 40 GB).

Nach etwa einem Jahr fiel der Server aus: die Festplatte wurde plötzlich nicht mehr erkannt. Üble Sache, Freitags passiert. Ganz schlecht, um kurzfristig Ersatz zu beschaffen, wegen des Wochenendes. Kurzerhand baute ich aus dem eher unbenutzten PC im zweiten Untersuchungsraum die Festplatte aus und ersetzte die Festplatte im Server. Backup vom Band zurückspielen war eine leichte Sache. Da aber das Backup nachts erstellt wird, hat der komplette Tag im Datenbestand gefehlt, d.h. alle eingegebenen Befunde des Tages waren weg. Meine Frau hat dann erstmal keine neuen Daten eingegeben, sondern alles vom Samstag auf Papier notiert, weil ich noch ein bißchen Hoffnung hatte, die alten Daten zu restaurieren.

Am Wochenende habe ich mir also die defekte Festplatte nochmals angesehen und festgestellt, dass (zum Glück) "nur" die Elektronikplatine einen Defekt hatte. Ich konnte also von einer der baugleichen Festplatten aus einem anderen PC die Platine abmontieren und alles tauschen. Damit war der Server dann mit dem alten Datenbestand wiederhergestellt. Nur der Samstag musste dann von den Notizen nachträglich eingegeben werden. Aber die komplette Historie aller Patienten war sichergestellt.

Als allernächstes habe ich dann sofort zwei neue Festplatten bestellt (bei der Gelegenheit dann auch eine Nummer größer) und den Linux-Server mit Raid1 ausgestattet. Er läuft heute immer noch als Samba-Server, Domaincontroller, DNS, Emailserver, Webproxy mit Cache, LDAP-Server, Chat-Server, interner Webserver, Firewall und perl-Bastelsystem ;).

Die Praxisdaten sind in der Zwischenzeit auf ein FreeNAS-System umgezogen - natürlich ebenfalls mit Raid1 und täglichem Backup, aber der ganze "Kleinkram" liegt immer noch auf demselben Linux-System, zumindest fast "demselben", was die Hardware angeht. Es gab natürlich zwischendurch noch andere Reparaturen, hier mal ein Netzteil, dann ein neues Mainboard, mehr RAM, schnellere CPU, ... Aber Linux ist da recht schmerzfrei, nach jeder Veränderung lief es trotzdem problemlos weiter. Gelegentlich musste ich mal ein neues Modul kompilieren, weil sich z.B. mit dem neuen Mainboard der Netzwerkchip geändert hat.

Nach einigen Erfahrungen mit Suse bin ich mittlerweile bei linux-from-scratch gelandet und baue sämtliche Software selbst. Aber dazu schreibe ich vielleicht nochmal einen eigenen Artikel, das ist nämlich eine ziemlich spannende Sache, wenn man mal unter die Motorhaube eines Linux-Systems schauen will ;)

28.02.2014

LTE-Empfangsqualität abfragen (Diagnose-Logfile)

Neulich hab ich mich beschwert, dass man beim alten Modell des LTE-Routers (Telekom LTE 1 bzw. Huawei B390s) nicht die Empfangsqualität abfragen kann.

Und es geht doch! Zwar ziemlich versteckt in den Tiefen der "Erweiterten Einstellungen", aber man kann es doch abfragen.

Wie üblich in Ihrem freundlichen Service-Blog auch gleich wieder die Möglichkeit, diese Daten automatisiert per Skript abzufragen und damit ggfs. regelmäßig zu protokollieren.

Der übliche Trick: mit dem ersten wget-Aufruf geschieht der Login, das Session-Cookie wird gespeichert, und mit dem zweiten Aufruf wird dann die interessante Seite abgerufen.
#!/bin/sh

PATH=/opt/bin:/opt/sbin${PATH:+:$PATH}

H=vodafonemobile.cpe
C=/tmp/cookies.txt
U1="http://"$H"/login.cgi"
U2="http://"$H"/diagnosis_export.cgi?configId=$(date +%s)&FileName=diagnosis.txt"

wget --save-cookies "$C" \
     --keep-session-cookies \
     -q --tries=1 \
     -O /dev/null \
     --post-data 'Username=admin&Password=yourpw' \
     "$U1"

wget --load-cookies "$C" \
     -q --tries=1 \
     -O - \
     "$U2" | \
awk '
{if (NR>=5&&NR<=42){print}}
'

rm -f "$C"
Der Output sieht dann in etwa so aus wie da unten.

Besonders interessant sind die Zeilen bei "RSRP" und "RSRQ". Mit den Werten -90dBm und -8 dB zeigt mir der Router 4 von 5 Balken an. Laut Telekom-Auskunft sind Werte bis -60/-2 möglich, das wäre super-super-optimal. Wenn man das Gerät in der Ausrichtung verändert, darf  RSRP ruhig etwas schlechter werden, falls man dadurch RSRQ verbessern kann. Optimal wäre, wenn beide Werte größer werden (-90 --> -60, -8 --> -2).

Auch sehr hübsch: man erfährt den Funkmast, mit dem der Router verbunden ist, und zwar mit dem Parameter "CellId".

~~~~~~Product Information~~~~~~
003    Model:               B390s-2                    PASS
004    Software Version:    V200R001C35SP12            PASS
005    Hardware Version:    B390-B390RW2A Ver.C        PASS
006    SN:                  4UA5TC1110400163           PASS
007    IMEI:                354637040077555            PASS
008    LAN MAC Address:     F4:C7:14:**:**:**           PASS
009    WLAN MAC Address:    F4:C7:14:**:**:**           PASS
010    Check AT-Port:        Available                  PASS

~~~~~~LTE State~~~~~~
011    Dialing Mode:                                   PASS
012    APN:                  internet.home              PASS
013    DNS:                  8.8.8.8                    PASS
014    PDN Type:             IPv4                       PASS
015    Service Status:       Normal Service            PASS
016    Connection Status:    LTE STATE CONNECTED       PASS
017    Frequency:            816000 kHz               PASS
018    Bandwidth:            10MHz                    PASS
019    CellId:               392                       PASS

020    IP Address:           2.162.**.***             PASS
021    RSRP:                 -90dBm                    PASS

022    RSSI:                 -66dBm                    PASS
023    RSRQ:                 -7dB                      PASS 

024    Roam:                  no                       PASS
025    Antenna State:        Built-In                  PASS

~~~~~~DHCP~~~~~~
026    Gateway IP Address:    192.168.42.1           PASS
027    DHCP Enabled:          false                  PASS
028    Subnet Mask:          255.255.255.0          PASS
029    DHCP_ip_pool_start:    N/A                    PASS
030    DHCP_ip_pool_end:     N/A                     PASS
031    DHCP_lease_time(s):    N/A                     PASS

~~~~~~SIM/PIN State~~~~~~
032    sim_state:             SIM Card detected       PASS
033    PIN code:             READY                    PASS
 

05.02.2014

Notfall-Update für Adobe Flash player - mal wieder

Und schon wieder ein außerplanmäßiges Update des Flash-Players für Windows, Mac OS und Linux. heise empfiehlt aufgrund der Dringlichkeit die sofortige manuelle Installation. Das Problem wird schon von Bösewichten ausgenutzt. Man sollte sich nicht auf das automatischen Update-Verfahren verlassen, das in den Flash-Player eingebaut ist.

Wie üblich in ihrem freundlichen Service-Blog die passende Automation zum Herunterladen und Installieren.
Falls ein Proxy verwendet wird, das "rem" bzw. "#" entfernen.

Das Tool wget wird bei Windows noch benötigt wie hier beschrieben. Bei Linux sollte es schon vorhanden sein, da es von vielen anderen Programmen intern verwendet wird.

Für Windows wie üblich beide Varianten, ActiveX und Netscape Plugin.
@echo off

rem set http_proxy=http://192.168.100.100:3128/
set VNP=12.0.0.70
set VAX=12.0.0.70

set H=fpdownload.macromedia.com
set P=/get/flashplayer/current/licensing/win
set AX=install_flash_player_12_active_x.exe
set NP=install_flash_player_12_plugin.exe

wget http://%H%%P%/%AX% -O flash-%VAX%_ax.exe
.\flash-%VAX%_ax -install
wget http://%H%%P%/%NP% -O flash-%VNP%_np.exe
.\flash-%VNP%_np -install

Für Linux 64 bit rpm (als root ausführen oder "sudo rpm" schreiben):
#!/bin/sh

# http_proxy=http://192.168.100.100:3128/

VL=11.2.202.341

H=fpdownload.macromedia.com
PL=/get/flashplayer/current/licensing/linux

DL() { wget -N "$1/$2"; mv "$2" "$3"; }

echo Linux 64 bit rpm ...
DL http://${H}${PL} \
   flash-plugin-${VL}-release.x86_64.rpm \
   flash-${VL}.x86_64.rpm
rpm -F --force
flash-${VL}.x86_64.rpm
[Update 20140205: Tippfehler in der Windows-Versionsnummer, Hinweis auf wget für Windows]
[Update 20140221: Neue Flashversionsnummern gemäß 2. heise-Meldung]

18.01.2014

Erbsen zählen - Perl und CGI - Dritter Teil

Im dritten Teil beschreibe ich nun das Interessanteste: die Auswertung der Daten in Text- und Grafikform, deren Prinzip ich im ersten Teil und Sammlung im zweiten Teil beschrieben habe.

Genau wie schon früher beschrieben, verwende ich das Grafikmodul GD für Perl. Dort kann man ein paar Daten hineinstecken und dann mit einem Aufruf unterschiedliche Schaubilder erzeugen lassen, als "line"-Graph, "bar"-Graph und viele andere. Der Output ist dann HTTP-konform eine Grafikdatei im gewünschten Format, z.B. PNG oder GIF, zusammen mit einer Headerzeile und dem passenden MIME-Typ.

Beim Design gibt es zwei grundsätzliche Überlegungen:
  1. Die Logdateien sammeln Daten pro überwachtem Hostname, ich muss also noch eine Summe bilden, um den Verbrauch pro Host im Verhältnis zum Rest beurteilen zu können.
  2. Ich würde gern sowohl eine Tabelle mit den aktuellen Daten sehen als auch einen Graphen, aus dem man Trends ablesen kann. Ich brauche also im CGI-Skript eine Fallunterscheidung - Text oder Grafik.
Hier kommt das komplette CGI-Skript. An ein paar Stellen füge ich Kommentare in rot oder blau ein, wenn es etwas zu erklären gibt.

Eine grundsätzliche Bemerkung vorneweg: die Datenübergabe erfolgt immer in Form von Referenzen (in C wären das Zeiger auf Datenstrukturen), deshalb verwende ich im Skript i.a. auch gleich Variablen, Arrays und Hashes, die Referenzen enthalten, damit die Übergabe an GD nicht noch unnötig Datenformate umwandeln muss.

Um das CGI-Skript bequem testen zu können, habe ich einen "Testmodus" eingebaut. Man kann generell Skripte, die das CGI-Modul verwenden, auch auf der Kommandozeile aufrufen. Die Argumente, die normalerweise in der Request-URL nach dem "?" folgen, schreibt man einfach als Pärchen mit "name=wert" hinter den Skriptnamen in die Kommandozeile, wie man hier am Beispiel sieht.
# ./accounting.pl scale=1000 testmode=99 xmax=400 ymax=300
Bei diesem Testmodus ist noch zu bedenken, dass natürlich die Umgebungsvariablen des Webservers nicht gesetzt  sind (QUERY_STRING, PATH_INFO, die SSL_*-Variablen, wenn das Skript mit https aufgerufen wurde, usw.). Ggfs. müsste man diese Variablen manuell mit passenden gefälschten Inhalten setzen, damit das Skript an den entsprechenden Stellen sinnvolle Werte bekommt.

Dem Skript kann man einen Parameter "filter" übergeben, um nur einen ganz bestimmten Hostnamen auszufiltern. Deshalb gibt es eine etwas unübersichtliche Fallunterscheidung, ob ein Filter gesetzt ist oder nicht.

Mit "mode=1" wird eine Grafik in voller Größe mit mehreren Graphen und der Gesamtsumme angezeigt; "mode=16" erzeugt eine Tabelle in Textform mit einer Zeile pro Hostname in der Logdatei und hinter jedem Hostnamen in einer Extraspalte eine wönzig kleine Grafik mit der Gesamtsumme und dem Verbrauch dieses Hosts.

#!/usr/bin/perl -w

use strict 'vars';
use strict 'refs';

use GD::Graph;
use GD::Graph::lines;
use GD::Graph::bars;
use GD::Graph::hbars;

# Die GD-Module zeichnen die verschiedenen Typen von Graphen
use CGI qw(:standard);

#use lib "/usr/local/bin";

my @data;

# Array mit Beschriftung, x-Skala und ein oder mehreren y-Datenpunkten
my %hosts;

# Array zum Speichern der Daten pro Hostname
my $graph;
my $format;
my ($xmax,$ymax);

# Größe des auszugebenden Bilds
my $query;
my $range;
my $filter;

# Regexfilter für Hostnamen
# mode=0 full graph mode all hosts
# mode=9 sum only graph
# mode=16 text mode
# mode=99 debug text mode
my $mode;
my @x;
my @y1;

# y1=summierte Daten IN
my @y2;

# y2=summierte Daten OUT
my $scale;
#my $offset;
my $title="LTE volume statistics";

# X-Beschriftung
# werte1
# werte2
# ...
@data=(
);

%hosts=(
# "dummy.moeller-seeling.local" => { "IN " => [ 0 ], "OUT" => [ 0 ] }
);

my $max=0;
my $lines=0;

# logfile inbound, outbound
my ($login,$logout);


$query=new CGI();
$mode=$query->param( 'mode' ) || 0;
$range=$query->param( 'range' ) || "60";
#$offset=$query->param( 'offset' ) || "0";
$scale=$query->param( 'scale' ) || "2500";
$filter=$query->param( 'filter' ) || "";
$xmax=$query->param( 'xmax' ) || "800";
$ymax=$query->param( 'ymax' ) || "600";
$login=$query->param( 'infile' ) || "/var/log/lte-acct.in";
$logout=$query->param( 'outfile' ) || "/var/log/lte-acct.out";
$graph = GD::Graph::lines->new($xmax, $ymax);
$format = $graph->export_format;

# 20140108-110202 IN  i9100.moeller-seeling.local              91M
# 20140108-110202 IN  lifetab.moeller-seeling.local            42M
# 20140108-110202 IN  nexus.moeller-seeling.local              8748K
# 20140108-110202 IN  vettie68.moeller-seeling.local           143M

#my $d;

sub readfile {
  my ($file,$tag,$y)=@_;

  if (open(F,"<",$file)) {
    my ($dt,$old)=("","");

      while (<F>) {
      my ($name,$num,$unit,$hm,$m);

      chomp;
# 20140107-184506 IN  lifetab.moeller-seeling.local            0
# dieser Regex zerlegt eine Zeile, $tag ist IN oder OUT      if ($filter) {
        ($name,$num,$unit,$dt,$hm,$m)=($filter,$7,$8,"$1$2$3-$4$5$6","$4$5",$5)
          if (/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2}) $tag\s+$filter.*?\s*(\d+)([KMG]?$)/);
      }
      else {
        ($name,$num,$unit,$dt,$hm,$m)=($7,$8,$9,"$1$2$3-$4$5$6","$4$5",$5)
          if (/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2}) $tag\s+([a-z.-][0-9a-z.-]+)\s*(\d+)([KMG]?)/);
      }
      if ($name && defined($num) && defined($unit)) {

        ++$lines;
        if ($unit eq "")  { $num>>=10; }
#       if ($unit eq "K") { $num<<=10; }
        if ($unit eq "M") { $num<<=10; }
        if ($unit eq "G") { $num<<=20; }
# alle Zahlenwerte sind in KB        if ($num>1000) {
          if ($ymax<100) { push(@x,$m); }
          else { push(@x,$hm); }
          $num>>=10;
          push(@{$hosts{$name}->{$tag}},$num);
if ($mode==99) { print STDERR "# <$_>\n# $hm $tag $filter $num $unit | $max\n"; }

# in den Logfiles stehen kumulierte Daten pro Host
# alle Werte vom selben Timestamp addieren für Gesamtsumme
          if ($dt eq $old) { $num+=pop(@{$y}); }
#         else { $num+=$offset; }
          push(@{$y},$num);
          $old=$dt;

# Timestamp aufheben für Vergleich mit nächster Zeile
          if ($max<$num) { $max=$num; }

# Maximum merken für y-Skalierung
if ($mode==99) { print STDERR "# @{$y}\n"; }
        }
      }

    }
    close(F);
  }
}

readfile($login, "IN ",\@y1);
readfile($logout,"OUT",\@y2);

if ($mode==99) {
  local($,=", ");
  foreach my $h (keys(%hosts)) {
    print STDERR "# h $h\n";
    print STDERR "# i @{$hosts{$h}->{'IN '}}\n";
    print STDERR "# o @{$hosts{$h}->{'OUT'}}\n";
  }
  print STDERR "# Sum\n";
  print STDERR "# i @y1\n";
  print STDERR "# o @y2\n";
}


# maximal die letzten $range werte aus der Datei darstellen


if ($range>0) {
  splice(@x,0,-$range);
  splice(@y1,0,-$range);
  splice(@y2,0,-$range);
  $lines=$range;
}
$data[0]=\@x;

# mode=9 nur die Summe
# mode !=9 alle Hostnamen auch einzeln zeigen
if ($mode!=9) {
  foreach my $h (keys(%hosts)) {

# inbound ist interessanter
# man könnte aber in und out anzeigen
    push(@data,$hosts{$h}->{'IN '});
#   push(@data,$hosts{$h}->{'OUT'});
  }
}
# sum of all hosts IN
push(@data,\@y1);
# sum of all hosts OUT
#push(@data,\@y2);

$max=$scale*int($max/$scale+1);

# sinnvolle obere y-Grenze ausrechnen

if ($mode==99) { print STDERR "# max=$max\n"; }

# Daten übergeben$graph->set(
  x_label       => 'Volume Date/Time, ' . $lines . " samples",
  y_label       => 'max ' . $max . ' GB',
  title         => 'LTE volume (GB)',
  y_max_value   => $max,
  y_tick_number => 20,
  y_label_skip  => ($ymax<200) ? 5: 2,
  x_labels_vertical => 1,
  x_label_skip  => $lines / 12,
) or die $graph->error;

# optional die Farben selbst festlegen
#$graph->set( dclrs => [ qw(green lred blue cyan) ] );

# gibt es überhaupt daten?

if ($lines>0) {
  if ($mode<16) {

# bilddatei erzeugen
# binmode ist nur mit windows-webserver wichtig
    print header("image/$format");
    binmode STDOUT;
    print $graph->plot(\@data)->$format();
  }

# textmodus output als tabelle
  elsif ($mode==16) {
    print CGI::header();

    print <<__HEADER__;
<HTML>
<HEAD>
<TITLE>$title</TITLE>
<META http-equiv="refresh" content="300" />
<style type="text/css">
th {
  padding : 3px;
  text-align : left;
}
td {
  padding : 3px;
  text-align : right;
}
</style>
</HEAD>
__HEADER__

print "<!-- ";
print join( " -->\n<!-- ",
           map( "$_ => $ENV{$_}", sort(keys(%ENV)))
          );
print "-->\n";

    print <<__BODY__;
<BODY>
<H1>$title</H1>
<table border="1">
<tr>
<th> Hostname </th>
<th> Download </th>
<th> Upload </th>
</tr>
__BODY__

    foreach my $h (keys(%hosts)) {
      print "<tr>\n";
      print "<th> $h</th>\n";
      print "<td>",pop(@{$hosts{$h}->{'IN '}})," MB </td>\n";
      print "<td>",pop(@{$hosts{$h}->{'OUT'}})," MB </td>\n";

# hinter jeden hostnamen einen wönzigen Detailgraphen anzeigen
if (defined($ENV{'HTTP_HOST'})) {
      print "<td><IMG SRC=http://",$ENV{'HTTP_HOST'},$ENV{'SCRIPT_NAME'},"?filter=$h&xmax=300&ymax=100&scale=",int($max/10),"</IMG></td>\n";
}
      print "</tr>\n";
    }
    print "<tr>\n";
    print "<th style='text-align:right'> &Sigma; </th>\n";
    print "<td>",pop(@y1)," MB </td>\n";
    print "<td>",pop(@y2)," MB </td>\n";
if (defined($ENV{'HTTP_HOST'})) {
      print "<td><IMG SRC=http://",$ENV{'HTTP_HOST'},$ENV{'SCRIPT_NAME'},"?mode=9&xmax=300&ymax=100&scale=",int($max/10),"</IMG></td>\n";
}
    print "</tr>\n";

    print <<__BODY__;
</table>
<DIV>
<HR/>
<ADDRESS>
ths, last changed 09.01.2014
</ADDRESS>
</DIV>
</BODY>
</HTML>
__BODY__

  }
}


[Update 20140122: Links zum 1. und 2. Teil eingefügt]

10.01.2014

Erbsen zählen - zweiter Teil

Nachdem ich also im 1. Teil das Grundprinzip des Erbsenzählens beschrieben habe, kommt hier ein bißchen mehr Skripting zum Einsatz, damit sich der Tag "Automation" auch lohnt ;)

Das Datensammeln an sich ist eine triviale Sache. Man braucht ein kleines Skript, das mit Hilfe von cron regelmäßig ausgeführt wird.

Ich habe einfach mal willkürlich festgelegt, dass es einmal stündlich laufen soll:

2 * * * * /usr/local/sbin/ip-accounting.sh -I /var/log/lte-acct.in -O /var/log/lte-acct.out >/dev/null 2>&1

Der Inhalt des Skripts sieht ein bißchen aufgebläht aus, weil ein paar Kommandozeilenargumente ausgewertet werden wollen. Die wichtigste Zeile im Skript habe ich nochmal extra rot markiert, das ist die eigentliche Datensammlung.
#!/bin/sh

PATH=/opt/sbin:/opt/bin:/usr/bin:/usr/sbin${PATH:+:$PATH}

bytesin=0
bytesout=0
while getopts "iI:oO:" opt
do
  case "$opt" in
  i) bytesin=1;;
  I) filein="$OPTARG";;
  o) bytesout=1;;
  O) fileout="$OPTARG";;
  esac
done
shift $(expr $OPTIND - 1)
if [ "$bytesin$bytesout$filein$fileout" = "00" ]
then
  bytesin=1
  bytesout=1
fi

dt=$(date +%Y%m%d-%H%M%S)

out=$(iptables -L INET_OUT -v | awk -v D="$dt" '{if(NR>2){printf"%s OUT %-40s %s\n",D,$7,$2}}'|sort)
if [ "$bytesout" -eq 1 ]
then
  echo "$out"
fi
if [ -n "$fileout" ]
then
  echo "$out" >> "$fileout"
fi

out=$(iptables -L INET_IN  -v | awk -v D="$dt" '{if(NR>2){printf"%s IN  %-40s %s\n",D,$8,$2}}'|sort)
if [ "$bytesin" -eq 1 ]
then
  echo "$out"
fi
if [ -n "$filein" ]
then
  echo "$out" >> "$filein"
fi
Und was genau macht diese rot markierte Zeile? Schauen wir mal genauer hin. Es sind mehrere Befehle in einer Zeile, verbunden mit dem Weiterleitungszeichen | (pipe). Diese Pipe verbindet den Output des Programms vorne mit dem Input des Programms dahinter.
iptables -L INET_IN  -v |

Der erste Befehl zeigt die bis dahin aufgelaufene Datenmenge für alle überwachten Systeme an. Wichtig ist der Switch -v, sonst bekomme ich die Datenmenge nicht zu sehen.
awk -v D="$dt" '{if(NR>2){printf"%s IN  %-40s %s\n",D,$8,$2}}' |

Die Ausgabe enthält allerdings ziemlich viel Kram, der mich gar nicht interessiert, deswegen verwende ich awk, um mir gezielt nur das zu extrahieren, was ich für meine Neugier brauche. Natürlich könnte ich normalerweise auch grep verwenden, um etwas zu suchen, aber weil ich gleichzeitig ein wenig programmieren will und einzelne Felder aus jeder Zeile des Outputs ausschneiden will, verwende ich ganz gern awk, statt grep mit cut o.ä. zu kombinieren. Spart ein wenig CPU ;)

Bei diesem Befehl benutze ich zwei eingebaute Fähigkeiten von awk: es zählt automatisch die Zeilennummern in der Variablen NR (number of records) mit, was ich verwende, um die ersten zwei Zeilen mit der Überschrift des iptables-Outputs wegzuwerfen, und es zerlegt automatisch die Zeile in "Felder" gemäß einem vorgegebenen Trennzeichen (üblicherweise Whitespace). awk arbeitet also den printf-Befehl nur ab der dritten Zeile des Inputs ab und zeigt davon nur das achte und das zweite Feld an (Hostname und Datenmenge)

Bei der Chain INET_OUT ist  übrigens  der Hostname im siebten Feld enthalten, und bei der Chain INET_IN im achten Feld, weil ich ja einmal nach "source" und einmal nach "destination" suche.

Randbemerkung: damit awk einen Timestamp mit ausgeben kann, gebe ich dem Aufruf von awk einen solchen vorgefertigten Timestamp auf der Kommandozeile mit dem Switch -v NAME=WERT mit. Das können nicht alle awk unter allen Unix-Varianten, m.W. wurde das mit dem "new awk" (nawk) unter SunOS eingeführt, GNU awk unter Linux kann es auf jeden Fall.
sort
Der letzte Befehl sortiert das ganze schlicht und einfach alphabetisch. Ich könnte auch iptables mit dem Switch -n verwenden, um die IP-Adressen zu sehen anstatt der Hostnamen. Dann wäre es angebracht, bei sort ebenfalls -n zu verwenden, um numerische Sortierung zu erzwingen.

Dieses Skript sammelt also stündlich Daten pro überwachtem Hostname. Die gesammelten Daten sehen dann so aus:
server:/share/www/cgi-bin # tail -n2 /var/log/lte-acct.in /var/log/lte-acct.out
==> /var/log/lte-acct.in <==
20140109-150202 IN  nexus.moeller-seeling.local              23M
20140109-150202 IN  vettie68.moeller-seeling.local           286M


==> /var/log/lte-acct.out <==
20140109-150202 OUT nexus.moeller-seeling.local              6752K
20140109-150202 OUT vettie68.moeller-seeling.local           13M

Wie man sieht, ist der Output menschenfreundlich und es wird immer eine halbwegs lesbare Größenordnung bei der Datenmenge angezeigt: KB, MB oder GB. Ohne Einheit sind "Byte" gemeint. Diese Kleinigkeit ist beim Programmieren einer kleiner Stolperstein gewesen ;)

Das Auswerte-Skript habe ich als CGI-Skript in Perl gelöst. Es kann sowohl eine Tabelle als auch eine Grafik erzeugen (mit dem Perl-Modul GD). Das folgt dann im nächsten Teil.

Am Ersten des Monats sollte man übrigens die Zähler wieder auf Null setzen:
iptables -L INET_IN -Z
iptables -L INET_OUT -Z
Ich habe schon ein Skript, das mir meinen Verbrauchszähler auf Null setzt, dort werde ich das noch mit einbauen.

09.01.2014

Erbsen zählen mit Linux - IP Traffic accounting

Manche unter uns wurden geplagt von langsamem DSL.

Ein paar von denen sind mutig auf LTE umgestiegen, das die Telekom und andere Anbieter in unterversorgten Gebieten anbieten müssen (zwar nur im 800 MHz-Band, aber immerhin).

Und ein paar von denen sind neugierig, welcher Mitbewohner denn nun den Traffic verursacht und schon am 20. die Quote für den ganzen Monat aufgebraucht hat ;)

Das alles und noch viel mehr kann man mit einem Linux-Server machen, wenn man seine PCs und Smartphones im Haushalt nicht direkt ins Internet lässt, sondern den Linux-Rechner als Gateway dazwischenklemmt.

Dieser Ansatz hat nebenher noch den Vorteil, dass auf dem Linux-Server ein Caching Proxy laufen kann und damit potentiell ein bißchen vom Traffic nur einmal über die externe Internetverbindung abgerufen werden muss.

Hauptsächlich ist aber das interessanteste, dass nun wirklich jede Internetverbindung am Linux sichtbar ist.

Mit dem Modul "iptables", das bei jeder Linux-Distribution dabei sein sollte, kann man eine Statistik führen, welcher Rechner oder welches Smartphone wieviel Traffic verursacht hat.

Normalerweise wird "iptables" dazu verwendet, mit Regeln festzulegen, was erlaubt ist und was nicht. Nebenbei wird aber auch mitgezählt, wieviele Bytes durch die Regeln geflossen sind. Man kann diesen Mechanismus also für den Nebeneffekt des Zählens verwenden, wenn man schlicht "keine Regel" festlegt.

Nebenbei könnte man aber natürlich auch am Proxy datenintensive Sites wie spotify usw. sperren, die besonders viel vom Datenvolumen verbrauchen.

Grundsätzlich muss man dazu einen Linux-Server mit zwei Ethernet-Schnittstellen aufbauen. Manche Mainboards haben schon zwei davon; falls nicht, kann man für sehr wenig Geld eine zweite Karte in den PC einbauen. Es bietet sich an, den Linux-Server dann auch gleich zum NAS auszubauen mit einer zweiten Festplatte (für ein gespiegeltes RAID1) oder mit mehreren (wenn man ein RAID5 haben will).

Das wichtigste kommt nun: der Router (DSL oder LTE) kommt an die zweite Schnittstelle (oBdA ab jetzt eth1 genannt), und niemand sonst darf direkt mit dem Router verbunden sein. Es ist also sinnvoll, das WLAN am Router abzuschalten oder das Passwort so zu ändern, dass sich niemand mehr anmelden kann. Außerdem muss am Linux-Server das IP-Forwarding eingeschaltet sein, damit die anderen Systeme im Haushalt die Chance haben, trotzdem ins Internet zu kommen. Wenn man das WLAN am Router abschaltet, handelt man sich noch ein zu lösendes Problem ein: man benötigt einen separaten WLAN-Access Point für das interne Netz, der für Smartphones die Basisstation wird.

Alle anderen PCs und der Linux-Server mit seiner ersten Schnittstelle (eth0) müssen an einen "internen" Switch angeschlossen werden. Das kann der interne, neue WLAN-Access Point sein, oder eine eigene kleine Kiste, ganz nach Geschmack und örtlichen Gegebenheiten.

Am Linux muss nun prinzipiell das IP-Forwarding eingeschaltet werden. Dies bedeutet, dass aller Traffic, der an eth0 ankommt und nicht für eine andere interne Station gedacht ist, automatisch an eth1 weitergeleitet wird (und von dort natürlich weiter ins Internet).

Das deute ich hier nur an, für Details mal ein bißchen im Internet nach "linux ip forwarding" suchen.
    iptables -A FORWARD -j ACCEPT -m state --state ESTABLISHED
    echo 1 > /proc/sys/net/ipv4/ip_forward
    net=192.168.1.0/24
    iptables        -A FORWARD     -j ACCEPT     -i eth0 -s "$net"
    iptables -t nat -A POSTROUTING -j MASQUERADE -o eth1 -s "$net"

    iptables -A FORWARD -j REJECT
Statt des gesamten Netzes (in der Variablen $net) kann man auch gezielt einzelne Hostnamen oder IPs angeben, aber das Setup des IP-Forwardings soll hier nicht das Thema sein.


Stattdessen will ich ein bißchen genauer auf das eigentliche Thema eingehen: das Zählen des Traffics.

Dazu richtet man mit iptables zwei zusätzliche "Chains" ein, und klinkt beide in die immer vorhandene FORWARD-Chain mit ein. Das bedeutet so etwas wie ein Verdoppeln der Daten, wobei die Duplikate in den neuen Chains (eine für Input, eine für Output) nur für das Zählen verwendet und dann weggeworfen werden.
    iptables -N INET_OUT
    iptables -N INET_IN 
    iptables -I FORWARD -j INET_IN
    iptables -I FORWARD -j INET_OUT
Diese beiden neuen Chains stattet man als nächstes mit der Information aus, für welche IP-Adresse überhaupt gezählt werden soll. Dabei muss zum Zählen pro IP-Adresse natürlich dieselbige angegeben werden als "Selektor". Einmal muss der Selektor als "source" (ausgehender Traffic) und einmal als "destination" (eingehender Traffic) verwendet werden (die Variable $ip geeignet setzen).
    iptables -I INET_OUT -s "$ip"
    iptables -I INET_IN  -d "$ip"
Danach kann man schon mal ein bißchen mit dem Gerät spielen (am Smartphone den App Store/Play Store aufrufen oder eine andere App mit Internetabrufen) und beobachten, ob sich bei den Zählern etwas bewegt.
iptables -L INET_IN -v
Um das regelmäßig anzuschauen, kann man den Befehl "watch" verwenden, der ein anderes Kommando regelmäßig aufruft. Meinem Smartphone habe ich im DNS den Namen "nexus" gegeben, und nach diesem Namen suche ich nun:

# watch -n 60 "iptables -L INET_IN -v|grep nexus"

Every 60.0s: iptables -L INET_IN -v|grep nexus
1567 1198K all -- any any anywhere nexus.moeller-seeling.local
Das Interessanteste dürfte der "inbound" sein, der hereinkommt. Typischerweise ist das Downloadvolumen eher groß, und der "outbound" besteht hauptsächlich darin, die Anfragen zu verschicken. Außer natürlich, man lädt gerade große Bilder zu Flickr oder Videos zu Youtube hoch ;)

Als nächstes richtet man dann einen cron-job ein, der diese Daten in regelmäßigen Abständen protokolliert und daraus lustige Reports erstellt. Davon erzähle ich im nächsten Beitrag, wenn ich mit Programmieren fertig bin ;)


[Update: Idee von Robert Dahlem von der LUG Frankfurt, außerdem diese FAQ]

21.11.2013

So baut man ein aktuelles Komplettpaket Acrobat Reader X

Hilfsvariablen setzen


set D=C:\tmp\AR10
set S=C:\tmp\download
set MSI=AdbeRdr1000_de_DE.msi
set AR=ftp://ftp.adobe.com/pub/adobe/reader/win/10.x

Ordner erstellen

if exist %S%\nul rmdir /S /Q %S%
if exist %D%\nul rmdir /S /Q %D%
mkdir %S%
mkdir %D%
cd /D %S%

wget beschaffen

Bei sourceforge.net herunterladen und das ZIP auspacken. wget ist ein cooles Kommandozeilenwerkzeug zum Abrufen von URLs, sei es http, https oder ftp.

Dateien herunterladen

wget -nv -nc -N %AR%/10.0.0/de_DE/%MSI%
wget -nv -nc -N %AR%/10.1.8/misc/AdbeRdrUpd1018.msp

Patches in das Grundpaket integrieren

msiexec /a %S%\%MSI% /qb- TARGETDIR=%D%
msiexec /p %S%\AdbeRdrUpd1018.msp /a %D%\%MSI% /qb- TARGETDIR=%D%
Die Angabe /a führt eine reine "Administrator"-Installation durch, d.h. die Software wird nicht auf dem PC installiert, auf dem der Befehl aufgerufen wird, sondern es wird nur vorbereitet, dass die Software auf anderen PCs installiert werden kann. Mit /p werden die heruntergeladenen Patches in die Basissoftware integriert. Die Reihenfolge von /p und /a im letzten Befehl oben muss man einhalten, sonst klappt es nicht.

Recht angenehm: man muss nur den neuesten Patch für den Acrobat Reader herunterladen und nicht sukzessiv alle. Derzeit aktuell ist 10.1.8.

Der Ordner C:\tmp\download wird danach nicht mehr benötigt und kann gelöscht werden. Den vollständigen Ordner C:\tmp\AR10 kopiert man nun auf ein Netzlaufwerk, z.B. ein NAS oder eine Festplatte am DSL-/LTE-Router.

Installation

msiexec /i I:\win\adobe\AR10\AdbeRdr1000_de_DE.msi /qb-!
Natürlich muss man hier das Beispiel I:\win\adobe\AR10 entsprechend an die eigenen Verhältnisse anpassen.

Credits: das Prinzip und die Downloadlinks hab ich mir hier im heise-Forum abgeguckt.