Linux netfilter Hacking HOWTO

Rusty Russell, mailing list netfilter@lists.samba.org v1.0 Tue May 2 14:07:03 CST 2000

Ins Deutsche übersetzt von Melanie Berg (mel@sekurity.de)

Inhaltsverzeichnis

1 Einleitung 2 1.1 Was ist Netfilter? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Was stimmte nicht mit dem, was wir im 2.0er und 2.2er Kernel hatten? . . . . . . . . . . . . 3 1.3 Wer bist Du? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.4 Wieso stürzt es ab? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2 Wo kann ich die neueste Version herbekommen? 5

3 Netfilter Architektur 5 3.1 Netfilter Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.2 Paketauswahl: iptables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3.2.1 Paketfiltern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.2.2 NAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3.3 Connection Tracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.4 Sonstige Möglichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

4 Informationen für Programmierer 7

4.1 ip tables verstehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 4.1.1 ip tables Datenstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 4.1.2 ip tables aus Sicht der Anwender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 4.1.3 ip tables verwenden und erforschen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

4.2 iptables erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 4.2.1 Der Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 4.2.2 Anwendertools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 4.2.3 ‘libiptc’ verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.3 NAT verstehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4.3.1 Connection Tracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.4 NAT/Connection Tracking erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4.4.1 Standard NAT-Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 4.4.2 Neue Protokolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 4.4.3 Neue NAT-Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.4.4 Protokoll-Hilfen für TCP und UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.5 Understanding Netfilter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.6 Neue Netfilter-Module schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.6.1 Netfilter-Schnittstellen benutzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.6.2 Eingereihte Pakete behandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.6.3 Kommandos vom Anwender empfangen . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4.7 Behandlung von Paketen aus Sicht der Anwender . . . . . . . . . . . . . . . . . . . . . . . . . 22

5 2.0er und 2.2er Paketfilter-Module übersetzen 22

6 Die Testsuite 22 6.1 Einen Test schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 6.2 Variablen und Umgebung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 6.3 Nützliche Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

6.3.1 gen ip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 6.3.2 rcv ip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 6.3.3 gen err . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 6.3.4 local ip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

6.4 Einige Ratschläge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

7 Motivation 26

8 Danke 27 :

1 Einleitung

