Aus Linux-Magazin 09/2006

Technologiestudie: Das Firewall-Modul Singwall meldet eintreffende Pakete akustisch

© photocase.com

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.

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.

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.

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]

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben