Es gibt viele Gründe, den Fluss der Datenpakete durch ein Computersystem zu manipulieren. NFQueue reiht Pakete in eine Warteschlange ein und erlaubt es Userspace-Programmen, diese weiterzuverarbeiten. Das erleichtert die individuelle Paketsteuerung.
Administratoren, denen die Standardfunktionen von Netfilter [1] für den Systembetrieb nicht ausreichen, können eigene Filterprogramme entwerfen, die im Userspace laufen. Das macht eine Vielzahl von Eingriffen in den Netzwerk-Traffic möglich. Der Kernel reicht dann Pakete, die seinen Netzwerk-Stack durchlaufen, an externe Anwendungen im Userspace weiter. Traditionell war hierfür das Queue-Sprungziel von IPtables zuständig, seit Version 2.6.14 bietet der Kernel jedoch ein zusätzliches Sprungziel namens NFQueue, das mehr Flexibilität erlaubt, weil hier mehr als eine Warteschlange möglich ist.
Anwendungsbeispiele
Diese Technologie verwendet unter anderem das Projekt Moblock: Die Software lädt aus dem Internet eine Datenbank mit IP-Adressen herunter und filtert darüber IP-Pakete [2]. Auch das Steganographie-Tool Stegan RTP [3] nutzt die Möglichkeit, Pakete im Userspace zu verändern. Es verändert die via Realtime Transfer Protocol (RTP) übertragenen Sprachsignale in Voice-over-IP-Telefonaten so, dass sie zum Träger für verborgene Nachrichten werden.
IP-Queue-Nachfolger
Das Queue-Sprungziel gibt es seit Kernelversion 2.3. NFQueue erweitert Queue um die Möglichkeit, mehrere Warteschlangen zu verwalten. Während die Queue-Implementation (»ip_queue«) nur eine Warteschlange bereitstellt, kennzeichnet bei NFQueue eine 16-Bit-Zahl die gewünschte Warteschlange. Es sind also bis zu 65536 unterschiedliche Filterregeln mit separaten Queues möglich. Wie im IPtables-Tutorial beschrieben, verweist das Queue-Sprungziel in aktuellen Kernelversionen auf die erste der von NFQueue verwalteten Warteschlangen (Queue 0) [4].
Voraussetzungen
Um die Kernel-Warteschlangen im Userspace zu nutzen, muss der Administrator im Kernelquellcode die Optionen »NETFILTER_NETLINK_QUEUE« und »NETFILTER_XT_TARGET_NFQUEUE« aktivieren. Alle relevanten Punkte sammelt das Kernel-Konfigurationstool (»make menuconfig«) unter »Networking support | Networking Options | Network packet filtering framework (Netfilter) | Core Netfilter Configuration« (Abbildung 1). Einige Distributionen liefern ihre Standardkernel bereits mit NFQueue-Support aus, so etwa Open Suse.
Im neuen Kernel lädt der Administrator die Module »x_tables«, »xt_NFQUEUE« und »nfnetlink_queue«. Letzteres benötigt noch »nfnetlink«, »modprobe« lädt es automatisch mit. Voraussetzung für alle drei ist das Modul »ip_tables«, das »modprobe« als IPtables-Basismodul normalerweise automatisch mit dem ersten davon abhängigen Modul aktiviert. Kernel mit einer Versionsnummer vor 2.6.16 nutzen noch nicht das Xtable-Framework für die Netfilter-Implementierung, weshalb das »xt_NFQUEUE«-Modul dort »ipt_NFQUEUE« oder bei noch älteren Kerneln »ip_queue« heißt.