Hi Leute. Dieses Dokument ist eine Reise; manche Teile sind gut besucht, und in anderen Bereichen wirst Du Dich fast alleine fühlen. Der beste Rat, den ich Dir geben kann, ist Dir eine große, heiße Tasse Kaffee oder Schokolade zu besorgen, Dich in einen bequemen Stuhl zu setzen, und den Inhalt in Dich aufzunehmen, bevor Du Dich in die gefährliche Welt des Netzwerk-Hackens begibst. Um die Verwendung der Infrastruktur auf dem Netfilter-Rahmenwerk besser zu verstehen, empfehle ich, das Paketfiltering-HOWTO und das NAT-HOWTO zu lesen. Für Informationen über Kernel-Programmierung empfehle ich Rusty’s Unreliable Guide to Kernle Hacking und Rusty’s Unreliable Guide to Kernel Locking. Dieses Dokument enthält Flüche. Das ist ein natürliches Gewürz in meiner Sprache, aber Du kannst die Originalversion dieses HOWTOs in American Broadcast übersetzen, wenn Du den Filter benutzt:

     sed `s/[^]aeio]ck/reak/'

1.1 Was ist Netfilter?

Netfilter ist eine Basis der Paketbehandlung, gehört aber nicht zu dem normalen Berkeley Socket Interface. Es besteht aus vier Teilen. Zuerst definiert jedes Protokoll ‘Hooks’ (IPv4 definiert 5), welche wohldefinierte Punkte auf der Reise eines Pakets durch den Protokoll-Stack sind. An jedem dieser Punkte wird das Protokoll Netfilter mit dem Paket und der Hook-Nummer aufrufen.

Zweitens können Teile des Kernels sich für das Aufpassen auf verschiedene Hooks für jedes Protokoll registrieren. Wenn ein Paket also an Netfilter gereicht wird, wird überprüft, ob irgendjemand für dieses Protokoll oder für diesen Hook registriert ist; Wenn ja, bekommt jeder von ihnen der Reihe nach die Chance, das Paket zu untersuchen (es möglicherweise zu verändern), das Paket zu verwerfen, es durchzulassen oder Netfilter zu beauftragen, das Paket für Userspace einzureihen.

Der dritte Teil besteht darin, dass eingereihte Pakete gesammelt werden (von ip queue-Treiber), um an den Userspace geschickt zu werden; diese Pakete werden asynchron behandelt.

Der letzte Teil besteht aus coolen Kommentaren im Code und Dokumentation. Das ist Bestandteil eines jeden experimentiellen Projekts. Das Netfilter-Motto ist (schamlos von Cort Dougan gestohlen):

              `` Also... wo ist das hier besser als KDE? ``

(Dieses Motto hat sich ein wenig ausgeweitet: “Peitsch mich aus, schlag mich, laß mich ipchains benutzen”).

Zusätzlich zu diesem einfach Rahmenwerk wurden verschiedene Module geschrieben, welche Funktionalitäten ähnlich zu früheren (pre-netfilter) Kernel bieten, im Besonderen ein erweiterbares NAT-System, und ein erweiterbares Paketfilter-System (iptables).

1.2 Was stimmte nicht mit dem, was wir im 2.0er und 2.2er Kernel hatten?

1. Keine ausgebaute Infrastruktur, um Pakete an Userspace weiterzugeben:

• Kernelprogrammierung ist schwer

• Kernelprogrammierung muß in C/C++ gemacht werden

• Dynamische Filter-Policies gehören nicht in den Kernel

• 2.2 führte ein, Pakete via netlink zum Userspace zu kopieren, aber das ist langsam, und ‘sanity checks’ (wenn ein Paket z.B. nur behauptet, von einer existierenden Schnittstelle zu kommen) sind nicht möglich.

2. Transparente Proxies sind eine Qual:

• Wir untersuchen jedes Paket, um zu sehen, ob ein Socket an diese Adresse gebunden ist

• Root darf an fremde Adressen binden

• Lokal-generierte Pakete können nicht redirected werden

• REDIRECT behandelt keine UDP-Antworten: Ein UDP-Named-Paket an 1153 weiterleiten funktioniert nicht, weil manche Clients Antworten von anderen Ports als 53 nicht mögen.

• REDIRECT koordiniert sich nicht mit der TCP/UDP Portzuordnung: Ein Benutzer kann einen Port durch eine REDIRECT-Regeln shadowen.

• Es ist während der 2.1er-Serie mindestens zweimal zusammengebrochen.

• Code ist extrem aufdringlich. Denk an die Statistiken von #ifdef CON-FIG IP TRANSPARENT PROXY in 2.2.1: es tritt in 11 Dateien insgesamt 34 mal auf. Im Vergleich dazu taucht CONFIG IP FIREWALL in 5 Dateien nur insgesamt 10 mal auf.

3. Es ist nicht mögliche, Paketfilter-Regeln unabhängig von der Adresse der Schnittstelle zu erstellen:

• Um lokal-generierte oder lokal-bestimmte Pakete von durchgehenden Paketen unterscheiden zu können, muß die lokale Adresse der Schnittstelle bekannt sein.

• Sogar das reicht bei Redirection oder Masquerading nicht aus.

• Die FORWARD-Kette besitzt nur Informationen über die ausgehende Schnittstelle, was bedeutet, dass Du herausfinden mußt, woher ein Paket kam, indem Du Dein Wissen über die Netzwerk-Topologie anwendest.

4. Masquerading ist abhängig von Paketfiltern:Interaktionen zwischen Paketfiltern und Masquerading machen eine Firewall komplex:

• Beim Eingangsfilter scheinen Antwortpakete für den Rechner selbst bestimmt zu sein

• Beim Forward-Filter werden demaskierte Pakete überhaupt nicht gesehen

• Beim Ausgangsfilter scheinen Pakete vom lokalen Rechner zu kommen

5. TOS-Manipulation, Redirect, ICMP unreachable und mark (welche Effekte auf Portforwarding, Routing und QoS haben können) hängen ebenfalls vom Paketfilter-Code ab.

6. ipchains-Code ist weder modular noch erweiterbar (zum Beispiel MAC-Adressen Filter, Filtern nach Optionen, etc).

7. Mangel an ausreichender Infrastruktur hat zu einem Überfluß von von verschiedenen Techniken geführt:

• Masquerading plus Per-Protokoll Module

• Schnelles, statisches NAT durch Routing Code (hat keine Per-Protokoll Routinen)

• Portforwarding, Redirect, Auto-Forwarding

• Das Linux NAT und virtuelle Server Projekte.

8. Inkompatibilität zwischen CONFIG NET FASTROUTE und Paketfiltern:

• Weitergeleitete Pakete gehen trotzdem durch drei Ketten

• Keine Möglichkeit, zu sagen, ob diese Ketten umgangen werden können

9. Wegen Routing-Protection (z.B. Verifikation der Quelladresse) keine Möglichkeit, verworfene Pakete zu untersuchen.

10. Keine Möglichkeit, automatisch die Zähler auf Paketfilter-Regeln zu lesen.

11. CONFIG IP ALWAYS DEFRAG ist eine Compilezeit-Option, die das Leben für Distributionen, die einen general-purpose Kernel wollen, schwer macht.

1.3 Wer bist Du?

Ich bin der einzige, der dumm genug ist, das zu tun. Als ipchains Co-Autor und jetziger Linux Kernel IP Firewall Maintainer sehe ich viele der Probleme, die die Leute mit dem jetzigen System haben, da ich erkenne, was sie alles machen wollen.

1.4 Wieso stürzt es ab?

Woah! Du hättest es letzte Woche sehen sollen!

Weil ich kein so guter Programmierer bin, wir wir alle es uns wünschen würden, und ich natürlich nicht alle Szenarien getestet habe, wegen Mangel an Zeit, Ausstattung und/oder Inspiration. Ich habe eine Testumgebung, und ich will Dich ermutigen, daran teilzunehmen.

2 Wo kann ich die neueste Version herbekommen?

Es gibt einen CVS Server auf samba.org, der die neuesten HOWTOs, Anwendertools und Testseiten enthält. Wenn Du browsen möchtest, kannst Du das Webinterface benutzen: Web Interface <http://www.samba.

org/cgi-bin/cvsweb/netfilter/>.

Um die neuesten Quellcodes zu bekommen, kannst Du folgendes tun:

1. Log Dich anonym auf dem SAMBA CVS Server ein:

          cvs -d :pserver:cvs@cvs.samba.org:/cvsroot login

2. Wenn Du nach einem Paßwort gefragt wirst, tippe ‘cvs’.

3. Überprüfe den verwendeten Code:

          cvs -d :pserver:cvs@cvs.samba.org:/cvsroot co netfilter

4. Um ein Update auf die neueste Version zu bekommen, verwende

          cvs update -d -P

3 Netfilter Architektur

Netfilter ist mehr eine Serie von Hooks auf bestimmten Positionen in einem Protokoll-Stack (zur Zeit IPv4, IPv6 und DECnet). Das (idealisierte) IPv4 Reise-Diagramm sieht folgendermaßen aus:

[figure: 2; 5; 4; 3; 1; Route; Route]

Links geht das Paket ein: Den einfachen sanity-check überstanden (ich meine, nicht verstümmelt, Checksumme OK, kein promiscuous receive), werden sie an den NF IP PRE ROUTING [1] Hook des Netfilter Rahmenwerks weitergereicht.

Als nächstes kommen sie in den Routing-Code, welcher entscheidet, ob das Paket für eine andere Schnittstelle oder für einen lokalen Prozeß bestimmt ist. Der Routing-Code kann unroutebare Pakete verwerfen.

Wenn es für den Rechner selbst bestimmt ist, wird der NF IP LOCAL IN [2] Hook des Netfilter Rahmenwerks wieder aufgerufen, bevor das Paket an den Prozeß (wenn es einen gibt) geschickt wird.

Wenn der Zielort stattdessen eine andere Schnittstelle ist, wird der NF IP FORWARD [3] Hook des Netfilter Rahmenwerks stattdessen aufgerufen.

Das Paket durchläuft dann den letzten Netfilter Hook, den NF IP POST ROUTING [4] Hook, bevor es wieder in die Leitung geschickt wird.

Für Pakete, die lokal generiert wurden, wird der NF IP LOCAL OUT [5] Hook aufgerufen. Hier kannst Du sehen, dass Routing erst dann einsetzt, wenn dieser Hook aufgerufen wurde: Tatsächlich wird zuerst der Routing-Code aufgerufen (um Angaben über Quelladresse und IP-Optionen zu bestimmen) und wird erneut aufgerufen, wenn das Paket geändert werden sollte.

3.1 Netfilter Grundlagen

Jetzt haben wir ein Beispiel von netfilter für IPv4, Du kannst sehen, wann jeder Hook aktiviert wird. Das ist die Essenz von Netfilter.

Kernelmodule können sich registrieren, um an irgendeinem dieser Hooks lauschen. Wenn dieser Netfilter Hook dann vom Networking-Code aufgerufen wird, hat an diesem Punkt jedes registrierte Modul die Möglichkeit, das Paket zu verändern. Das Modul kann Netfilter sagen, eine von drei Sachen zu tun:

1. NF ACCEPT: Die Reise wie gewöhnlich fortsetzen.

2. NF DROP: das Paket verwerfen, die Reise nicht fortsetzen.

3. NF STOLEN: Ich habe das Paket übernommen, setz die Reise nicht fort.

4. NF QUEUE: Das Paket einreihen (gewöhnlich für Userspace).

5. NF REPEAT: Diesen Hook erneut aufrufen.

Die anderen Teile von Netfilter (eingereihte Pakete behandeln, coole Kommentare) werden später in der Kernel-Sektion erklärt.

Auf diesem Fundament können wir ziemliche komplexe Paketfilter-Manipulationen aufbauen, wie in den nächsten zwei Sektionen gezeigt werden wird.

3.2 Paketauswahl: iptables

Ein System zur Paketauswahl mit dem Namen IP-Tables wurde über das Netfilter Rahmenwerk gebaut. Es ist ein direkter Abkömmling von ipchains (welches von ipfwadm abstammt, welches, wenn ich mich richtig erinnere, von BSD’s ipfw abstammt), mit Erweiterungen. Kernelmodule können eine neue Tabelle registrieren und von einem Paket verlangen, eine vorgegebene Tabelle zu durchwandern. Diese Methode zur Paketauswahl wird für Paketfilter (die ‘Filter’-Tabelle), Network Address Translation (die ‘NAT’-Tabelle) und allgemeine Behandlung von pre-routing Paketen (die ‘mangle’-Tabelle) verwendet.

3.2.1 Paketfiltern

Diese Tabelle, ‘filter’, sollte die Pakete niemals verändern: sie soll sie nur filtern.

Einer der Vorteile von iptables Filtern gegenüber ipchains ist, dass es klein und schnell ist, und es die Netfilter-Hooks NF IP LOCAL IN, NF IP FORWARD und NF IP LOCAL OUT verwendet. Das bedeutet, dass es für jedes gegebene Paket einen (und nur einen) möglichen Ort gibt, um es zu filtern. Dies vereinfacht die Dinge ungemein. Außerdem bedeutet der Fakt, dass das Netfilter Rahmenwerk beides, eingehende und ausgehende Schnittstellen für den NF IP FORWARD Hook bietet, dass viele Arten des Filterns weitaus einfacher werden.

Beachte: Ich habe die Kernelteile sowohl von ipchains als auch von ipfwadm zu Modulen auf Netfilter portiert, was es ermöglicht, die alten ipfwadm und ipchains Anwendungstools ohne ein Upgrade weiterzuverwenden.

3.2.2 NAT

Dies ist das Königreich der NAT-Tabelle, welche mit Paketen von drei Netfilter-Hooks gefüttert wird: für nicht-lokale Pakete sind für Quell- und Zielveränderungen jeweils der NF IP PRE ROUTING und der NF IP POST ROUTING Hook perfekt. Um das Ziel von lokalen Paketen zu verändern, wird der NF IP LOCAL OUT Hook verwendet.

Diese Tabelle unterscheidet sich insoweit leicht von der ‘Filter’-Tabelle, als dass nur das erste Paket einer neuen Verbindung die Tabelle durchwandern wird: Das Resultat dieser Untersuchung wird dann auf alle weiteren Pakete derselben Verbindung angewandt.

Masquerading, Portforwarding, transparente Proxies Ich unterteile NAT in Source NAT (wo die Quelle des ersten Pakets verändert wird) und in Destination NAT (wo das Ziel der ersten Pakets verändert wird).

Masquerading ist eine spezielle Form von Source NAT: Port-Forwarding und transparente Proxies sind eine spezielle Form von Destination NAT. Dies wird nun alles mit dem NAT-Rahmenwerk erledigt, anstatt unabhängige Einheiten zu sein.

3.3 Connection Tracking

Connection Tracking ist ein Fundament von NAT, wurde aber als separates Modul implementiert; dies erlaubt es, durch eine Erweiterung der Paketfilter einfach und sauber Connection Tracking zu verwenden (das ‘state’ Modul).

3.4 Sonstige Möglichkeiten

Die neue Flexibilität bietet sowohl die Möglichkeit, wirklich abgefahrene Dinge zu tun, als auch Erweiterungen oder einen kompletten Ersatz zu schreiben, die auch vermischt werden können.

4 Informationen für Programmierer

Ich weihe Dich in ein Geheimnis ein: mein Hamster hat alles programmiert. Ich war nur das Medium, wenn Du es so willst das ‘Front-End’, im großen Plan meines Hamsters. Also mach mich nicht für irgendwelche Bugs verantwortlich. Mach den schlauen mit dem Fell verantwortlich.

4.1 ip tables verstehen

iptables besteht einfach aus einem benannten Array von Regeln im Speicher (daher der Name ‘iptables’) und Informationen darüber, wo Pakete von jedem Hook die Reise beginnen sollen. Nachdem eine Tabelle registriert wurde, kann ihr Inhalt von Anwenderseite her gelesen oder ersetzt werden, indem getsockopt() und setsockopt() benutzt werden.

iptables registriert nicht mit irgendwelchen Netfilter Hooks: es verlässt sich auf andere Module, die das tun und es angemessen mit Paketen füttern sollen.

4.1.1 ip tables Datenstruktur

Der Bequemlichkeit halber wird dieselbe Datenstruktur verwendet, um eine Regel von Anwenderseite her oder im Kernel selbst zu repräsentieren, obwohl einige Felder nur im Kernel selbst benutzt werden.

Jede Regel besteht aus folgenden Teilen:

1. Einem ‘struct ip entry’.

2. Null oder mehr ‘struct ipt entry match’ Strukturen, an jede Platz für Variablen (0 oder mehr Bytes) von Daten angehängt.

3. Einer ‘struct ipt entry target’ Struktur, an jede Platz für Variablen (0 oder mehr Bytes) von Daten angehängt.

Die Variablen-Natur der Regeln gibt eine große Flexibilität für Erweiterungen, wie wir sehen werden, besonders, da jeder Treffer oder jedes Ziel eine willkürliche Menge von Daten tragen können. Dies birgt zwar auch ein paar Fallen, aber wie auch immer: Wir müssen aufpassen. Wir tun dies, indem wir uns darüber versichern, dass die ‘ipt entry’, die ‘ipt entry match’ und die ‘ipt entry target’ Strukturen eine angenehme Größe haben, und dass alle Daten auf die maximale Auslastung des Rechners aufgerundet werden, indem wir den IPT ALIGN() Makro verwenden.

‘struct ipt entry’ hat die folgenden Felder:

1. Einen ‘struct ipt ip’ Teil, der die Bestimmungen für den IP-Header enthält, der zutreffen soll.

2. Ein ‘nf cache’ Bitfeld, das zeigt, welche Teile des Pakets diese Regel untersucht hat.

3. Ein ‘target offset’ Feld, das den Absatz vom Anfang dieser Regel angibt, wo die ipt entry target Struktur beginnt.

4. Ein ‘next offset’ Feld, das die absolute Größe dieser Regel angibt, einschließlich Treffer und Ziele.

5. Ein ‘comefrom’ Feld, das vom Kernel benutzt wird, um die Reise des Pakets zurückzuverfolgen.

6. Ein ‘struct ipt counters’ Feld, was die Paket- und Bytezähler für Pakete, die auf diese Regel gepaßt haben, enthält.

ipt entry match’ und ‘struct ipt entry target’ sind sehr ähnlich darin, dass sie ein Feld für die Gesamtlänge (jeweils ‘match size’ und ‘target size’) enthalten, einen Union, der den Namen des Treffers oder des Ziels (auf Anwenderseite) enthält, und einen Pointer (für den Kernel).

Wegen der trickreichen Natur der Datenstruktur einer Regel werden einige Hilfsroutinen angeboten:

ipt get target()

Diese Funktion liefert einen Pointer auf das Target einer Regel.

IPT MATCH ITERATE()

Dieser Makro ruft die gegebene Funktion für jeden Treffer einer Regel auf. Das erste Argument der Funktion ist ‘ipt match entry’ und andere Argumente (wenn es welche gibt) sind die, die im IPT MATCH ITERATE() Makro angeboten werden.

IPT ENTRY ITERATE()

Diese Funktion macht einen Pointer zu einem Eintrag, der Gesamtgröße der Tabelle der Einträge und einer Funktion, die aufzurufen ist. Das erste Argument der Funktion ist ‘struct ipt entry’, und andere Argumente (wenn es welche gibt) sind die, die im IPT ENTRY ITERATE() Makro angeboten werden.

4.1.2 ip tables aus Sicht der Anwender

Userspace hat vier Operationen: Es kann die aktuelle Tabelle lesen, die Info lesen (Hook-Positionen und Größe der Tabelle), die Tabelle ersetzen (und die alten Zähler nehmen), und neue Zähler einbauen.

So kann Userspace eine kleine Operation simulieren: Dies wird von der libiptc Library getan, welche die einfachen “add/delete/replace” semantics für Programme bietet.

Da diese Tabellen in den Kernel transferiert werden, stellt sich die Frage der Auslastung der Maschinen, die unterschiedliche Userspace- und Kernelspace Typ-Regeln haben (z.B. Sparc mit 32-Bit Userland). Diese Fälle werden mit dem Überschreiben der Definition von IPT ALIGN für diese Platformen in ‘libiptc.h’ behandelt.

4.1.3 ip tables verwenden und erforschen

Der Kernel beginnt die Untersuchung an der Stelle, die durch einen bestimmten Hook definiert ist. Diese Regel wird überprüft; wenn das ‘struct ipt ip’ Element zutrifft, wird jedes ‘struct ip entry match’ der Reihe nach untersucht (es wird die match-Funktion entsprechend dem Treffer aufgerufen). Wenn die match-Funktion 0 liefert, endet die Iteration auf dieser Regel. Wenn sie den ‘hotdrop’ Parameter auf 1 setzt, wird das Paket ebenfalls sofort verworfen werden (Dies wird für einige verdächtige Pakete benutzt, so wie in der TCP-match Funktion).

Wenn diese Iteration bis zum Ende durchläuft, werden die Counter erhöht, ‘struct ipt entry target’ wird untersucht: Wenn es ein Standardtarget ist, wird das ‘verdict’ Feld gelesen (negativ bedeutet ein Urteil über das Paket, positiv steht für ein Offset, zu dem man springen kann). Wenn die Antwort positiv ist und das Offset nicht das der nächsten Regel ist, wird die ‘back’ Variable gesetzt, und der vorige ‘back’ Wert wird in das ‘comefrom’ Feld der Regel geschrieben.

Für nicht-standard Targets wird die target Funktion aufgerufen: Sie liefert ein Urteil (nicht-standard Ziele können zu nichts springen, das würde den statischen Loop-Detection-Code brechen). Das Urteil kann IPT CONTINUE sein, um bei der nächsten Regel weiterzumachen.

4.2 iptables erweitern

Weil ich so faul bin, ist iptables leicht erweiterbar. Dies ist hauptsächlich der Versuch, die Arbeit auf die anderen zu schieben, worum es bei Open Source eigentlich geht (Bei Freier Software, wie RMS sagen würde, geht es um Freiheit, und ich saß bei einem seiner Gespräche dabei, als ich das hier schrieb).

iptables erweitern besteht aus zwei Teilen: den Kernel erweitern, indem man ein neues Modul schreibt, und möglicherweise das Anwendungsprogramm iptables erweitern, indem man eine neue Shared Library schreibt.

4.2.1 Der Kernel

Ein Kernelmodul selbst zu schreiben ist ziemlich einfach, wie Du an den Beispielen sehen kannst. Eine Sache, auf die Du achten mußt, ist, dass Dein Code integriert werden muß: Es kann ein Paket von Anwenderseite her ankommen, während ein anderes an einem Interrupt eintrifft. Tatsächlich kann es ab 2.3.4 bei SMP vorkommen, dass ein Paket an einem Interrupt pro CPU eintrifft.

Folgende Funktionen mußt Du kennen:

init module()

Das ist der Eintrittspunkt des Moduls. Es liefert eine negative Zahl oder 0, wenn Netfilter es erfolgreich registriert.

cleanup module()

Das ist der Austrittspunkt des Moduls; es soll die Registration beim Kernel wieder aufheben.

ipt register match()

Dies wird verwendet, um einen neuen, zutreffenden Typ zu registrieren. Du übergibst ihm ein ‘struct ip match’, was gewöhnlich als statische Variable deklariert wird (file-scope).

ipt register target()

Dies wird verwendet, um einen neuen Typ zu registrieren. Du übergibst ihm ein ‘struct ip target’, was gewöhnlich als statische Variable deklariert wird (file-scope). ipt unregister target()

Um die Registration des Ziel aufzuheben.

ipt unregister match()

Um die Registration des Treffers aufzuheben.

Eine Warnung zu trickreichen Dingen (wie das Verwenden von Countern) für Deine neuen Treffer oder Ziele. Auf SMP-Maschinen wird die vollständige Tabelle mit Hilfe von memcpy für jede CPU dupliziert: Wenn Du die Information wirklich zentral halten willst, solltest Du Dir die Treffermethode zu ‘limit’ ansehen.

Neue Match-Funktionen Neue match-Funktionen werden gewöhnlich als alleinstehende Module geschrieben. Es ist möglich, diese Module der Reihe nach zu erweitern, obwohl es normalerweise nicht nötig ist. Eine Möglichkeit, um den Benutzern zu erlauben, direkt mit Deinem Modul zu sprechen, ist es, die ‘nf register sockopt’ Funktion des Netfilter Rahmenwerks zu nutzen. Eine andere Möglichkeit ist es, Symbole für andere Module zu exportieren, damit sie sich registrieren können; auf diese Weise machen es Netfilter und iptables.

Das Wesentliche Deiner neuen Match-Funktion ist ‘struct ipt match’, was an ‘ipt register match()’ übergeben wird. Diese Struktur hat die folgenden Felder:

list Dieses Feld ist auf irgendeinen Blödsinn gesetzt, z.B. ‘{NULL, NULL}’.

name Dieses Feld ist der Name der Match-Funktion, auf die sich der Anwender bezieht. Der Name sollte dem Modul entsprechend gewählt werden (wenn der Name “mac” ist, muß das Modul “ipt mac.o” heißen), damit es automatisch geladen werden kann.

match Diese Feld ist ein Pointer auf eine Match-Funktion, welche aus einem skb, dem Pointer auf ein Eingangsund ein Ausgangsgerät (eins davon kann NULL sein, das hängt von dem Hook ab), einem Pointer auf die Match-Daten der zutreffenden Regel, der Größe dieser Regel, dem IP-Offset (nicht Null steht für ein non-head Fragment), einem Pointer auf den Protokoll-Header (ich meine nur den IP-Header), der Gesamtlänge der Daten (ich meine die Paketlänge minus der IP-Header Länge) und schließlich einem Pointer auf die ‘hotdrop’ Variable besteht. Es soll nicht Null liefern, wenn die Regel auf das Paket zutrifft, und kann ‘hotdrop’ auf 1 setzen, wenn es Null liefert, um anzuzeigen, dass das Paket sofort verworfen werden soll.

checkentry Dieses Feld ist ein Pointer auf eine Funktion, die die Bestimmungen für eine Regel untersucht; Wenn es 0 liefert, wird die Regel des Benutzers nicht akzeptiert werden. Zum Beispiel wird der “tcp” Match Typ nur TCP-Pakete akzeptieren, wenn also der ‘struct ipt ip’ Teil der Regel nicht spezifiziert, dass das Protokoll TCP sein muß, wird Null geliefert. Das Tablename Argument erlaubt Deinem Match, zu kontrollieren, in welchen Tabellen es eingesetzt werden kann, und ‘hook mask’ ist eine Bitmaske von Hooks, von denen diese Regel möglicherweise aufgerufen wurde: Wenn Dein Match von einem der Netfilter-Hooks keinen Sinn macht, kannst Du das hier umgehen.

destroy Dieses Feld ist ein Pointer auf eine Funktion, die aufgerufen wird, wenn ein Eintrag, der dieses Match verwendet, gelöscht wird. Dies erlaubt Dir auch, Resourcen in checkentry dynamisch zuzuordnen und sie hier wieder aufzuräumen. me

Dieses Feld ist auf ‘& this module’ gesetzt, welches Deinem Modul einen Pointer gibt. Es bewirkt, dass der usage-count steigt und fällt, je nachdem, ob Regeln dieses Typs erstellt oder gelöscht werden. Das bewahrt den Benutzer davon, das Modul zu entfernen (und somit das cleanup module aufzurufen), wenn sich eine Regel darauf bezieht.

Neue Targets Neue Targets werden auch gewöhnlich als alleinstehende Module geschrieben. Die Diskussionen der oberen Sektion ‘Neue Match Funktionen’ können hier ähnlich angewandt werden.

Das Wesentliche Deines neuen Targets ist ‘struct ipt target’, das an ‘ipt register target()’ übergeben wird. Diese Struktur hat die folgenden Felder:

list Dieses Feld ist auf irgendeinen Blödsinn gesetzt, z.B. ‘{NULL, NULL}’.

name Dieses Feld ist der Name der Ziel-Funktion, auf die sich der Anwender bezieht. Der Name sollte dem Modul entsprechend gewählt werden (wenn der Name “REJECT” ist, muß das Modul “ipt REJECT.o” heißen), damit es automatisch geladen werden kann.

target Diese Feld ist ein Pointer auf eine Ziel-Funktion, welche aus einem skbuff, dem Pointer auf ein Eingangsund ein Ausgangsgerät (eins davon kann NULL sein) einem Pointer auf die Ziel-Daten, der Größe der Ziel-Daten, und der Position der Regel in der Tabelle besteht. Die Target-Funktion liefert eine nichtnegative, absolute Position, zu der zu springen ist, oder ein negatives Urteil (was das negierte Urteil minus 1 ist).

checkentry Dieses Feld ist ein Pointer auf eine Funktion, die die Bestimmungen für eine Regel überprüft; wenn das 0 liefert, wird die Regel des Benutzers nicht akzeptiert werden.

destroy Dieses Feld ist ein Pointer auf eine Funktion, die aufgerufen wird, wenn ein Eintrag, der dieses Match verwendet, gelöscht wird. Dies erlaubt Dir auch, Resourcen in checkentry dynamisch zuzuordnen und sie hier wieder aufzuräumen.

me Dieses Feld ist auf ‘& this module’ gesetzt, welches Deinem Modul einen Pointer gibt. Es bewirkt, dass der usage-count steigt und fällt, je nachdem, ob Regeln dieses Typs erstellt oder gelöscht werden. Das bewahrt den Benutzer davon, das Modul zu entfernen (und somit das cleanup module aufzurufen), wenn sich eine Regel darauf bezieht.

Neue Tabellen Wenn Du willst, kannst Du für einen speziellen Zweck eine neue Tabelle erstellen. Um das zu tun, rufst Du ‘ipt register table()’ mit ‘struct ipt table’ auf, was die folgenden Felder hat:

list Dieses Feld ist auf irgendeinen Blödsinn gesetzt, z.B. ‘{NULL, NULL}’.

name Dieses Feld ist der Name der Ziel-Funktion, auf die sich der Anwender bezieht. Der Name sollte dem Modul entsprechend gewählt werden (wenn der Name “nat” ist, muß das Modul “ipt nat.o” heißen), damit es automatisch geladen werden kann. table

Dies ist ein voll ausgenutztes ‘struct ipt replace’, wie es vom Anwender genutzt werden kann, um eine Tabelle zu ersetzen. Der ‘counters’ Pointer sollte auf NULL gesetzt sein. Diese Datenstruktur kann als ‘ initdata’ deklariert werden, damit es nach dem Booten verworfen werden kann.

valid hooks Dies ist eine Bitmaske der IPv4 Netfilter Hooks, mit der Du die Tabellen betreten wirst: Sie wird verwendet, um zu überprüfen, dass diese Eintrittspunkte gültig sind und um die möglichen Hooks für die ipt match und die ipt target ‘checkentry() Funktionen zu berechnen.

lock Dies ist das read-write Lock für die vollständige Tabelle, initialisiere es auf RW LOCK UNLOCKED.

private Dies wird vom ip tables Code intern verwendet.

4.2.2 Anwendertools

Jetzt, wo Du Dein nettes cooles Kernelmodul geschrieben hast, möchtest Du vielleicht seine Optionen von Anwenderseite her kontrollieren. Lieber, als für jede Erweiterung von iptables eine abgespaltene Version zu haben, benutze ich eine Technologie der späten 90er: Furbies. Sorry, ich meine shared Libraries.

Gewöhnlich benötigen neue Tabellen keine Erweiterungen von iptables: Der Benutzer verwendet einfach die ‘-t’ Option, um auf eine neue Tabelle zugreifen zu können.

Die shared Libraries sollten eine ‘ init()’ Funktion haben, die beim Laden automatisch aufgerufen wird: Das moralische Gegenstück zu der ‘init module()’ Funktion der Kernelmodule. Dies sollte ‘register match()’ oder ‘register target()’ aufrufen, abhängig davon, ob Deine shared Library ein neues Match oder ein neues Ziel ist.

Du brauchst nur dann eine shared Library, wenn Du einen Teil der Struktur initialisieren oder eine zusätzliche Option bieten willst. Das Ziel ‘REJECT’ zum Beispiel benötigt nichts dergleichen, hat also auch keine shared Library.

Es gibt nützliche Funktionen, die in ‘iptables.h’ beschrieben werden, besonders:

check inverse()

Überprüft, ob eins der Argumente ein ‘!’ ist, und wenn ja, setzt es das ‘invert’ Flag, wenn das noch nicht geschehen sein sollte. Wenn sie ‘true’ liefert, solltest Du optind wie im Beispiel erhöhen.

string to number()

Konvertiert einen String zu einer Zahl in der gegebenen Größenordnung. Sie liefert -1, wenn der String ungültig ist oder außerhalb der Größenordnung liegt.

exit error()

Diese Funktion sollte aufgerufen werden, wenn ein Fehler gefunden wird. Das erste Argument ist gewöhnlich ‘PARAMETER PROBLEM’, was bedeutet, dass der User die Kommandozeile nicht richtig benutzt hat.

Neue Match-Funktionen Die init() Funktion Deiner shared Library übergibt einen Pointer auf ein statisches ‘struct iptables match’ an ‘register match()’, was die folgenden Felder hat: next

Dieser Pointer wird verwendet, um eine verlinkte Liste von Matches zu erstellen (wie sie für die listing-Regeln verwendet wird). Am Anfang sollte er auf NULL gesetzt sein.

name Der Name der Match-Funktion. Er sollte entsprechend dem Namen der Library gewählt sein (z.B. “tcp” für ‘libipt tcp.so’).

version Dies wird gewöhnlich auf den NETFILTER VERION Makro gesetzt: Es stellt sicher, dass das iptables Binary nicht aus Versehen die falsche Library auswählt.

size Die Größe der Match-Daten für dieses Match; damit Du Dir keine Sorgen machen mußt, wird sie von IPT ALIGN() automatisch aufgerundet werden.

userspacesize Für einige Matches ändert der Kernel intern einige Felder (z.B. im Fall von ‘limit’). Das bedeutet, dass ein einfaches ‘memcmp’ nicht mehr ausreicht, um zwei Regeln zu vergleichen (benötigt für die deletematching-rule Funktionalität). Wenn dies der Fall ist, plaziere all die Felder, die sich nicht ändern, an den Anfang der Struktur, und lege die Größe der sich nicht ändernden Felder hier ab.

help Eine Funktion, die die Synopsis der Option ausdruckt.

init Dies kann verwendet werden, um etwas mehr Platz (wenn vorhanden) in der ipt entry match Stuktur zu schaffen, und um jegliche nfcache Bits zu setzen; Wenn Du etwas untersuchst, das Du nicht ausdrücken kannst, indem Du den Inhalt von ‘/linux/include/ netfilter ipv4.h’ verwendest, wende im NFC UNKNOWN Bit einfach OR an. Dies wird vor ‘parse()’ aufgerufen werden.

parse Dies wird aufgerufen, wenn eine nicht erkannte Option auf der Kommandozeile gesehen wird: Es sollte nicht NULL liefern, wenn diese Option tatsächlich für Deine Library bestimmt war. ‘invert’ liefert ‘true’, wenn bereits ein ‘!’ gesehen wurde. Der ‘flags’ Pointer ist für die exklusive Verwendung Deiner Match-Library und wird gewöhnlich dazu verwendet, eine Bitmaske von bestimmten Optionen zu speichern. Vergiß nicht, das nfcache Feld auszurichten. Wenn nötig, kannst Du die Größe der ‘ipt entry match’ Struktur erhöhen, indem Du Sie erneut zuordnest, dann mußt Du aber auch sicherstellen, dass die Größe durch den IPT ALIGN Makro gehen muß.

final check Dies wird nach überstandener Kommandozeileneingabe aufgerufen. Ihm werden die für Deine Library reservierten Integer-’flags’ übergeben. Dies gibt Dir die Gelegenheit, zu überprüfen, ob alle benötigten Optionen angegeben wurden, zum Beispiel ‘exit error()’ aufzurufen, wenn dies der Fall sein sollte.

print Dies wird von Chain-Listing-Code verwendet, um jegliche zusätzliche Match-Information einer Regel (wenn vorhanden) ausdrucken (zum Standard Output). Das numerische Flag wird gesetzt, wenn der User es mit der ‘-n’ Option angegeben hat.

save Dies ist das Gegenteil von parse: Es wird von ‘iptables-save’ verwendet, um die Optionen, die die bestimmte Regel erstellt haben, zu reproduzieren. extra opts

Dies ist eine NULL-terminierte Liste von Extra-Optionen, die Deine Library anbietet. Dies wird vermischt mit den jetzigen Optionen und so an getopt long übergeben; Für Details siehe Manpage.

Es gibt noch extra Elemente am Ende dieser Struktur, die iptables intern verwendet: Du brauchst sie hier nicht zu setzen.

Neue Targets Die init() Funktionen Deiner shared Libraries übergeben ‘register target()’ einen Pointer auf ein statisches ‘struct iptables target’, welches ähnliche Felder hat wie die iptables match Strukturen, die oben detailliert beschrieben wurden.

Manchmal braucht ein Target keine Library für den Userspace; Eine triviale mußt Du sowieso erstellen: Es gab früher zu viele Probleme mit schlecht plazierten Libraries.

4.2.3 ‘libiptc’ verwenden

libiptc ist die Kontroll Library für iptables, entworfen, um Regeln im iptables Kernelmodul aufzulisten und zu manipulieren. Während seine jetzige Verwendung sich auf das iptables Tool beschränkt, macht es das Schreiben neuer Tools recht einfach. Um diese Funktionen nutzen zu können, mußt Du root sein.

Die Kerneltabellen selbst bestehen nur aus ein paar Tabellen mit Regeln und einer Anzahl von Nummern, die die Eintrittspunkte repräsentieren. Die Namen der Ketten (“INPUT”) werden als Abstraktion von der Library unterstützt. Benutzerdefinierte Ketten werden benannt, indem ein Fehlerverweis vor dem Beginn der benutzerdefinierten Kette eingefügt wird, der den Kettennamen in einer speziellen Datensektion des Targets enthält (Die Positionen der eingebauten Ketten werden durch die drei Eintrittspunkte der Tabelle definiert).

Wenn ‘iptc init()’ aufgerufen wird, wird die Tabelle einschließlich der Counter gelesen. Diese Tabelle wird von der ‘iptc insert entry()’, ‘iptc replace entry()’, ‘iptc append entry()’, ‘iptc delete entry()’, ‘iptc delete num entry()’, ‘iptc flush entries()’, ‘iptc zero entries()’, ‘iptc create chain()’ ‘iptc delete chain()’, und ‘iptc set policy()’ Funktion manipuliert.

Die Änderungen an der Tabelle werden nicht eher zurückgeschrieben, bis die ‘iptc commit()’ Funktion aufgerufen wird. Das bedeutet, dass es für zwei Benutzer der Library gleichzeitig möglich ist, auf derselben Kette zu operieren, um jeweils der Schnellere zu sein; Um das zu verhindern, ist Locking nötig, was aber zur Zeit nicht eingesetzt wird.

Es gibt kein Wettrennen mit bei Countern, wie auch immer; Counter werden so in den Kernel zurückaddiert, dass eine Erhöhung der Zähler zwischen dem Lesen und dem Schreiben der Tabelle immernoch in der neuen Tabelle auftauchen wird.

Es gibt verschiedene Hilfsfunktionen:

iptc first chain()

Diese Funktion liefert den Namen der ersten Kette in dieser Tabelle.

iptc next chain()

Diese Funktion liefert den Namen der nächsten Kette in dieser Tabelle: NULL bedeutet, es gibt keine weiteren Ketten.

iptc builtin()

Liefert true, wenn der Name der gegebenen Kette der Name einer eingebauten Kette ist.

iptc first rule()

This returns a pointer to the first rule in the given chain name: NULL for an empty chain. iptc next rule()

Dies liefert einen Pointer auf die erste Regel im Namen der gegebenen Kette: NULL für eine leere Kette.

iptc get target()

Dies gibt das Target der gegebenen Regel. Wenn es kein erweitertes Target ist, wird der Name dieses Targets geliefert. Wenn es ein Sprung auf eine andere Kette ist, wird der Name dieser Kette geliefert. Wenn es ein Urteil (z.B. DROP) ist, wird dieser Name geliefert. Wenn die Regel kein Ziel hat (und eine accountig-style Regel), wird ein leerer String geliefert. Beachte, dass Du diese Funktion verwenden solltest, anstatt den Wert des ‘verdict’ Felds der ipt entry Struktur direkt zu benutzen, da sie die oben genannten weiteren Interpretationen des Standard Urteils bietet.

iptc get policy()

Dies liefert die Policy einer eingebauten Kette und füllt das Counterargument mit den Treff-Statistiken dieser Policy.

iptc strerror()

Diese Funktion liefert eine aussagekräftigere Erklärung von gescheitertem Code in der iptc Library. Wenn eine Funktion scheitert, wird es errno setzen: Dieser Wert kann an iptc strerror() übergeben werden, um eine Fehlermeldung zu erhalten.

4.3 NAT verstehen

Willkommen zu Network Address Translation im Kernel. Beachte, dass die angebotene Infrastruktur mehr für Vollständigkeit als für Effizienz entworfen wurde und dass weitere Entwicklungen die Effizienz erhöhen können. Im Moment bin ich froh, dass es überhaupt funktioniert.

NAT wird aufgeteilt in Connection Tracking (was die Pakete überhaupt nicht verändert) und in den NAT Code selbst. Connection Tracking kann auch als iptables Modul verwendet werden, um subtile Unterscheidungen von Zuständen zu machen, die NAT egal sind.

4.3.1 Connection Tracking

Connection Tracking benutzt als Hooks die von der Priorität höhergestellten NF IP LOCAL OUT und NF IP PRE ROUTING, um Pakete zu sehen, bevor sie das System betreten.

Das ncft Feld in skb ist ein Pointer auf einen der infos[] Arrays im Inneren von struct ip conntrack. So können wir den Zustand von skb durch das Element bestimmen, auf welches dieser Array zeigt: Dieser Pointer erkennt beides, die State Struktur und die Beziehung des skb zu diesem Zustand.

Der beste Weg, das ‘nfct’ Feld zu lesen, ist es, die ‘ip conntrack get()’ Funktion aufzurufen, die NULL liefert, wenn es nicht gesetzt ist, oder den Connection Pointer. Außerdem füllt sie ctinfo, was die Beziehung des Paket zu der Verbindung beschreibt. Dieser numerierte Typ hat folgende Werte:

IP CT ESTABLISHED Das Paket ist Teil einer in Originalrichtung aufgebauten Verbindung.

IP CT RELATED Das Paket steht in Zusammenhang mit der Verbindung und geht in die Originalrichtung.

IP CT NEW Das Paket versucht, eine neue Verbindung aufzubauen (offensichtlich geht es in die Originalrichtung).

IP CT ESTABLISHED + IP CT IS REPLY Das Paket ist Teil einer aufgebauten Verbindung, und zwar in Antwortrichtung.

IP CT RELATED + IP CT IS REPLY Das Paket steht in Zusammenhang mit der Verbindung und geht in die Antwortrichtung.

Ein Antwortpaket kann also als solches indentifiziert werden, indem man auf >= IP CT IS REPLY testet.

4.4 NAT/Connection Tracking erweitern

Diese Rahmenwerke wurden entworfen, um jegliche Art von Protokollen und verschiedenen Mapping-Arten unterzubringen. Manche dieser Mapping-Arten können sehr spezifisch sein, so wie load-balancing oder failover Mappings.

Intern konvertiert Connection Tracking ein Paket zu einem ‘Tupel’, der den interessanten Teil des Pakets repreasentiert, bevor nach darauf zutreffenden Regeln gesucht wird. Dieser Tupel hat einen manipulierbaren Teil und einen nicht-manipulierbaren Teil; “src” und “dst” genannt, da dies die Ansicht des ersten Pakets in der Source NAT Welt ist (in der Destination NAT Welt würde es ein Antwortpaket sein). Die Tupel aller Pakete in demselben Paket-Stream in dieser Richtung sind gleich.

Das Tupel eine TCP-Pakets enthält z.B. den manipulierbaren Teil (Quell-IP und Quellport) und den nichtmanipulierbaren Teil (Ziel-IP und Zielport) Der manipulierbare und der nicht-manipulierbare Teil müssen trotzdem nicht vom selben Typ sein; Das Tupel eines ICMP-Pakets enthält den manipulierbaren Teil (Quell-IP und ICMP-ID) und den nicht-manipulierbaren Teil (Ziel-IP und ICMP Typ und Code).

Jedes Tupel hat ein Inverses, nämlich das Tupel des Antwortpakets in dem Stream. Das Inverse eines ICMP Ping-Pakets (icmp id 1234, von 192.168.1.1 an 1.2.3.4) ist zum Beispiel ein Ping Antwort Paket (icmp id 1234, von 1.2.3.4 an 192.168.1.1).

Diese Tupel, die von ‘struct ip conntrack tuple’ repräsentiert werden, werden häufig verwendet. Tatsächlich ist das, zusammen mit dem Hook, an dem das Paket einging (was einen Effekt auf die zu erwartende Art der Manipulation hat) und dem beteiligten Device, die komplette Information über das Paket.

Die meisten Tupel sind Teil von ‘struct ip conntrack tuple hash’, was einen doppelt verlinkten Listeneintrag und einen Pointer auf die Verbindung, zu der das Paket gehört, hinzufügt.

Eine Verbindung wird von ‘struct ip conntrack’ repräsentiert: Es hat zwei ‘struct ip conntrack tuple hash’ Felder: Eins, was sich auf die Richtung des Originalpakets bezieht (tuplehash[IP CT DIR ORIGINAL]), und eins, was sich auf die Antwortrichtung des Pakets bezieht (tuplehash[IP CT DIR REPLY]).

Wie auch immer, das erste, was der NAT Code tut, ist nachzusehen, ob der Connection Tracking Code es geschafft hat, ein Tupel zu extrahieren und ein bestehende Verbindung zu finden, indem er in das nfct Feld von skbuff sieht; Das sagt uns, ob es ein Versuch ist, eine neue Verbindung aufzubauen, oder, wenn nicht, in welche Richtung das ganze geht; im letzteren Fall werden die Manipulationen ausgeführt, die vorher für diese Verbindung bestimmt wurden.

Wenn es der Anfang einer neuen Verbindung war, suchen wir nach einer Regel für dieses Tupel, indem wir den Standard-Mechanismus von iptables verwenden. Trifft eine Regel zu, wird sie verwendet, um Manipulationen sowohl für die Original-, als auch für die Antwortrichtung auszuführen; dem Connection Tracking Code wird mitgeteilt, dass die zu erwartenden Antwort sich geändert hat. Dann wird das Paket wie oben beschrieben verändert.

Wenn es keine Regel gibt, wird eine ‘Null’ Bindung erstellt: Das mappt das Paket gewöhnlich nicht, stellt aber sicher, dass wir keinen anderen Stream über den existierenden mappen. Manchmal kann die Null-Bindung nicht erstellt werden, weil wir bereits einen existierenden Stream darübergemappt haben. In diesem Fall könnte die Pre-Protocol Manipulation versuchen, ihn zu remappen, obwohl es vom Namen her keine Null-Bindung ist.

4.4.1 Standard NAT-Targets

NAT Targets sind wie alle anderen iptables Target-Erweiterungen, außer, dass sie darauf bestehen, nur in der ‘nat’ Tabelle verwendet zu werden. Sowohl SNAT als auch DNAT Targets verwenden ‘struct ip nat multi range’ als Extradaten; dies wird benutzt, um einen Bereich von Adressen zu bestimmen, auf denen ein Mapping gültig ist. Ein Element dieses Bereichs, ‘struct ip nat range’, besteht aus einer inklusiven minimalen und maximalen IP-Adresse und aus einem inklusiven minimalen und maximalen Wert zur Protokollbestimmung (zum Beispiel TCP Ports). Es gibt auch Platz für Flags, die uns sagen, ob die IP-Adresse gemappt werden kann (manchmal wollen wir nur den protokoll-spezifischen Teil eines Tupels mappen, nicht die IP), oder ein anderes, das uns sagt, dass der protokoll-spezifische Teil des Bereichs ‘valid’ ist.

Ein Multi-Range ist ein Array aus diesen ‘struct ip nat range’ Elementen; das bedeutet, dass ein Bereich “1.1.1.1-1.1.1.2 Ports 50-55 AND 1.1.1.3 Port 80” sein kann. Jedes Element wird zu dem Bereich dazuaddiert (Union, für die, die diese Theorie mögen).

4.4.2 Neue Protokolle

Der Kernel von innen Ein neues Protokoll zu implementieren bedeutet zuerst, zu entscheiden, was der manipulierbare und was der nicht-manipulierbare Teil des Tupels sein sollen. Jedes Teil des Tupels hat die Eigenschaft, dass es den Stream eindeutig identifiziert. Der manipulierbare Teil des Tupel ist der Teil, mit dem Du NAT machen kannst: Für TCP ist das der Quellport, für ICMP ist das die ICMP id; etwas, das das “Stream Identifier” benutzt werden kann. Der nicht-manipulierbare Teil ist der Rest des Pakets, der den Stream zwar auch eindeutig identifiziert, mit dem wir aber nicht spielen können (z.B. TCP Zielport, ICMP Typ).

Sobald Du Dich dazu entschieden hast, kannst Du eine Erweiterung für den Connection Tracking Code schreiben und die ‘ip conntrack protocol’ Struktur, die Du an ‘ip conntrack register protocol()’ übergeben mußt, ausbauen.

Die Felder von ‘struct ip conntrack protocol’ sind:

list Setz es auf ‘{NULL, NULL}’; verwendet, um sich der Liste zu nähern.

proto Die Nummer Deines Protokolls, siehe ‘/etc/protocols’.

name Der Name Deines Protokolls. Das ist der Name, den der Anwender sehen wird; meistens ist es das beste, den entsprechenden Namen aus ‘/etc/protocols’ zu verwenden.

pkt to tuple Die Funktion, die die protokollspezifischen Teile des Tupels ausfüllt, sobald ihr das Paket übergeben wird. Der ‘datah’ Pointer zeigt auf den Anfang des Headers (direkt hinter der IP-Adresse), und datalen ist die Länge des Pakets. Es liefert NULL, wenn das Paket nicht lang genug ist, um Header-Informationen zu enthalten; datalen wird (gezwungenermaßen) immer mindestens 8 Byte lang sein. invert tuple

Diese Funktion wird einfach dazu verwendet, den protokollspezifischen Teil des Tupels so zu ändern, wie eine Antwort auf dieses Paket aussehen würde.

print tuple Diese Funktion wird verwendet, um den protokollspezifischen Teil eines Tupels auszudrucken; gewöhnlich wird es mit sprintf() in den mitgegebenen Buffer geschrieben. Geliefert wird die Anzahl von verwendeten Buffer-Zeichen. Dies wird verwendet, um den Status des /proc Eintrags zu drucken.

print conntrack Diese Funktion wird benutzt, um den privaten Teil der Conntrack Struktur (wenn vorhanden) zu drucken. Sie wird auch verwendet, um den Status unter /proc zu drucken.

packet Diese Funktion wird aufgerufen, wenn ein Paket erkannt wird, das Teil einer aufgebauten Verbindung ist. Du bekommst einen Pointer auf die Conntrack Struktur, den IP-Header, die Länge und ctinfo. Du gibt ein Urteil über das Paket zurück (gewöhnlich NF ACCEPT), oder -1 wenn das Paket kein gültiger Teil der Verbindung ist. Innerhalb dieser Funktion kannst Du, wenn Du willst, diese Verbindung löschen, Du solltest aber folgendes Idiom verwenden, um races zu vermeiden:

     if (del_timer(&ct->timeout))
             ct->timeout.function((unsigned long)ct);

new Diese Funktion wird aufgerufen, wenn ein Paket zum ersten Mal eine Verbindung aufbaut; es gibt kein ctinfo arg, da das erste Paket der Definition nach ctinfo IP CT NEW ist. Wenn es beim Verbindungsaufbau scheitert, liefert die Funktion 0, oder im selben Moment ein Connection Timeout.

Sobald Dein fertig bist und getestet hast, dass Du Dein neues Protokoll einsetzen kannst, ist es an der Zeit, NAT beizubringen, wie es zu übersetzen ist. Dies bedeutet, ein neues Modul zu schreiben; eine Erweiterung zum NAT-Code und die ‘ip conntrack protocol’ Struktur, die Du an ‘ip conntrack register protocol()’ übergeben mußt.

list Setz es auf ‘{NULL, NULL}’; verwendet, um sich der Liste zu nähern.

name Der Name Deines Protokolls. Das ist der Name, den der Anwender sehen wird; meistens ist es das beste, den entsprechenden Namen aus ‘/etc/protocols’ zu verwenden, um auto-loading zu ermöglichen, wie wir später noch sehen werden.

protonum Die Nummer Deines Protokolls, siehe ‘/etc/protocols’.

manip pkt Das ist die andere Hälfte der von Conntection Tracking benutzten ‘ptk to tuple’ Funktion: Stell sie Dir als “tuple to ptk” vor. Trotzdem gibt es ein paar Unterschiede: Du bekommst einen Pointer auf den Anfang des IP-Headers, und die Gesamtlänge des Pakets. Das liegt daran, dass manche Protokolle (UDP, TCP) den IP-Header kennen müssen. Dir wird das ‘ip nat tuple manip’ Feld des Tupels gegeben (ich meine das “src” Feld) statt des ganzen Tupels und der Art der Manipulation, die auszuführen ist. in range

Diese Funktion wird verwendet, um zu sagen, ob manipulierbarer Teil des gegebenen Tupels im gegebenen Bereich liegt. Diese Funktion ist ein bißchen trickreich: Wir erhalten die Art der Manipulation, die auf das Tupel angewandt wurde, die uns sagt, wie wir den Bereich interpretieren können (ist es ein Quell- oder ein Zielbereich, auf den wir abzielen?). Diese Funktion wird benutzt, um zu überprüfen, ob ein existierendes Mapping uns in den richtigen Bereich bringt, außerdem noch, um zu überprüfen, ob vielleicht überhaupt keine Manipulation nötig ist.

unique tuple Diese Funktion ist das Herzstück von NAT: Wir erhalten ein Tupel und einen Bereich und sollen den Per-Protocol Teil des Tupels verändern, um es im Bereich zu plazieren und somit einzigartig zu machen. Wenn wir kein unbenutztes Tupel in diesem Bereich finden, wird 0 geliefert. Außerdem bekommen wir einen Pointer auf die Conntrack Struktur, welcher für ip nat user tuple() benötigt wird. Gewöhnlich wird einfach der Per-Protocol Teil des Tupels durch den Bereich iteriert, und ‘ip nat user tupel()’ wird solange überprüft, bis es einmal false liefert. Beachte, dass der Null-Mapping Fall bereits überprüft wurde: Entweder liegt er außerhalb des gegebeben Bereichs, oder er ist schon besetzt. Wenn IP NAT RANGE PROTO SPECIFIED gesetzt ist, bedeutet dies, dass der Anwender NAT, und nicht NAPT macht: Etwas Vernünftiges mit dem Bereich tun. Wenn kein Mapping erwünscht ist (In TCP zum Beispiel sollte kein Ziel-Mapping den TCP-Port ändern, wenn es nicht ausdrücklich verlangt wird), wird 0 geliefert.

print Given a character buffer, a match tuple and a mask, write out the per-protocol parts and return the length of the buffer used.

print range Mit einem gegebenen Charakter-Buffer, einem Match-Tupel und einer Maske wird dies den Per-Protocol Teil ausdrucken und die Länge des Buffers liefern.

4.4.3 Neue NAT-Targets

Das ist der wirklich interessante Teil. Du kannst neue NAT-Targets schreiben, die einen neuen Mapping-Typ bieten: Zwei Extra-Targets werden im Standard Package geliefert: MASQUERADE und REDIRECT. Diese illustrieren recht einfach das Potential und die Macht des Schreibens neuer NAT-Targets.

Sie werden genauso wie alle anderen iptables Targets geschrieben, nur intern werden sie ‘ip nat setup info()’ aufrufen und die Verbindung extrahieren.

4.4.4 Protokoll-Hilfen für TCP und UDP

Dieser Teil befindet sich noch in der Entwicklung.

4.5 Understanding Netfilter

Netfilter ist ziemlich simpel und wurde in den vorangegangenen Sektionen recht ausführlich beschrieben. Wie auch immer, manchmal ist es notwendig, weiter zu gehen als die NAT oder die ip tables Infrastruktur bietet, oder vielleicht möchtest Du sie auch vollständig ersetzen.

Ein wichtiger Punkt bei Netfilter (Naja, in der Zukunft) ist Caching. Jedes skb hat ein ‘nfcache’ Feld: Eine Bitmaske, die sagt, welche Felder im Header untersucht wurden und ob das Paket verändert wurde oder nicht. Die Idee ist, dass auf jeden Netfilter Hook in den dazu relevanten Bits eine OR-Verknüpfung angewandt werden kann, so dass wir später ein Cache-System schreiben können, das clever genug sein wird, um zu erkennen, wann Pakete nicht an Netfilter gereicht werden müssen.

Die wichtigsten Bits sind NFC ALTERED, was bedeutet, dass das Paket verändert wurde (Dies wird für den NF IP LOCAL OUT Hook von IPv4 bereits verwendet, um geänderte Pakete erneut zu routen), und NFC UNKNOWN, was aussagt, dass Caching nicht angewandt werden sollte, da einige Eigenschafte untersucht wurden, die nicht ausgedrückt werden konnten. Im Zweifelsfall solltest Du das NFC UNKNOWN Flag in das nfcache Feld von skb in Deinem Hook setzen.

4.6 Neue Netfilter-Module schreiben

4.6.1 Netfilter-Schnittstellen benutzen

Um Pakete im Kernel zu empfangen/behandeln, kannst Du einfach ein Modul schreiben, welches beim “Netfilter Hook” registriert. Im Grunde ist das ein Ausdruck von Interesse an einem gegebenen Punkt; der genaue Punkt ist protokollspezifisch und wird in protokollspezifischen Netfilter-Headern, wie “netfilter ipv4.h”, definiert.

Um Netfilter Hooks zu registrieren (und das wieder aufzuheben), benutzt Du die ‘nf register hook’ und die ‘nf unregister hook’ Funktionen. Beide benötigen einen Pointer auf ‘struct nc hook ops’, welchen Du wie folgt benutzt:

list Setz es auf ‘{NULL, NULL}’; verwendet, um sich der Liste zu nähern.

hook Die Funktion, die aufgerufen wird, wenn ein Paket auf diesen Hook-Punkt trifft. Deine Funktion muß NC ACCEPT, NC DROP oder NC QUEUE liefern. Wenn NC ACCEPT geliefert wird, wird der nächste an diesen Punkt angehängte Hook aufgerufen werden. Wenn NC DROP geliefert wird, wird das Paket verworfen werden. Wenn NC QUEUE geliefert wird, wird das Paket gequeued werden. Wenn Du einen Pointer auf einen skb Pointer erhälst, kannst Du skb, wenn Du willst, vollständig ersetzen.

flush Zur Zeit unbenutzt: Es wurde entworfen, um Paket-Treffer weiterzureichen, wenn der Cache gelöscht wird. Vielleicht wird es niemals implementiert werden: Setz es auf NULL.

pf Die Protokollfamilie, zum Beispiel ‘PF INET’ für IPv4.

hooknum Die Nummer des Hooks, für den Du Dich interessierst, z.B. ‘NC IP LOCAL OUT’.

4.6.2 Eingereihte Pakete behandeln

Dies ist die zur Zeit von ‘ip queue’ verwendete Schnittstelle; Du kannst hier registrieren, um Pakete für ein gegebenes Protokoll zu behandeln. Hier herrscht eine ähnliche Semantik wie bei der Registrierung für einen Hook, außer, dass Du eine Behandlung des Pakets blockieren kannst. Außerdem siehst Du nur Pakete, für die ein Hook mit ‘NC QUEUE’ geantwortet hat.

Die zwei Funktionen, die verwendet werden, um Interesse an gequeueten Paketen zu registrieren, heißen ‘nc register queue handler()’ und ‘nf unregister queue handler()’. Die Funktion, die Du registrieren wirst, wird mit dem ‘void *’ Pointer aufgerufen werden, mit dem Du sie an ‘nf register queue handler()’ übergeben hast.

Wenn niemand registriert ist, ein Paket zu behandeln, ist das Liefern von NF QUEUE äquivalent zum Liefern von NF DROP.

Die Pakete werden eingereiht werden, sobald Du Dich dafür registriert hast. Du kannst mit ihnen tun, wasimmer Du willst, wenn Du aber fertig bist mit ihnen, mußt Du ‘nf reinject()’ aufrufen (benutz nicht einfach nur ‘kfree skb()’). Wenn Du ein skb reinjizierst, übergibst Du ihm das skb, das dem Queue-Handler gegebene ‘struct nf info’ und ein Urteil: NF DROP verwirft es, NF ACCEPT läßt es weiter durch die Hooks iterieren, NF QUEUE reiht sie erneut ein und NF REPEAT bewirkt, dass der Hook, der das Paket eingereiht hat, erneut konsultiert wird (Paß auf unendliche Loops auf!).

Du kannst in ‘struct nf info’ sehen, um hilfreiche Informationen über das Paket zu bekommen, z.B. über die Schnittstelle oder den betreffenden Hook.

4.6.3 Kommandos vom Anwender empfangen

Netfilter Komponenten wollen mit dem Anwender interagieren. Die Methode hierfür ist die Verwendung des setsockopt Mechanismus. Beachte, dass jedes Protokoll modifiziert werden muß, um nf setsockopt() für nicht verstandene setsockopt Nummern (und nf getsockopt() für getsockopt Nummern) aufrufen zu können, und das bis jetzt nur IPv4, IPv6 und DECnet modifiziert wurden.

Wir verwenden eine mittlerweile bekannte Technik: Wir registrieren ein ‘struct nf sockopt ops’, indem wir nf register sockopt() aufrufen. Die Felder dieser Struktur sind wie folgt:

list Setz es auf ‘{NULL, NULL}’; verwendet, um sich der Liste zu nähern.

pf Die Protokollfamilie, die Du behandelst, zum Beispiel PF INET.

set optmin und

set optmax Diese bestimmen den (exklusiven) Bereich der verwendeten setsockopt Nummern. 0 und 0 bedeutet also, dass Du keine setsockopt Nummern benutzt.

set This is the function called when the user calls one of your setsockopts. You should check that they have NET ADMIN capability within this function.

get optmin and

get optmax These specify the (exclusive) range of getsockopt numbers handled. Hence using 0 and 0 means you have no getsockopt numbers. get

Das ist die Funktion, die aufgerufen wird, wenn der User eine Deiner getsockopts aufruft. Du solltest überprüfen, ob es eine NET ADMIN Capability in dieser Funktion gibt.

Die zwei letzten Felder werden intern verwendet.

4.7 Behandlung von Paketen aus Sicht der Anwender

Durch das Verwenden der libipq Library und des ‘ip queue’ Moduls kann nun fast alles, was im Kernel getan werden kann, auch von Anwenderseite her geschehen. Das bedeutet, dass Du, mit etwas Geschwindigkeitsverlust, Deinen Code ausschließlich im Userspace entwickeln kannst. Wenn Du keine große Bandbreite filtern mußt, wirst Du das angenehmer finden, als die Pakete im Kernel zu behandeln.

In den frühen Tagen von Netfilter habe ich das bewiesen, indem ich eine Embryo-Version von iptables zum Userspace portierte. Netfilter öffnet den Leute die Tür, um ihre eigenen recht effizienten Module zu schreiben, und das in welcher Sprache sie wollen.

5 2.0er und 2.2er Paketfilter-Module übersetzen

Sieh Dir die ip fw compat.o Datei an; diese sollte eine Portierung recht einfach machen.

6 Die Testsuite

Auf dem CVS Server gibt es eine Test-Suite: Je größer der Bereich ist, den diese Testsuite abdeckt, desto größer wird Deine Zufriedenheit sein, zu sehen, dass Änderungen am Code nichts komplett zerbrochen haben. Triviale Tests sind mindestens so wichtig, wie trickreiche Tests: Es sind die trivialen Tests, die die komplexen vereinfachen (da Du weißt, dass die Grundlagen gut funktionieren, bevor die trickreichen Tests ausgeführt werden).

Die Tests sind einfach: Sie sind nur Shell-Scripte im /testsuite Verzeichnis, die erfolgreich sein sollen. Die Scripte werden in alphabetischer Reihenfolge ausgeführt, ‘01test’ kommt also vor ‘02test’. Zur Zeit gibt es fünf Testverzeichnisse:

00netfilter/ Generelle Tests zum Netfilter-Rahmenwerk.

01iptables/ Tests zu iptables.

02conntrack/ Tests zu Connection Tracking.

03NAT/ Tests zu NAT.

04ipchains-compat/ Tests zur Kompatibilität von ipchains/ipfwadm.

Im testsuite/ Verzeichnis gibt es ein Script mit dem Namen ‘test.sh’. Es konfiguriert zwei Dummy-Schnittstellen (tap0 und tap1), aktiviert Forwarding und beseitigt alle Netfilter-Module. Dann geht es durch die oberen Verzeichnisse und führt dort alle test.sh Scripte aus, bis eins davon nicht funktioniert. Dieses Script hat zwei optionale Argumente: ‘-v’ druckt jeden gerade ausgeführten Test aus. Außerdem kann ein optionaler Testname angegeben werden: Ist dieser gegeben, werden alle anderen Tests ausgelassen, bis dieser eine gefunden wird.

6.1 Einen Test schreiben

Erstelle in dem betreffenden Verzeichnis eine neue Datei: Versuche, Deinen Test so zu numerieren, dass er zur richtigen Zeit ausgeführt wird. Um zum Beispiel ICMP Reply Tracking zu testen (02conntrack/02reply.sh), müssen wir zuerst sicher sein, dass ausgehende ICMP Pakete sauber getrackt werden (02conntrack/01simple.sh).

Gewöhnlich ist es besser, mehrere kleine Dateien zu erstellen, von denen jede einen Bereich abdeckt, da das hilft, Probleme für die Leute, die auf der Testsuite arbeiten, sofort zu isolieren.

Wenn etwas in dem Test schief läuft, gib ein ‘exit 1’, das verursacht einen Fehler; wenn dies etwas ist, das Du schon erwartet hast, solltest Du eine eindeutige Meldung hinterlassen. Dein Test sollte mit ‘exit 0’ enden, wenn alles gut läuft. Du solltest den Erfolg eines jeden einzelnen Befehls überprüfen, indem an den Anfang des Scripts die Option ‘set -e’ setzt oder indem Du ein ‘|| exit 1’ an jeden Befehl anhängst.

Die Hilfsfunktionen ‘load module’ und ‘remove module’ können verwendet werden, um Module zu laden: Du solltest Dich nicht auf das automatische Laden von Modulen in der Testsuite verlassen, es sei denn, das ist es spezifisch, was Du testen willst.

6.2 Variablen und Umgebung

Du hast zwei Schnittstellen zum Spielen: tap0 und tap1. Ihre Schnittstellenadressen stehen jeweils in $TAP0 und $TAP1. Beide haben die Netzmaske 255.255.255.0; ihre Netzwerke stehen jeweils in $TAP0NET und $TAP1NET.

Es gibt eine leere Temporary Datei in $TMPFILE. Sie wird am Ende Deines Tests gelöscht.

Dein Script wird aus dem /testsuite Verzeichnis heraus gestartet werden, woimmer das auch sein mag. Also solltest Du Tools (wie iptables) mit Pfadnamen wie ‘../userspace’ verwenden.

Dein Script kann mehr Informationen ausdrucken, wenn $VERBOSE gesetzt ist (was bedeutet, dass der User ein ‘-v’ auf der Kommandozeile angegeben hat.

6.3 Nützliche Tools

Es gibt verschiedene nützliche Testsuite-Tools im Unterverzeichnis ‘Tools’: Jedes davon endet mit einem Nicht-Null Exit Status, wenn es ein Problem gibt.

6.3.1 gen ip

Du kannst IP-Pakete mit ‘gen ip’ generieren, was IP-Pakete an Standard-Input liefert. Du kannst IP-Pakete an tap0 und tap1 liefern, indem Du Standard-Output an /dev/tap0 oder /dev/tap1 sendest (Falls diese nicht existieren sollten, werden sie beim ersten Verwenden der Testsuite erstellt).

gen ip ist ein einfaches Programm, das zur Zeit sehr penibel mit der Reihenfolge seiner Argumente ist. Zuerst gibt es die allgemeinen optionalen Argumente:

FRAG=offset,length

Erstelle ein Paket, zerlege es dann in Fragmente von folgendem Offset und folgender Länge. MF

Setz das ‘More Fragments’ Bit in dem Paket. MAC=xx:xx:xx:xx:xx:xx

Setz die Quell-MAC-Adresse des Pakets. TOS=tos

Setz das TOS-Feld im Paket (0 - 255).

Als nächstes kommen die erforderlichen Argumente:

source ip

Quell-IP-Adresse des Pakets. dest ip

Ziel-IP-Adresse des Pakets. length

Gesamtlänge des Pakets, einschließlich Header. protocol

Protokollnummer des Pakets, 17 ist zum Beispiel UDP.

Die Argumente hängen dann vom Protokoll ab: für UDP (17) gibt es Quell- und Ziel-Portnummer. Für ICMP (1) gibt es Typ und Code der ICMP Meldung: ist der Typ 0 oder 8 (ping-Antwort oder ping), werden zwei zusätzliche Argumente (das ID und das Sequenzfeld) benötigt. Für TCP werden Quell- und Zielport, sowie Flags (“SYN”, “SYN/ACK”, “ACK”, “RST” or “FIN”) benötigt. Hier gibt es drei optionale Argumente: “OPT=”, gefolgt von einer durch Kommata getrennten Liste von Optionen, “SYN=” gefolgt von einer Sequenznummer, und “ACK=” gefolgt von einer Sequenznummer. Schließlich sagt das optionale Argument “DATA”, dass der Payload des TCP-Pakets mit den Inhalt von Standard-Input gefüllt werden soll.

6.3.2 rcv ip

Du kannst Dir IP-Pakete mit rcv ip ansehen, was eine Kommandozeile ausgibt, die so nah wie möglich an die Originalwerte herankommt, die an gen ip übergeben wurden (mit Ausnahme von Fragmenten). Das ist extrem nützlich für das Analysieren von Paketen. Es erfordert zwei Argumente:

wait time

Die maximale Zeit in Sekunden, um auf ein Paket von Standard Input zu warten. iterations

Die Anzahl der zu empfangenden Pakete.

Es gibt ein optionales Argument “DATA”, was den Payload des Pakets nach dem Paketheader auf Standard-Output ausdruckt. Der Standardweg, rcv ip in einem Shellscript zu verwenden, ist wie folgt:

# Jobkontrolle aufsetzen, damit mir $ in Shellscripten verwenden können.
set -m
# Zwei Sekunden auf ein Paket von tap0 warten.
../tools/rcv_ip 2 1 < /dev/tap0 > $TMPFILE &
# Sichergehen, dass rvp_ip läuft.
sleep 1
# Ein ping-Paket senden
../tools/gen_ip $TAP1NET.2 $TAP0NET.2 100 1 8 0 55 57 > /dev/tap1 || exit 1
# Auf rvp_ip warten
if wait %../tools/rcv_ip; then :
else
    echo rcv_ip failed:
    cat $TMPFILE
    exit 1
fi

6.3.3 gen err

Dieses Programm nimmt ein Paket (wie zum Beispiel von ip gen generiert) und wandelt es in einen ICMP Fehler um.

Es hat drei Argumente: Die Quell-IP-Adresse, einen Typ und einen Code. Die Ziel-IP-Adresse wird auf die Quell-IP-Adresse des von Standard-Input übergebenen Pakets gesetzt.

6.3.4 local ip

Dies nimmt ein Paket von Standard-Input und injiziert es von einem einfachen Socket ins System. Dies gibt ihm den Anschein eines lokal-generierten Pakets (anders, als ein Paket an eines der Ethertap Devices zu übergeben, was wie ein Paket aussehen würde, das woanders generiert wurde).

6.4 Einige Ratschläge

All diese Tools nehmen an, dass sie alles mit einem Lesen und Schreiben tun können: Dies ist wahr für die Ethertap Devices, könnte aber nicht wahr sein, wenn Du etwas Trickreiches mit Pipes machst.

dd can verwendet werden, um Pakete auszuschneiden: dd hat eine obs (output block size) Option, die verwendet werden kann, um Pakete mit einem einzigen Schreiben auszugeben.

Teste zuerst auf Erfolg: z.B. ein Test darauf, ob Pakete erfolgreich geblockt werden. Teste zuerst, ob Pakete normal durchgehen, teste anschließend, ob Pakete geblockt werden. Andernfalls könnte ein Fehler, der nichts damit zu tun hat, die Pakete aufhalten...

Versuche, exakte Tests zu schreiben, keine ‘Schreib zufälliges Zeug und warte ab, was passiert’ Tests. Wenn ein exakter Tests nicht funktioniert, ist es sehr nützlich, das zu wissen. Wenn ein zufälliger Test nicht funktioniert, hilft das nicht viel.

Wenn ein Tests ohne ein Meldung nicht gelingt, kannst Du ein ‘-x’ (ich meine ‘#! /bin/sh -x’) in die erste Zeile des Scripts setzen, um zu sehen, welches Kommando gerade ausgeführt wird.

Wenn ein Test mal gelingt, mal nicht, überprüfe zufälligen Netzwerkverkehr, der dazwischenkommen könnte (versuche, alle externen Schnittstellen zu deaktivieren). Da ich an das gleiche Netzwerk wie Andrew Trigell angeschlossen bin, werde ich zum Beispiel ständig von Windows Broadcasts geplagt.

7 Motivation

Als ich ipchains entwickelte, merkte ich (in einem dieser Flash-Momente-während-man-auf-Einlaß-wartet in einem chinesischen Restaurant), dass Paketfiltern an der falschen Stelle ansetzte. Ich kann es jetzt nicht mehr finden, aber ich erinnere mich daran, dass ich eine Mail an Alan Cox schrieb, der mir antwortete ‘Warum beendest Du nicht erst das, was Du im Moment tust, obwohl Du wahrscheinlich recht hast’. Um es kurz zu sagen, sollte Pragmatismus über die Richtige Sache siegen.

Als ich ipchains, was ursprüngliche eine kleine Modifikation des Kernelteils von ipfwadm war, und dann doch eine größere Neu-Überarbeitung wurde, beendete und das HOWTO schrieb, bemerkte ich die Verwirrung, die in der großen Linux-Community über Dinge wie Paketfiltern, Masquerading, Portforwarding und ähnliches herrscht.

Das ist das Gute daran, wenn Du Dein eigener Support bist: Du bekommst ein besseres Gefühl dafür, was die User versuchen zu tun, und womit sie noch straucheln. Es ist die größte Auszeichnung für Freie Software, wenn sie von vielen Usern eingesetzt wird (darum geht es, richtig?), und das bedeutet, dass man es leicht machen muss. Die Architektur, und nicht die Dokumenation, ist der Schlüssel hierzu.

Ich hatte also die Erfahrung, sowohl mit dem ipchains-Code, als auch mit einer guten Vorstellung davon, was die Leute dort draußen taten. Es gab nur zwei Probleme:

Zuerst wollte ich nicht in den Security-Bereich zurückgehen. Ein Security-Consultant zu sein ist ein konstantes moralischen Tauziehen zwischen Deinem Gewissen und Deinem Konto. Auf einem fundamentalen Level verkaufst Du das Gefühl von Sicherheit, was alles andere ist als wirkliche Sicherheit. Vielleicht wäre es anders, in einer Militäreinrichtung zu arbeiten, wo man Sicherheit versteht.

Das zweite Problem ist, dass es nicht nur um Newbies geht; eine wachsende Anzahl von großen Unternehmen und ISPs benutzen dieses Zeug. Ich brauchte zuverlässige Informationen von dieser Klasse von Benutzern, wenn es sich auf die Home-User von Morgen ausbreiten sollte.

Diese Probleme wurden gelöst, als ich auf der Usenix 1998 auf David Bonn von Watchguard traf. Sie suchten einen Linux-Kernel Programmierer; Im Ende einigten wir uns darauf, dass ich für einen Monat zu ihrer Niederlassung in Seattle kommen würde, wo wir sehen würde, ob wir eine Vereinbarung rausschlagen könnten, in der sie meinen neuen Code und meine jetzige Support-Arbeit sponsorn würden. Das Gehalt, das sie mir anboten, war mehr als das, nachdem ich gefragt hatte, ich hatte also keine Einnahmeverluste. Das bedeutet, dass ich mir für eine Weile keine Sorgen um Geld zu machen brauche.

Die Nähe zu Watchguard gab mir die Nähe zu den großen Clients, die ich brauchte, und dass ich unabhänging von ihnen war, erlaubte mir, alle User (z.b. Watchguard Competitors) gleich zu supporten.

Ich hätte also einfach Netfilter schreiben und ipchains portieren können und wäre fertig damit gewesen. Leider hätte das den ganzen Masquerading-Code im Kernel gelassen: Die Unabhängigkeit zwischen Paketfiltern und Masquerading ist ein wichtiger Punkt beim Paketfiltern, aber Masquerading muß auch auf das Netfilter-Rahmenwerk übertragen werden.

Meine Erfahrung mit dem ipfwadm ‘Schnittstellen-Adressierungs’ Feature hat mich auch gelehrt, dass es keine Hoffnung dafür gab, den Masquerading Code einfach auszulassen und zu erwarten, dass ihn jemand brauchte und somit selbst die Arbeit einer Portierung zu Netfilter übernehmen würde.

Ich brauchte also mindestens soviele Features wie im damaligen Code; vorzugsweise ein paar mehr, um mutige und kreative User zu ermutigen, es mir nachzumachen. Dies bedeutete, transparente Proxies (zum Glück!), Masquerading und Port-Forwarding zu ersetzen. Mit anderen Worten, ein komplettes NAT-Layer.

Sogar, wenn ich mich dazu entschieden hätte, das existierenden Masquerading Layer zu Portieren, anstatt ein generisches NAT-System zu schreiben, zeigte der Masquerading Code doch sein Alter und den Mangel an Wartung. Es gab keinen Masquerading Maintainer, und das zeigte sich jetzt. Es scheint, dass ernsthafte User Masquerading generell nicht verwenden und dass es nicht viele Home-User gibt, die die Arbeit der Wartung übernehmen würden. Tapfere Leute wie Juan Ciarlante schrieben Fixes, aber der Punkt (der immer weiter nach hinten verschoben wurde) war erreicht, an dem eine komplette Überarbeitung nötig war.

Beachte bitte, das nicht ich die Person war, die NAT überarbeitet hat: Ich verwendete Masquerading nicht mehr und hatte den existierenden Code zu der Zeit nicht studiert. Das ist wahrscheinlich der Grund, warum es länger dauerte, als es dauern sollte. Aber das Resultat ist ziemlich gut, meiner Meinung nach, und ich habe sicher verdammt viel gelernt. Ohne Zweifel wird die zweite Version noch besser werden, wenn wir sehen, wie die Leute es einsetzen.

8 Danke

Danke denen, die geholfen haben.