QVINTVS · SCRIBET

Fritz!Box mit ein paar Tools aufrüsten

Ein Artikel darüber, wie man sich ein paar Extra-Konsolenwerkzeuge für die Fritz!Box besorgt, am Beispiel der Z-Shell.

Die Fritz!Boxen des Herstellers AVM gelten als die besseren unter den Consumer-Routern. Man kann sie bei Bedarf umfangreich mithilfe von Freetz aufrüsten, doch wollte ich so weit nun auch wieder nicht gehen. Mir ging es mehr darum, einfach ein bisschen mit dem Gerät herumzuspielen. Das heißt konkret: Programme dafür kompilieren und auf der Fritz!Box ausführen können.

Die Fritz!Box selbst hat keinen gewöhnlichen x86-Prozessor und besitzt weder einen C-Compiler noch eine dynamisch linkbare libc (C runtime library). Das bedeutet, dass, wenn man nicht ein ganzes System kompilieren möchte, alle Programme, die auf dem Gerät ausgeführt werden sollen, speziell für die von ihr benutzte Prozessorarchitektur gebaut und zudem statisch gelinkt werden müssen. Ohne Compiler ist das freilich generell etwas schwierig. Was also tun?

Die Lösung stellt ein sogenannter Cross-Compiler da. Das ist ein Compiler, der im Gegensatz zu einem gewöhnlichen (nativen) Compiler Programme nicht für die Plattform baut, auf der er läuft, sondern für eine andere, in diesem Falle eben für die Fritz!Box. Es ist grundsätzlich möglich, sich den Quellcode des GCC herunterzuladen und sich einen entsprechenden Compiler zu bauen, jedoch erweist sich dies als außerordentlich kompliziert. Zunächst einmal benötigt der GCC selbst die GNU binutils. Das allein ist noch kein schwerwiegendes Hindernis, da die binutils keine weiteren Abhängigkeiten besitzen (und der GCC auch nicht). Jedoch ist der sich ergebene Cross-Compiler fast funktionslos, da ein elementarer Teil fehlt: Die libc. Hier liegt das eigentliche Problem: Es gibt zahlreiche Implementationen der libc, zwischen denen man wählen kann, darunter auch die GNU C libary (glibc). Die glibc ist jedoch recht schwierig zu kompilieren, funktioniert nur in gewissen Kombinationen von GCC- und binutils-Versionen und ist mit Hinblick auf embedded-Systeme auch nicht gerade schmal. Auch andere libc-Implementationen sind nicht einfach zu kompilieren, rasch gerät man an Kompatibilitätsprobleme der Bestandteile unter sich oder mit dem Hostsystem (typischer Fall unter Arch Linux: autoconf zu neu, Kompilierung abgebrochen wegen nicht mehr vorhandener Features1. Daher ist es sinnvoll, diese sog. Toolchain (der Dreiklang aus Binutils, Compiler und libc) von einem spezialisierten Programm erstellen zu lassen.

Ich habe dafür zunächst crosstool-ng benutzt, es stellte sich jedoch heraus, dass das Programm nur noch schlecht gewartet wird und Probleme mit den neuen neuen Programmversionen (insbesondere von autoconf) unter Arch Linux hat. Aus meinen Versuchen mit Arch Linux auf meinem alten ARCHOS-G9-Tablet hatte ich aber noch Erfahrungen mit Buildroot, der Embedded-Entwicklungsumgebung des µClibc-Teams. Diese kann auch dazu genutzt werden, lediglich die Toolchain für die Kreuzkompilation zu erstellen, ohne auch Kernel und Dateisystem zu erstellen. Die Entwicklungsumgebung wird aktiv weiterentwickelt und funktionierte in meinen Tests ganz hervorragend.

Ans Werk

Zunächst einmal galt es festzustellen, welche Prozessorarchitektur die Fritz!Box denn nun eigentlich hat, denn dieses Wissen ist zentral für die Erstellung eines Cross-Compilers. Dafür ist zunächst Shellzugriff auf die Fritz!Box erforderlich. Fritz!Boxen unterstützen dazu einen Telnet-Zugang, der mit demselben Passwort wie das Webinterface gesichert ist, jedoch zunächst einmal aktiviert werden muss, in meinem Falle per Telefoncode #96*7*. Einloggen und mit uname -a das System herausfinden:

$ telnet fritz.box
Trying xxx...
Connected to fritz.box.
Escape character is '^]'.
password: 


BusyBox v1.19.3 (2012-08-08 14:58:13 CEST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

ermittle die aktuelle TTY
tty is "/dev/pts/0"
Console Ausgaben auf dieses Terminal umgelenkt
# uname -a
Linux fritz.fonwlan.box 2.6.32.60 #3 SMP Wed Dec 4 17:03:03 CET 2013 mips GNU/Linux

Aha. Es handelt sich um einen MIPS-Prozessor. Was man in der Ausgabe hier leider nicht sieht (und was ich durch einen falschen Crosscompiler erst habe lernen müssen), ist, dass MIPS-Prozessoren sowohl als Little- oder als BigEndian ausgeführt werden können (das ist die Bytereihenfolge im Speicher). Bei meiner Fritz!Box handelt es sich um eine BigEndian-MIPS-Architektur. Es scheint, als ob LittleEndian-MIPS-Porzessoren wohl unter dem Kürzel mipsel laufen, aber mangels Testhardware ist mir nicht bekannt, ob uname das dann auch korrekt ausgibt.

Bevor wir dieses Wissen nun für die Konfiguration von Buildroot nutzen, sollte die Fritz!Box noch etwas weiter vorbereitet werden. Die Dateisystemhierarchie ist bis auf /var größtenteils als readonly eingehangen, außerdem ist nur sehr wenig Speicherplatz verfügbar (wenige MiB). Viele Fritz!Boxen besitzen jedoch einen USB-Anschluss, und es zeigt sich, dass zumindest mein Exemplar auch in der Lage ist, mit ext4-Dateisystemen umzugehen. Ein dort eingesteckter USB-Stick mit ext4-Dateisystem wird regulär als /dev/sda1 erkannt und kann an eine entsprechende Stelle gemounted werden:

# mkdir /var/extern
# mount /dev/sda1 /var/extern

Somit sind nun unter /var/extern mehrere GiB Speicherplatz verfügbar. Perfekt für weitere Programme.

Die Telnet-Sitzung kann jetzt mit

# exit

beendet werden. Zurück auf dem Kompilationssystem wird nun mit dem Bau von Buildroot begonnen:

$ cd ~/Downloads
$ wget http://buildroot.uclibc.org/downloads/buildroot-2014.05.tar.bz2
$ tar -xvjf buildroot-*.tar.bz2
$ cd buildroot-*
$ make menuconfig

make menuconfig bringt ein hübsches Konfigurationsmenü hervor, in dem man alle relevanten Optionen einstellen kann. Insbesondere ist dabei darauf zu achten als Zielarchitektur mips in der 32-Bit-Variante zu wählen. Wer zu faul ist zum Lesen, findet hier meine generierte Konfiguration — einfach in .config (mit führendem Punkt!) umbenennen und in das Buildroot-Verzeichnis legen. Achtung! Diese Konfiguration ist für Buildroot 2014.05! Andere Versionen müssen nicht notwendigerweise funktionieren.

Nun Buildroot anweisen, die Toolchain (und nur die) zu bauen:

$ make toolchain

Das dauert relativ lange, das Ergebnis befindet sich dann im Ordner output/host. Daher:

$ export PATH=$HOME/Downloads/buildroot-2014.05/output/host/usr/bin:$PATH
$ mips-buildroot-linux-uclibc-gcc --version
mips-buildroot-linux-uclibc-gcc (Buildroot 2014.05) 4.7.3
Copyright (C) 2012 Free Software Foundation, Inc.
Dies ist freie Software; die Kopierbedingungen stehen in den Quellen. Es
gibt KEINE Garantie; auch nicht für MARKTGÄNGIGKEIT oder FÜR SPEZIELLE ZWECKE.

Fertig ist der Cross-Compiler. Als libc kommt hier die µClibc zum Einsatz.

Ein Programm bauen

Die Fritz!Box-Shell ist doch sehr limitiert, daher habe ich mich entschieden, sie aufzurüsten. Und zwar mit der Z-Shell. Die hat genau eine Abhängigkeit, und das ist ncurses, welches keine weiteren Abhängigkeiten hat. Daher:

$ cd ~/Downloads
$ wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
$ tar -xvzf ncurses-*.tar.xz
$ cd ncurses-5.9
$ ./configure --prefix=/home/quintus/mips --host=mips-buildroot-linux-uclibc --disable-shared --enable-static
$ make -j4
$ make install

Wichtig ist hier vor allem die --host-Option, die das Zielsystem angibt und Kreuzkompilation verlangt. --enable-static stellt sicher, dass eine statische Library erzeugt wird (weil wir nicht erst noch dynamische Libs auf der Box installieren wollen), --disable-shared deaktiviert die von uns nicht benötigten dynamischen Libraries.

Unter /home/quintus/mips ensteht damit die übliche UNIX-Verzeichnisstruktur aus bin, lib, etc. Diese Verzeichnisse müssen dem Compiler für die weitere Kompilation der Z-Shell bekannt sein. Zusätzlich soll die vollständige statische Kompilierung erreicht werden, d.h. auch die libc soll statisch einkompiliert werden. Dies lässt sich durch die Schalter -static (normale Libs) und -static-libgcc (statische libc) erreichen:

$ export PATH=$home/mips/bin:$PATH
$ export CFLAGS="-I$HOME/mips/include -static -static-libgcc "
$ export LDFLAGS="-L$HOME/mips/lib -static -static-libgcc "

Weiter geht’s mit der Z-Shell:

$ wget http://sourceforge.net/projects/zsh/files/zsh/5.0.5/zsh-5.0.5.tar.bz2/download -O zsh-5.0.5.tar.bz2
$ tar -xvjf zsh-*.tar.bz2
$ cd zsh-5.0.5
$ ./configure --host=mips-buildroot-linux-uclibc --prefix=/var/extern --enable-etcdir=/var/extern/etc/zsh --disable-dynamic --disable-shared --enable-static
$ make -j4
$ make install DESTDIR=/home/quintus/mips/zsh

Interessant sind hier mehrere Dinge. Neben den schon für ncurses erwähnten Optionen tritt hier ein anderes Präfix auf. Wie zu Anfang des Artikels beschrieben, besitzt die Fritz!Box nur wenig Speicherplatz, weshalb ich empfehle, auf /var/extern o.ä. einen USB-Stick einzuhängen. Dorthin soll die Z-Shell später einmal installiert werden. --enable-etcdir legt das globale Konfigurationsverzeichnis für zshenv, zshrc usw. fest; auch dieses soll sich auf dem USB-Stick befinden. --disable-dynamic sorgt dafür, dass alle Module statisch in die Z-Shell einkompiliert werden anstatt dynamisch nachgeladen zu werden (wobei das wohl nicht zwingend erforderlich ist). DESTDIR bei make install ist erforderlich, weil die Z-Shell ansonsten auf dem kompilierenden System nach /var/extern installiert werden würde, was natürlich nicht gewünscht ist. So wird stattdessen die Dateisystemwurzel für die Installation gewissermaßen „verschoben“. Unterhalb von /home/quintus/mips/zsh befinden sich daher nunmehr var/extern und dessen Unterverzeichnisse. Die Fritz!Box besitzt ein minimales tar, das mit nichtkomprimierten Archiven umgehen kann, sodass zur Übertragung einfach paketiert werden kann:

$ cd /home/quintus/mips/zsh/var/extern
$ tar -cvf zsh.tar *

Übertragung und Konfiguration

Die resultierende Archivdatei zsh.tar kann jetzt auf die Fritz!Box übertragen werden. Dazu kann man sich des guten, alten Netcats bedienen. Zunächst ein zweites Terminal starten und dort wieder per Telnet auf der Fritz!Box einloggen. Nach dem Login auf den USB-Stick wechseln und den Dateiempfang vorbereiten:

# cd /var/extern
# nc -l -p 9995 > zsh.tar  # Gibt nicht zurück, sondern wartet auf den Empfang

Auf dem Kompilationssystem nun den Übertragungsvorgang starten:

$ cat zsh.tar | nc fritz.box 9995

Nach wenigen Sekunden gibt das nc in der telnet-Sitzung zurück. Wieder zum betreffenden Terminal wechseln und die letzten Einrichtungsschritte vornehmen:

# tar xf zsh.tar
# mkdir -p etc/zsh
# vi etc/zsh/zshenv
# vi etc/zsh/zshrc

In der zshenv muss der PATH eingetragen werden:

# /var/extern/etc/zsh/zshenv
# Z-Shell config for ALL YOUR SHELLS

export PATH=/sbin:/bin:/usr/bin:/usr/sbin:/var/extern/bin:/var/extern/usr/bin:/var/extern/sbin:/var/extern/usr/sbin

Die Aufnahme der Pfade unter /var/extern ermöglicht das spätere Hinzufügen weiterer Programme (und macht die zsh-Executable für die Z-Shell selbst auffindbar; diese liegt nämlich jetzt in /var/extern/bin/zsh). Die zshrc sollte vor allem eines enthalten: eine sinnvolle Definition von PS1, daneben Inputmappings, um die Steuertasten (Pfeiltasten, Backspace, Entf, etc.) richtig zu verstehen:

# /var/extern/etc/zsh/zshrc
# Z-Shell config for your INTERACTIVE shells

# Shell prompt definition
PS1=$'[%n@%m] %~ %# '

# Keybindings
bindkey "\e[1~" beginning-of-line # Home
bindkey "\e[4~" end-of-line # End
bindkey "\e[5~" beginning-of-history # PageUp
bindkey "\e[6~" end-of-history # PageDown
bindkey "\e[2~" quoted-insert # Ins
bindkey "\e[3~" delete-char # Del
bindkey "\e[1;5C" forward-word # Ctrl+Right
bindkey "\eOc" emacs-forward-word
bindkey "\e[1;5D" backward-word # Ctrl-Left
bindkey "\eOd" emacs-backward-word
bindkey "\e\e[C" forward-word
bindkey "\e\e[D" backward-word
bindkey "\e[Z" reverse-menu-complete # Shift+Tab
# for rxvt
bindkey "\e[7~" beginning-of-line # Home
bindkey "\e[8~" end-of-line # End
# for non RH/Debian xterm, can't hurt for RH/Debian xterm
bindkey "\eOH" beginning-of-line
bindkey "\eOF" end-of-line
# for freebsd console
bindkey "\e[H" beginning-of-line
bindkey "\e[F" end-of-line

Jetzt kann die Z-Shell gestartet werden:

# exec /var/extern/bin/zsh --login
[root@fritz] /var/extern # 

Das ist auch schon fast praktikabel, hat allerdings noch eine massive Einschränkung: Die Fritz!Box enthält keine Terminfo-Dateien. Diese benötigt jedoch ncurses, um die Kontrolltastendrücke korrekt zu verarbeiten. Für jedes Terminal gibt es dabei eine eigene Terminfo-Datei, die auf dem Kompilationssystem unter /usr/share/terminfo in einem der alphabetisch sortierten Verzeichnisse zu finden ist, für mein Terminal (urxvt) etwa die zwei Dateien /usr/share/terminfo/r/rxvt-unicode und /usr/share/terminfo/r/rxvt-unicode-256color (für xterm u.a. sind natürlich andere Dateien einschlägig — dies lässt sich leicht mit echo $TERM in einem neuen Terminalfenster herausfinden). Diese werden nach derselben Netcat-Prozedur wie gerade auf die Fritz!Box kopiert und dort unter /var/extern/usr/share/terminfo/r/rxvt-unicode und /var/extern/usr/share/terminfo/r/rxvt-unicode-256-color oder für andere Terminals entsprechend abgelegt; die erforderlichen Verzeichnisse dabei selbstverständlich mit mkdir erzeugen. Danach (sofern noch nicht geschehen) die Z-Shell verlassen. Neu mit telnet anmelden und die Z-Shell dieses mal so starten (TERM natürlich hier entsprechend des benutzten Terminal-Emulators setzen):

# export TERMINFO=/var/extern/usr/share/terminfo
# export TERM=rxvt-unicode-256color
# exec /var/extern/bin/zsh --login

Fertig. Nun besitzt man eine funktionable Z-Shell auf der Fritz!Box und kann mit allen weiteren Programme, die man schon immer dort laufen lassen wollte, genauso verfahren.

Valete.

  1. Unschön: Installiert man eine ältere Version von autoconf, beschwert sich die nächste Library, dass autoconf zu alt sei und neuere Features benötigt werden.