Horch, was kommt von draußen rein: Eine Firewall, die losträllert, sobald Pakete eintreffen, unterrichtet den lauschenden Admin über die Lage im Netz. Damit das Spiel nicht monoton ausfällt, dirigiert die Portnummer die Tonhöhe. Diese Virtuosität gelingt mit Netfilter-Hooks und dem passenden Modul.
Ambitionierte Surfer wollen ebenso wissen wie jeder Admin, was so von ihrem Rechner ins Netz fließt und was von dort eintrudelt. Sichtbar machen diesen Netzwerkverkehr zahlreiche Programme wie Tcpdump oder Ethereal. Sie beobachten die Netzwerkschnittstelle und analysieren die Paketdaten.
Gut informiert
Auch Firewalls informieren über die strömenden Datenpakete per Logmeldung. Physiologisch und psychologisch ist das Auge während des Surfens aber mehr am Inhalt einer Webseite oder den Fotos der letzten Firmenfeier interessiert als am Betrachten irgendwelcher Logdateien. Die Infos verschwinden in der Regel ungesehen.
Dabei hat der Mensch mehrere Sinne: Er könnte den Netzwerkverkehr parallel wahrnehmen, wenn er ihn in Echtzeit hören würde – eine Art akustischer (oder singender) Netzmonitor. Es gibt mehrere Möglichkeiten, dies zu realisieren. Ein eleganter Auftakt sind die Netfilter-Hooks, im Folgenden anhand des Moduls »singwall« vorgestellt.
Das Netfilter-Konzept ermöglicht es, Kernelerweiterungen zu schreiben, die sich in die Bearbeitungskette von Netzwerkpaketen über so genannte Hooks (Haken) einklinken. Dabei ist kein Kernelpatch nötig, ein ladbares Modul genügt. Das ist perfekt für Firewalls und andere paketbasierte Anwendungen.
Eingeklinkt
Die erste Aufgabe des Kernelmoduls besteht darin, einen Hook zu registrieren. Der Kernel weiß dann, wofür sich dieses Modul interessiert. Dazu gibt es die beiden nahe liegend benannten Funktionen »nf_register_hook(struct nc_hook_ops*)« und »nf_unregister_hook(struct nc_hook_ops*)«. Letztere klinkt den Hook wieder aus. Das Entladen des Moduls ist wichtig, da es sonst zu sehr unschönen Kernelfehlern kommt. Kristallisationspunkt beim Registrieren des Hook ist die Struktur »nc_hook_ops«:
struct nf_hook_ops {
struct list_head list;
nf_hookfn *hook;
struct module *owner;
int pf;
int hooknum;
int priority;
};
Sie enthält alle für den Kernel relevanten Information zum Einhängen des Hook. Der erste Eintrag dient der Verwaltung in einer verketteten Liste. Die Hook-Funktion selbst ist in »hook« als Zeiger abgelegt. Über diesen Funktionszeiger ruft der Kernel später das Kernelmodul auf. Der »owner«-Eintrag speichert das zugehörige Modul, er findet sich bei zahlreichen weiteren Strukturen im Kernel.
Protokollwahl
Interessant sind besonders die letzten drei Einträge: »pf« bezeichnet die Protokollfamilie (OSI-Netzwerkschicht), für die sich der Hook interessiert. In »linux/socket.h« steht eine Übersicht möglicher »pf«-Werte. Für IPv4 ist »PF_INET« reserviert, den das Singwall-Modul auch verwendet. Die »hooknum« bestimmt, ab welcher Stelle sich das Modul einklinkt. Die Hooks sind durchnummeriert und per Makro mit Namen versehen. Für IPv4 finden sich die möglichen Werte in »linux/netfilter_ipv4.h«.
Die singende Firewall soll bei ein- und ausgehenden Paketen Geräusche je nach Pakettyp produzieren. Zwei Hooks – »hooknum=NF_IP_LOCAL_OUT« für ausgehende und »hooknum=NF_IP_LOCAL_IN« für eingehende Pakete – sind in der Lage, diese Aufgabe zu erledigen. Der letzte Eintrag der »nf_hook_ops«-Struktur legt schließlich fest, mit welcher Priorität der Kernel den neuen in die Liste der bereits vorhandenen Hooks einreiht. Netfilter arbeitet die Hooks nach ihrer Priorität ab. Die »priority«-Werte für IPv4 sind ebenfalls in »linux/netfilter_ipv4.h« aufgelistet. Für die Singwall ist dabei der Wert »priority=NF_IP_PRI_FIRST« möglich, damit kommt der Hook als Erster an die Reihe.
Ein Hook ohne Haken
Der Prototyp eines Hook, genauer der Hook-Funktion, ist in »linux/netfilter.h« definiert:
typedef unsigned int nf_hookfn( unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *) );
Das wichtige und für Singwall einzig relevante Argument steht an zweiter Stelle: »skb«. Es handelt sich um einen Zeiger auf eine »sk_buff«-Struktur (Socket Buffer). Mit dieser zentralen Netzwerkdatenstruktur überträgt der Kernel effizient Daten zwischen einzelnen Netzwerkschichten.
Drum prüfe …
Die für die singende Firewall anstehende Paketanalyse ist komplett mit Hilfe des Socketpuffers möglich. Die sehr umfangreiche »sk_buff«-Struktur ist in »linux/skbuff.h« hinterlegt. Sie verweist über den Zeiger »data« auf die Netzwerkdaten und enthält darüber hinaus viele Verwaltungsinformationen. Im Fall von TCP-Daten entnimmt die Firewall den Netzwerkdaten zunächst den TCP-Header. Das versetzt sie in die Lage, die Portnummer zu extrahieren.
Wichtig sind die Rückgabewerte des Hook. Sie regeln, was mit den von ihm verarbeiteten Paket im Weiteren geschieht. Alle Rückgabewerte sind in »linux/netfilter.h« verzeichnet, beispielsweise »NF_DROP« und »NF_ACCEPT«. Ersteres verwirft das gerade bearbeitete Paket, während »NF_ACCEPT« es ungehindert passieren lässt. Damit ist es sehr leicht möglich, eine minimale Firewall zu schreiben. Passt dem Filter das gerade bearbeitete Paket nicht, retourniert der Hook »NF_DROP«, der Kernel kümmert sich um den Rest. Die singende Firewall gibt stets »NF_ACCEPT« zurück, da sie Netzwerkdaten nur analysiert, aber nichts filtert.
Aufgaben
Als Ouvertüre soll die Singwall zunächst UDP-, TCP- und ICMP-Pakete durch einen einleitenden Ton unterschiedlicher Frequenz kennzeichnen. Bei TCP soll ein zweiter Ton den Port (und damit den Dienst) verraten. Hierfür ist die bereits angesprochene Analyse des TCP-Headers notwendig.
Die »tcphdr«-Struktur speichert die Daten des Headers. Für die Portnummer greift Singwall auf das »dest«-Feld zurück. Sie muss den Wert zunächst in die Rechner-Byteordnung konvertieren. Dafür bedient sie sich bei der »ntohs(unsigned short int netshort)«-Funktion, sie liefert die Portnummer als Integerwert zurück. Der gesamte Ablauf der Paketanalyse ist in Abbildung 1 dargestellt.