Abbildung 1: Optionen in der Kernelkonfiguration (»make menuconfig«) unter dem Punkt »Core Netfilter Configuration« aktivieren NFQueue im Kernel.
Programmierung
Um nun aus dem Userspace die NFQueue-Funktionen des Netfilter-Subsystems nutzen zu können, binden Programme die beiden Bibliotheken »libnetfilter_queue.so« und »libnfnetlink.so« ein. Debian- und Ubuntu-Anwender holen diese Bibliotheken und die zugehörigen Headerdateien in ihr System, indem sie die Pakete »libnetfilter-queue-dev« und »libnfnetlink-dev« installieren: Über das Kommando
aptitude install libnetfilter-queue-dev libnfnetlink-dev
zieht Aptitude auf Debian-Systemen automatisch alle Abhängigkeiten nach, darunter auch die zuvor genannten Bibliotheken. Auch für Open Suse 11.1 gibt es bereits fertige Pakete im Netz [5] – die Dateien »libnfnetlink-devel-0.0.41-jen0.i586.rpm«, »libnfnetlink0-0.0.41-jen0.i586.rpm«, »libnetfilter_queue1-0.0.17 -jen0.i586.rpm« sowie »libnetfilter_queue -devel-0.0.17-jen0.i586.rpm« aus dem FTP-Verzeichnis installiert der Administrator mit »rpm«.
Während Debian für die veraltete Queue-Schnittstelle im Paket »iptables-dev« Manpages anbietet, die die Schnittstellen beschreiben (»man -k ipq« führt alle relevanten Seiten auf), gibt es für die NFQueue-Bibliotheksfunktionen keine entsprechende Dokumentation. Programmierer sind somit darauf angewiesen, die Headerdateien zu durchsuchen oder Beiträge von hilfsbereiten Entwicklern zu lesen, die ihre Erkenntnisse aus dem Umgang mit NFQueue dokumentiert haben. Erste Ansätze einer Beschreibung liefert etwa ein älterer Mailinglisten-Beitrag [6].
|
Tabelle 1: |
|---|
Nach der Konfiguration des Kernels und der Installation der Userspace-Pakete ist das System für die Arbeit mit den Warteschlangen bereit. Den grundlegenden Ablauf zeigt Abbildung 2: Vom Userspace aus greift ein Prozess auf die Bibliotheken zu, die als Schnittstelle zum Netfilter-Code im Kernel dienen. Dieser ist auch dafür zuständig, bestimmte Pakete in die Warteschlange einzureihen. Von dieser Warteschlange holt die »libnetfilter_queue«-Bibliothek die Pakete ab und reicht sie an das Programm im Userspace weiter.
Um auf die gewünschte Warteschlange und die darin befindlichen Netzwerkpakete zuzugreifen, arbeitet eine Anwendung, etwa die in Listing 1, nur wenige Schritte ab. Tabelle 1 beschreibt die verwendeten Funktionen aus der NFQueue-Bibliothek, Tabelle 2 erklärt die verwendeten Variablen.