Abbildung 1: Der Kernel übergibt per Netfilter-Hook die eintreffenden Pakete an Singwall. Das Modul analysiert das Protokoll, berechnet daraus eine Frequenz und schreibt das Resultat in einen Ringpuffer.
Ein technisch-menschliches Problem behindert allerdings die geradlinige Implementation: Der Netzverkehr ist möglicherweise viel zu schnell, als dass das Ohr noch einzelne Piepser wahrnehmen könnte. Zur gezielten Entschleunigung implementiert Singwall einen Ringpuffer der Länge »RING_SIZE«. Die Hook-Funktion schreibt in diesen Puffer die abzuspielenden Frequenzen.
Zwei Zähler speichern die Position des als Nächstes erklingenden Tons (»tone_counter«) und die Position des nächsten freien Eintrags (»tone_pos_counter«). Kommen die Netzwerkpakete so schnell, dass sich »tone_pos_counter« und »tone_counter« mehr als »RANGE_SYNC« voneinander entfernen, führt der Code einen Resync durch: Er setzt »tone_pos_counter« auf »tone_counter« zurück. Der Ringpuffer ist in Abbildung 2a veranschaulicht. Das Verfahren übergeht bei hohen Datenraten zwar viele Pakete, in der Praxis sind alle Verbindungen aber bestens zu hören.

Abbildung 2a: Die Hook-Funktion legt die zu spielenden Frequenzen in einen Ringpuffer. Wird der Puffer zu voll, wirft ein Resync einige Daten raus.
Die vorgestellte Implementation der singenden Firewall konnte normalem Surfen problemlos folgen. Lediglich ein »ping 127.0.0.1 -f« oder massive Downloads zwingen sie zu mehrmaligem Resynchronisieren, das tut dem audiophilen Spaß aber keinen Abbruch.
Den Ton erzeugt Singwall über den PC-Speaker. Entsprechenden Code hat die Kerntechnik-Reihe [1] im Linux-Magazin bereits vorgestellt. Er lässt den Lautsprecher in der jeweils gewünschten Frequenz losträllern (gelegentlich bereitet neuere Hardware allerdings Probleme und der Speaker bleibt stumm). Die Tonlänge ist auf 20 Millisekunden festgelegt – das ist ein Kompromiss zwischen guter Hörbarkeit und kurzer Abarbeitungszeit, die erforderlich ist, um möglichst wenige Pakete zu verpassen.
Die Firewall darf keinesfalls während der 20 Millisekunden Tonausgabe den Netzverkehr lähmen. Für die nötige Parallelität sorgt ein eigener Kernelthread, der den Ringpuffer mit den einzelnen Frequenzen der Reihe nach abarbeitet. Der Threadcode lehnt sich an die Implementation in [2] an. Er besteht aus einer »while()«-Schleife, die im Abstand von 10 Millisekunden läuft. Diese Technologiestudie verzichtet auf Locking zwischen den parallelen Aktionen – schlimmstenfalls verschluckt die Firewall ein paar Tönchen oder stottert kurz, was musikalisch nicht weiter tragisch ist.
Erst beim Entladen des Moduls endet der Thread und damit auch die Endlosschleife. Die Zusammenarbeit von Hook-Funktion und Threads am Ringpuffer ist in Abbildung 2b zu erkennen.

Abbildung 2b: Während die Hook-Funktion Einträge in den Ringpuffer füllt, liest ein paralleler Kernelthread die Daten und spielt sie auf dem PC-Lautsprecher.
Musik marsch!
Die Theorie ist in Listing 1 umgesetzt. Seit der Kernelversion 2.6.16 braucht das Modul zwei Include-Dateien. Der If-Block in den Zeilen 11 bis 14 erledigt das. Danach definiert das Modul die Werte für das Ringpuffer-Management: »RING_SIZE« und »SYNC_RANGE«. Diese Werte sind frei anpassbar.
|
Listing 1: Singwall |
|---|
001 #include <linux/version.h>
002 #include <linux/module.h>
003 #include <linux/kernel.h>
004 #include <linux/netfilter.h>
005 #include <linux/netfilter_ipv4.h>
006 #include <linux/init.h>
007 #include <linux/tcp.h>
008 #include <asm/io.h>
009 #include <linux/inet.h>
010
011 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
012 #include <linux/in.h>
013 #include <linux/ip.h>
014 #endif
015
016 #define RING_SIZE 500
017 #define SYNC_RANGE 100
018 MODULE_LICENSE("GPL");
019
020 static wait_queue_head_t wq;
021 static int thread_id;
022 static DECLARE_COMPLETION(on_exit);
023
024 static struct nf_hook_ops nfho_out,nfho_in;
025
026 u16 tone[RING_SIZE];
027 int tone_counter;
028 int tone_pos_counter;
029
030 void update_tone_pos_counter(void) {
031 if(tone_pos_counter<RING_SIZE-1) tone_pos_counter++;
032 else tone_pos_counter=0;
033 }
034
035 void update_tone_counter(void) {
036 if(tone_counter<RING_SIZE-1) tone_counter++;
037 else tone_counter=0;
038 }
039
040 void check_syncing(void) {
041 int counter;
042 if ((abs((tone_pos_counter - tone_counter+RING_SIZE)
043 % RING_SIZE))>SYNC_RANGE)
044 {
045 counter=tone_counter;
046
047 while (((abs((counter - tone_counter+RING_SIZE)
048 % RING_SIZE))<=SYNC_RANGE))
049 {
050 tone[(counter%RING_SIZE)-1]=0;
051 counter++;
052 pr_debug(" %d ",(counter%RING_SIZE)-1);
053 }
054 pr_debug("SingingFirewall: Resyncing %d<---%d!n",
055 tone_counter,tone_pos_counter);
056 tone_pos_counter=tone_counter;
057 }
058 }
059
060 static int thread_code(void *data) {
[...]
092 }
093
094 unsigned int hook_func(
095 unsigned int hooknum,
096 struct sk_buff **skb,
097 const struct net_device *in,
098 const struct net_device *out,
099 int (*okfn)(struct sk_buff *))
100 {
101 struct tcphdr *thead;
102 struct sk_buff *sk=*skb;
103 u16 port;
104 char check;
105
106 check=0;
107 if (sk->nh.iph->protocol == IPPROTO_TCP)
108 {tone[tone_pos_counter]=100; check=1;}
109 if (sk->nh.iph->protocol == IPPROTO_UDP)
110 {tone[tone_pos_counter]=200; check=1;}
111 if (sk->nh.iph->protocol == IPPROTO_ICMP)
112 {tone[tone_pos_counter]=300; check=1;}
113 if (!check) return NF_ACCEPT;
114
115 update_tone_pos_counter();
116 check_syncing();
117 pr_debug("Tone Pos Counter: %dn",tone_pos_counter);
118
119 check=0;
120 if (sk->nh.iph->protocol == IPPROTO_TCP) {
121 thead=(struct tcphdr *) (sk->data + (sk->nh.iph->ihl * 4));
122 port=ntohs(thead->dest);
123 switch( port )
124 {
125 case 21 : {tone[tone_pos_counter]=1000;check=1;break;}
126 case 22 : {tone[tone_pos_counter]=1500;check=1;break;}
127 case 80 : {tone[tone_pos_counter]=2000;check=1;break;}
128 case 443: {tone[tone_pos_counter]=2500;check=1;break;}
129 }
130
131 if (check) {
132 update_tone_pos_counter();
133 check_syncing();
134 pr_debug("Tone Pos Counter: %dn",tone_pos_counter);
135 }
136 }
137 return NF_ACCEPT;
138 }
139
140 int init_module() {
141 int counter;
142 for (counter=0; counter<=RING_SIZE-1; counter++)
143 tone[counter]=0;
144 tone_counter=0;
145 tone_pos_counter=0;
146
147 init_waitqueue_head(&wq);
148 thread_id=kernel_thread(thread_code, NULL, CLONE_KERNEL);
149 if(thread_id==0) return -EIO;
150
151 nfho_out.hook = hook_func;
152 nfho_out.hooknum = NF_IP_LOCAL_OUT;
153 nfho_out.pf = PF_INET;
154 nfho_out.priority = NF_IP_PRI_FIRST;
155
156 nfho_in.hook = hook_func;
157 nfho_in.hooknum = NF_IP_LOCAL_IN;
158 nfho_in.pf = PF_INET;
159 nfho_in.priority = NF_IP_PRI_FIRST;
160
161 nf_register_hook(&nfho_out);
162 nf_register_hook(&nfho_in);
163 return 0;
164 }
165
166 void cleanup_module() {
167 if(thread_id) kill_proc(thread_id, SIGTERM, 1);
168 wait_for_completion(&on_exit);
169 nf_unregister_hook(&nfho_in);
170 nf_unregister_hook(&nfho_out);
171 outb(inb_p(0x61) & 0xFC, 0x61);
172 }
|
Falls beim Compiler-Aufruf das Makro »DEBUG« definiert ist, gibt das System noch Text in »/var/log/messages« aus. Achtung: Das Modul protokolliert auch die Werte für »tone_counter« und »tone_pos_counter« und erzeugt so sehr viele Meldungen in der Logdatei. Fürs Debugging sind diese Daten wertvoll: Sie zeigen, wie das Modul die Zähler des Ringpuffers synchronisiert, falls der Netzwerkverkehr zu flott eintrifft.
Die Init-Routine des Moduls (ab Zeile 140) setzt zunächst die beiden Zähler des Ringpuffers auf null. Anschließend startet es den Thread inklusive Warteschlange (Zeilen 147 bis 149). Am Ende findet sich die Registrierung der Hooks für die ein- und ausgehenden Pakete und IPv4. Die Cleanup-Routine ab Zeile 166 macht diese Schritte wieder rückgängig, sodass das Modul problemlos aus dem Kernel verschwindet. Sicherheitshalber befiehlt der Aufruf in Zeile 171 noch dem PC-Speaker sich abzuschalten.
Protokollprüfer
Die Hook-Funktion (Zeilen 94 bis 138) prüft das Protokoll anhand des Eintrags »sk->nh.iph->protocol« im eingereichten Socketpuffer (Zeilen 107 bis 112). Erkennt Singwall ein Protokoll, setzt es eine Frequenz auf den Ringpuffer (Zeile 115). Handelt es sich um ein TCP-Paket, ergänzen die Zeilen 120 bis 136 eine zweite Frequenz, die der Portnummer entspricht. Wer will, fügt in die Portliste nach Belieben weitere Dienste ein.
Das Aktualisieren der Zähler übernehmen die Funktionen »update_tone_pos_counter()« (Zeile 30) und »update_tone_counter()« (Zeile 35). Die Synchronisation beider Zeiger erledigt die Funktion »check_syncing()« (Zeile 40). Dass sich darum eine eigene Funktion kümmert, ist zwar nicht besonders elegant, funktioniert aber sehr robust und trennt die Aufgaben klar.
Mit dem Makefile aus Listing 2 ist das Modul schnell übersetzt. Ein »insmod singwall.ko« als Root lädt es anschließend in den Kernel. Dort nimmt die singende Firewall dann unmittelbar ihren musikalischen Dienst auf und versetzt den PC-Lautsprecher in Schwingungen, die mit der Netzwerklast harmonieren. Deutlich zu unterscheiden sind beispielsweise die verschlüsselten und die im Klartext ausgelieferten Webseiten. Der Ton bei HTTPS-Verbindungen klingt auch fürs ungeschulte Ohr höher als bei gewöhnlichen unverschlüsselten Websites. Auch SSH- und FTP-Pakete unterscheiden sich gut hörbar.
|
Listing 2: Makefile |
|---|
01 #Debug-Meldungen? 02 #EXTRA_CFLAGS+=-DDEBUG 03 04 ifneq ($(KERNELRELEASE),) 05 obj-m := singwall.o 06 else 07 KDIR := /lib/modules/$(shell uname -r)/build 08 PWD := $(shell pwd) 09 10 all: 11 $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules 12 endif |
Wer im Makefile die Zeile 2 auskommentiert, übersetzt das Modul im Debugging-Modus. Er handelt sich damit tausende Zeilen in der Logdatei ein – hilfreich bei der Fehlersuche, im Regelbetrieb eher lästig. Sollte der Krach aus dem PC-Speaker wider Erwarten stören, sorgt »rmmod singwall« für Stille. (fjl)
|
Infos |
|---|
|
[1] Eva-Katharina Kunst, Jürgen Quade, “Kerntechnik 3”: Linux-Magazin 10/03, S. 81 [2] Eva-Katharina Kunst, Jürgen Quade, “Kerntechnik 4”: Linux-Magazin 11/03, S. 96 [3] Netfiter: [http://www.netfilter.org] [4] Downloads: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2006/09/Singwall] |