Abbildung 2: Über die Bibliotheken »libnetfilter_queue« und »libnfnetlink« sowie die NFQueue-Module von Netfilter greifen Userspace-Anwendungen auf die im Kernel verwalteten Warteschlangen zu.
|
Listing 1: |
|---|
01 #include <fcntl.h>
02 #include <stdio.h>
03 #include <unistd.h>
04
05 #include <arpa/inet.h>
06 #include <libnetfilter_queue/libnetfilter_queue.h>
07 #include <linux/icmp.h>
08 #include <linux/ip.h>
09 #include <linux/netfilter.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
13 #define BUFSIZE 1024
14
15 static int
16 callback_funktion(struct nfq_q_handle *queuehandle,
17 struct nfgenmsg *nfgmsg,
18 struct nfq_data *nfqd,
19 void *data)
20 {
21 int id, plen, useable, num, ret = 0;
22 char *pkt, *icmp_data;
23 struct nfqnl_msg_packet_hdr *h;
24 struct iphdr *iph;
25 struct icmphdr *icmph;
26
27 h = nfq_get_msg_packet_hdr(nfqd);
28 if (h) {
29 id = ntohl(h->packet_id);
30 plen = nfq_get_payload(nfqd, &pkt);
31 printf("Processing packet %d ... ", id);
32
33 iph = (struct iphdr *)pkt;
34 icmph = (struct icmphdr *)(pkt + sizeof(struct iphdr));
35 icmp_data = (char *)(pkt + sizeof(struct iphdr) + sizeof(struct icmphdr));
36 if (icmph->type != 8 && icmph->code != 0)
37 ret = nfq_set_verdict(queuehandle, id, NF_ACCEPT, 0, NULL);
38
39 useable = plen - sizeof(struct iphdr) - sizeof(struct icmphdr);
40 num = read((int)data, icmp_data, useable);
41 printf("embedded %d bytesn", num);
42 ret = nfq_set_verdict(queuehandle, id, NF_ACCEPT, plen, (void *)pkt);
43 }
44
45 return ret;
46 }
47
48 int
49 main(int argc, char **argv)
50 {
51 int fd, nfqfd, num;
52 char buf[BUFSIZE];
53 struct nfq_handle *nfqh;
54 struct nfq_q_handle *queuehandle;
55
56 if (argc != 2) {
57 fprintf(stderr, "Usage: %s FILENAMEn", argv[0]);
58 return 1;
59 }
60
61 if ((fd = open(argv[1], O_RDONLY)) < 0) {
62 fprintf(stderr, "*** open()n");
63 return 1;
64 }
65
66 nfqh = nfq_open();
67 if (!nfqh) {
68 fprintf(stderr, "*** nfq_open()n");
69 return 1;
70 }
71
72 nfq_unbind_pf(nfqh, AF_INET);
73 if (nfq_bind_pf(nfqh, AF_INET) < 0) {
74 fprintf(stderr, "*** nfq_unbind_pf()n");
75 return 1;
76 }
77
78 queuehandle = nfq_create_queue(nfqh, 0, callback_funktion, (void *)fd);
79 if (!queuehandle) {
80 fprintf(stderr, "*** nfq_create_queue()n");
81 return 1;
82 }
83
84 if (nfq_set_mode(queuehandle, NFQNL_COPY_PACKET, BUFSIZE) < 0) {
85 fprintf(stderr, "*** nfq_set_mode()n");
86 return 1;
87 }
88
89 nfqfd = nfq_fd(nfqh);
90 while((num = recv(nfqfd, buf, BUFSIZE, 0)) >= 0) {
91 nfq_handle_packet(nfqh, buf, num);
92 }
93
94 nfq_destroy_queue(queuehandle);
95 nfq_close(nfqh);
96 close(fd);
97 return 0;
98 }
|
Userspace-Zugriff
Listing 1 ist ein Beispiel-C-Programm, das Datenfelder von ICMP-Echo-Request-Paketen [7] verändert, abhängig vom Inhalt einer angegebenen Datei. Diese Aufgabe ausschließlich im Kernelspace zu lösen wäre relativ schwierig – mit Iptables und NFQueue kann ein Userspace-Programm sie hingegen einfach und elegant lösen. Nicht nur C-Programme können die NFQueue-Bibliothek nutzen. Es ist genauso denkbar, die Entwicklung in einer anderen Hochsprache wie C++ oder auch mit einer interpretierten Sprache wie Perl durchzuführen. Dafür bietet sich das SWIG-Framework [8] an, es gibt aber auch Pakete für Skriptsprachen wie Perl und Python [9].
Andocken an die Warteschlange
Wenn das Beispielprogramm die Kommandozeilenparameter überprüft hat (Zeilen 56 bis 59), folgen die ersten Schritte in der Funktion »main()« (ab Zeile 48). Hinweise zu den verwendeten Funktionen gibt Tabelle 1. Wichtig ist vor allem Zeile 78, die die Callback-Funktion »callback_funktion()« an die NFQueue-Funktion »nfq_create_queue()« übergibt. Ihr viertes Argument (hier »(void *)fd«) ist optional und für Verweise auf private Daten bestimmt. Der Programmierer kann damit einen Zeiger auf Daten übergeben, die das Netfilter-Subsystem an die Callback-Funktion weiterreicht. Der Beispielcode verwendet das Argument, um ihr den lokalen Datei-Deskriptor mitzuteilen.
Die Funktion »nfq_fd()« (Zeile 89) erzeugt aus dem übergebenen Handle einen Deskriptor, den das Programm an den »recv()«-Systemaufruf übergeben kann. Er dient dazu, Daten von der Warteschlange zu lesen. Die im Schleifenrumpf aufgerufene Funktion »nfq_handle_packet()« (Zeile 91) nimmt diese Daten entgegen und ruft im Anschluss die Callback-Funktion auf.
Rückruf garantiert
Das Netfilter-Subsystem ruft die Callback-Funktion (Zeilen 15 bis 46) auf und übergibt dabei einige wichtige Argumente, darunter ein Handle zur Warteschlange (»struct nfq_q_handle«) und die Netfilter-Daten (»struct nfq_data«). Da der Funktionsaufruf von »nfq_set_mode()« in Zeile 84 die Option »NFQNL_COPY_PACKET« enthält, kann die Callback-Funktion auf das gesamte in die Warteschlange gestellte Netzwerkpaket zugreifen. Als ersten Schritt sollte sie jedoch »nfq_get_msg_packet_hdr()« aufrufen (Zeile 27), da die hiervon gelieferte Datenstruktur unter anderem die Paket-ID enthält, die bei der Rückgabe des Pakets an den Kernel via »nfq_set_verdict()« anzugeben ist.
Der Aufruf von »nfq_get_payload()« (Zeile 30) holt einen Zeiger auf die eigentlichen Paketdaten (zweites Argument, »pkt«), die das Programm dann abhängig vom Inhalt interpretieren kann. Da für das Beispiel nur ICMP-Pakete in die NFQueue-Warteschlange gelangen, greift die Anwendung gezielt auf die ICMP-Headerfelder zu. In Zeile 39 berechnet sie, wie viele Bytes auf das ICMP-Datenfeld abfallen, und kopiert dann mit »read()« in der nächsten Zeile maximal diese Menge an Daten in das entsprechende Feld des ICMP-Headers.
Abschließend ruft sie die Funktion »nfq_set_verdict()« auf, die das – in diesem Fall veränderte – Paket an den Kernel zurückgibt (Zeile 42). Die Option »NF_ACCEPT« fordert Netfilter dabei auf, das Paket normal weiterzuverarbeiten. Alternativ könnte das Programm im Userspace entscheiden, bestimmte Pakete zu verwerfen, indem es statt »NF_ACCEPT« die Option »NF_DROP« verwendet. Sind die letzten beiden Argumente dieser Funktion nicht »0« und »NULL«, enthalten sie die Länge und einen Zeiger auf veränderte Paketdaten. So arbeitet auch der Beispielcode.
Probelauf
Beim Übersetzen muss der Linker wissen, dass das Programm die Bibliotheken »netfilter_queue« und »nfnetlink« benötigt. Listing 2 zeigt ein passendes Makefile. Vor dem Testlauf teilt ein »iptables«-Aufruf dem Kernel mit, dass er Pakete in eine Warteschlange einreihen soll. Dafür sorgt das Sprungziel »NFQUEUE«. Genauere Auskunft über die Syntax gibt die Manpage von »iptables«.
Das Beispielprogramm erwartet nur ICMP-Pakete, der folgende Aufruf befiehlt dem Kernel, diese in die NFQueue-Warteschlange zu stellen:
iptables -t filter -I OUTPUT --proto icmp -j NFQUEUE
Ruft nun ein Anwender auf diesem Rechner »ping« auf, landen die damit erzeugten Pakete in der neuen Warteschlange. Nur wenn ein Prozess diese abholt und anschließend wieder an den Kernel übergibt, funktioniert die Kommunikation. Andernfalls bleiben die Pakete für immer in der Warteschlange hängen.
|
Listing 2: Makefile |
|---|
01 CC=gcc
02 CFLAGS=-Wall -g
03 LDFLAGS=-lnetfilter_queue -lnfnetlink
04
05 BIN=pinger
06
07 all: $(BIN)
08
09 $(BIN): $(BIN).o
10 $(CC) $(LDFLAGS) -o $@ $<
11
12 $(BIN).o: $(BIN).c
13 $(CC) $(CFLAGS) -c -o $@ $<
14
15 clean:
16 -rm -f $(BIN){,.o}
|
Die Informationen, die das Beispielprogramm innerhalb des ICMP-Datenfelds überträgt, holt es sich aus der Datei »/tmp/data«, die zunächst der folgende Perl-Aufruf erzeugt:
perl -e 'print "A"x20,"B"x20;' >/tmp/data
Er schreibt je 20-mal »A« und »B« in diese Datei. Wenn nun das Programm läuft und gleichzeitig »ping« Pakete verschickt, erhält der Empfänger diese Pakete mit verändertem ICMP-Datenfeld, was einige Ping-Versionen mit einem Fehlerhinweis quittieren. Abbildung 3 zeigt die empfangenen ICMP-Echo-Request-Pakete als Wireshark-Ausgabe, Abbildung 4 führt die IPtables-Regeln vor und nach dem Ausführen des Beispiels auf.

Abbildung 4: Ein Ping bei aktivem Beispielprogramm und passender IPtables-Regel erhöht den Paketzähler um 1. Auch das Programm »pinger« zeigt an, dass es ein Paket verarbeitet hat.
Die ICMP-Prüfsumme passt wegen der Veränderung nicht, das Betriebssystem des Zielrechners antwortet nicht – wie sonst üblich – mit einem Echo-Reply. Experimentierfreudige erweitern das Beispiel jedoch um die Neuberechnung dieser Prüfsumme innerhalb der Funktion »callback_funktion()«, nachdem sie die Daten aus der Datei gelesen hat.
|
Tabelle 2: Variablen |
|---|
Fazit
Zusammengefasst ermöglicht »libnetfilter_queue« die schnelle Entwicklung von Firewall- und Filterfunktionen im Userspace. Dadurch haben Entwickler deutlich mehr Werkzeuge, als es im Kernelspace der Fall ist. Unsauberer Code hat auf dieser Ebene keinen Einfluss auf die Systemstabilität: Fehlerhafte Paketbehandlungsprogramme stürzen wie alle anderen Anwendungen mit Programmfehlern ab, reißen aber nicht Teile des Kernels mit sich. Das alles war zwar schon mit dem älteren Queue-Ziel möglich, das aber nur eine einzige Warteschlange einrichten konnte. NFQueue macht dank Tausender Queues den Weg für wesentlich komplexere Anwendungen frei: Dafür sind nur mehrere Programme wie Listing 1 und dazu passende IPtables-Regeln notwendig.
Gegenwärtig ändert sich der Netfilter-Code stark. In absehbarer Zeit wird die NF-Tables-Implementierung [10] IPtables und damit auch das NFQueue-Ziel ablösen. Wie dann Netzwerkpakete im Userspace zu behandeln sind, steht noch nicht fest. (mg/hge)
|
Infos |
|---|
|
[1] Netfilter: [http://www.netfilter.org] [2] Moblock: [http://moblock.berlios.de] [3] Stegan RTP – RTP Covert Channel: [http://steganrtp.sourceforge.net] [4] Iptables-Tutorial: [http://security.maruhn.com/iptables-tutorial] [5] Open-Suse-Pakete: [ftp://ftp.gwdg.de/pub/linux/misc/suser-jengelh/SUSE-11.1/i586/] [6] Dokumentation zu »libnetfilter_queue«: [http://lists.netfilter.org/pipermail/netfilter-devel/2006-February/023286.html] [7] ICMP Type 8, Echo Request: [http://www.networksorcery.com/enp/protocol/icmp/msg8.htm] [8] Simplified Wrapper and Interface Generator (SWIG): [http://www.swig.org] [9] NFQueue-Bindings: [http://software.inl.fr//trac/wiki/nfqueue-bindings] [10] Ankündigung der ersten Release von »nftables«: [http://lwn.net/Articles/324251] |










