Kapitel 14. Netzwerk-Treiber

Inhalt
Das Design von snull
Mit dem Kernel verbinden
Die Struktur net_device im Detail
Öffnen und Schließen
Paket-Übertragung
Paket-Empfang
Der Interrupt-Handler
Änderungen im Verbindungszustand
Die Socket-Buffer
MAC-Adreßauflösung
Eigene ioctl-Befehle
Statistische Informationen
Multicasting
Abwärtskompatibilität
Schnellreferenz

Wir lassen jetzt die Zeichen- und Block-Treiber hinter uns und begeben uns in die faszinierende Welt der Netzwerke. Netzwerk-Schnittstellen sind die dritte Standardklasse von Linux-Geräten, und in diesem Kapitel wird beschrieben, wie sie mit dem Rest des Kernels zusammenarbeiten.

Die Rolle einer Netzwerk-Schnittstelle im System ähnelt der eines eingehängten Block-Geräts. Ein Block-Gerät registriert seine Fähigkeiten im Array blk_dev und in anderen Kernel-Strukturen. Danach “überträgt” und “empfängt” es auf Anfrage Blocks mit seiner request-Funktion. Analog dazu muß sich auch eine Netzwerk-Schnittstelle in bestimmten Datenstrukturen registrieren, damit sie aufgerufen wird, wenn Pakete mit der Außenwelt ausgetauscht werden sollen.

Es gibt einige wichtige Unterschiede zwischen eingehängten Festplatten und Schnittstellen zur Paket-Übertragung. Zunächst einmal wird eine Festplatte durch eine Gerätedatei im Verzeichnis /dev repräsentiert, während eine Netzwerk-Schnittstelle keine solche Einsprungstelle hat. Die normalen Datei-Operationen (Lesen, Schreiben usw.) sind bei Netzwerk-Schnittstellen nicht sinnvoll, weswegen man hier nicht den üblichen Unix-Ansatz “alles ist eine Datei” anwenden kann. Daher existieren Netzwerk-Schnittstellen in einem eigenen Namensraum und exportieren einen eigenen Satz von Operationen.

Sie mögen vielleicht einwerfen, daß Applikationen die Systemaufrufe read und write verwenden, wenn sie Sockets benutzen, aber diese arbeiten auf einer Art Software-Objekt, das nicht die Schnittstelle selbst ist. Mehrere hundert Sockets können gleichzeitig auf der gleichen physikalischen Schnittstelle arbeiten.

Der wichtigste Unterschied ist aber, daß Block-Treiber nur auf Anforderung durch den Kernel arbeiten, wohingegen Netzwerk-Treiber auch asynchron Pakete von außen empfangen. Während also ein Block-Treiber gebeten wird, einen Puffer an den Kernel zu schicken, bittet das Netzwerk-Gerät darum, eingehende Pakete an den Kernel weiterreichen zu dürfen. Die Kernel-Schnittstelle für Netzwerk-Treiber ist auf diese andere Betriebsart ausgerichtet.

Netzwerk-Treiber müssen auch darauf eingestellt sein, eine Reihe von administrativen Aufgaben wie das Setzen von Adressen, das Ändern von Übertragungsparametern und die Pflege von Statistiken über übertragene Daten und Fehler zu unterstützen. Die API für Netzwerk-Treiber spiegelt diese Bedürfnisse wider und sieht daher etwas anders aus als die Schnittstellen, die wir bisher kennengelernt haben.

Das Netzwerk-Subsystem des Linux-Kernels ist vollständig protokollunabhängig entworfen worden. Das gilt sowohl für Netzwerk-Protokolle (IP, IPX oder andere Protokolle) als auch für Hardware-Protokolle (Ethernet, Token Ring und andere). Die Interaktion zwischen dem Netzwerk-Treiber und dem eigentlichen Kernel geschieht paketweise. Damit können Protokoll-Fragen vor dem Treiber und die physikalische Übertragung vor dem Protokoll versteckt werden.

In diesem Kapitel wird beschrieben, wie die Netzwerk-Schnittstellen in den Rest des Linux-Kernels passen, und eine speicherbasierte modularisierte Netzwerk-Schnittstelle namens — Sie werden es sich gedacht haben — snull vorgeführt. Um die Diskussion zu vereinfachen, benutzt die Schnittstelle das Ethernet-Hardware-Protokoll und überträgt IP-Pakete. Das Wissen, das Sie anhand von snull erwerben, kann leicht auf andere Protokolle als IP angewendet werden, und das Schreiben von Nicht-Ethernet-Treibern unterscheidet sich nur durch kleine Details, die für das jeweilige Protokoll spezifisch sind.

Wir sprechen in diesem Kapitel nicht über IP-Nummern-Vergabe, Netzwerkprotokolle oder andere allgemeine Netzwerkkonzepte. Solche Themen betreffen Treiber-Autoren (normalerweise) nicht, und es wäre ohnehin unmöglich, in weniger als einigen hundert Seiten einen befriedigenden Überblick über diese Themen zu geben.

Das Netzwerk-Subsystem ist in den letzten Jahren mehrfach verändert worden, weil die Kernel-Entwickler versucht haben, die bestmögliche Performance herauszuholen. Der Großteil dieses Kapitels behandelt Netzwerk-Treiber für den 2.4-Kernel, aber die Code-Beispiele funktionieren wieder einmal auch für 2.0- und 2.2-Kernel. Wir behandeln die Unterschiede zwischen den Kernel-Versionen am Ende des Kapitels.

Eine Anmerkung zur Terminologie, bevor wir in die Welt der Netzwerk-Geräte einsteigen: In der Netzwerk-Welt verwendet man den Begriff Oktett für eine Gruppe von acht Bits. Dies ist im allgemeinen die kleinste Einheit, die Netzwerk-Geräte und Protokolle kennen. Der Begriff Byte wird in diesem Kontext fast nie verwendet. Wir werden hier daher auch von Oktetten sprechen.

Das Design von snull

In diesem Abschnitt werden die Design-Überlegungen beschrieben, die beim Entwurf von snull erfolgten. Auch wenn diese Informationen vielleicht zunächst nur marginal nützlich erscheinen, könnten Sie beim Spielen mit dem Beispiel-Code auf Probleme stoßen, wenn Sie diese Überlegungen nicht verstehen.

Die erste und wichtigste Entwurfsentscheidung ist die, daß Beispiel-Schnittstellen nicht an real existierende Hardware gebunden sein sollten — genau wie der gesamte Beispiel-Code dieses Buches. Aus dieser Einschränkung ist etwas hervorgegangen, das der Loopback-Schnittstelle ähnelt. snull ist aber keine Loopback-Schnittstelle, sondern simuliert Konversationen zwischen echten entfernten Hosts, um das Schreiben von Netzwerk-Treibern besser zu demonstrieren. Der Linux-Loopback-Treiber ist sehr einfach; Sie finden ihn in drivers/net/loopback.c.

Ein weiteres wichtiges Merkmal von snull ist die Funktion als IP-Schnittstelle. Das ist eine Folge der internen Verarbeitungsvorgänge in der Schnittstelle — snull muß sich die Pakete anschauen und interpretieren, um ein Paar von Hardware-Schnittstellen korrekt emulieren zu können. Echte Schnittstellen sind unabhängig vom übertragenen Protokoll, und diese Einschränkung von snull hat auch keinen Einfluß auf den Beispiel-Code in diesem Kapitel, der protokollunabhängig ist.

Zuweisen von IP-Nummern

Das Modul snull erzeugt zwei Schnittstellen. Diese unterscheiden sich von einer einfachen Loopback-Schnittstelle dadurch, daß alles, was über die eine Schnittstelle übertragen wird, auf der anderen ankommt, und nicht auf sich selbst. Es wirkt so, als hätten Sie zwei Verbindungen nach außen, aber in Wirklichkeit antwortet Ihr Computer nur sich selbst.

Leider kann dieser Effekt nicht allein durch Zuweisung von IP-Nummern erreicht werden, weil der Kernel kein Paket über die Schnittstelle A schicken würde, das an seine eigene Schnittstelle B gerichtet ist. Statt dessen würde es den Loopback-Kanal verwenden, ohne die Daten durch snull zu schicken. Um trotzdem eine Kommunikation über die snull-Schnittstellen zu bekommen, müssen die Quell- und Zieladressen während der Übertragung modifiziert werden. Pakete, die durch eine der Schnittstellen geschickt werden, sollten also von der anderen empfangen werden, der Empfänger des ausgehenden Paketes sollte aber nicht als der lokale Rechner erkannt werden. Das gleiche gilt für die Quelladresse der empfangenen Pakete.

Um dieses “versteckte Loopback” zu erreichen, schaltet die snull-Schnittstelle das niedrigstwertige Bit des dritten Oktetts der Quell- und Zieladressen um. Es wird also sowohl die Netzwerk-Nummer als auch die Host-Nummer von Klasse-C-IP-Nummern geändert. Dadurch erscheinen Pakete, die an das Netzwerk A (das mit sn0, der ersten Schnittstelle, verbunden ist) gerichtet sind, auf der Schnittstelle sn1 als Pakete an das Netzwerk B.

Um hier nicht mit zu vielen Nummern hantieren zu müssen, geben wir den verwendeten IP-Nummern symbolische Namen:

  • snullnet0 ist das Klasse-C-Netzwerk, das mit der Schnittstelle sn0 verbunden ist. Entsprechend ist snullnet1 das Netzwerk von sn1. Die Adressen dieser Netzwerke sollten sich nur im niedrigstwertigen Bit des dritten Oktetts unterscheiden.

  • local0 ist die IP-Adresse von sn0, sie liegt in snullnet0. Die Adresse von sn1 ist local1. local0 und local1 müssen sich im rechts stehenden Bit sowohl im dritten als auch im vierten Oktett unterscheiden.

  • remote0 ist ein Host in snullnet0, dessen viertes Oktett gleich dem von local1 ist. Jedes Paket, das an remote0 geschickt wird, kommt an local1 an, nachdem seine Klasse-C-Adresse vom Schnittstellen-Code modifiziert worden ist. Der Host remote1 gehört zu snullnet1, und sein viertes Oktett ist identisch mit dem von local0.

Die Arbeit der snull-Schnittstellen ist in Abbildung 14-1 dargestellt, wobei die Hostnamen, die zu jeder Schnittstelle gehören, neben dem Namen der Schnittstellen angegeben sind.

Abbildung 14-1. Wie ein Host seine Schnittstellen sieht

Hier folgen einige mögliche Werte für die Netzwerk-Nummern. Wenn Sie diese Zeilen in /etc/networks ergänzen, können Sie die Netzwerke auch namentlich angeben. Die Werte wurden aus dem Nummernbereich genommen, der für die private Nutzung reserviert ist.



snullnet0       192.168.0.0
snullnet1       192.168.1.0

Die folgenden Nummern sind mögliche Host-Nummern, die Sie in /etc/hosts eintragen können:



192.168.0.88   local0
192.168.0.99   remote0
192.168.1.99   local1
192.168.1.88   remote1

Das Wichtige an diesen Nummern ist, daß der Host-Anteil von local0 gleich dem Host-Anteil von remote1 und der Host-Anteil von local1 gleich dem Host-Anteil von remote0 ist. Sie können völlig andere Nummern verwenden, solange diese Beziehung erhalten bleibt.

Seien Sie aber vorsichtig, wenn Ihr Rechner bereits mit einem Netzwerk verbunden ist. Die von Ihnen gewählten Nummern könnten echte Internet- oder Intranet-Adressen sein, und die Zuweisung an Ihre Schnittstellen würde eine Kommunikation mit den eigentlichen Adressen-Inhabern verhindern. Und auch wenn die oben gezeigten Nummern keine weiterleitbaren Internet-Nummern sind, könnten sie bereits von Ihrem privaten Netzwerk verwendet werden, falls dieses hinter einer Firewall liegt.

Welche Nummern Sie auch wählen, Sie können die Schnittstellen durch Eingabe der folgenden Befehle einrichten:


ifconfig sn0 local0
ifconfig sn1 local1
case "`uname -r`" in 2.0.*)
    route add -net snullnet0 dev sn0
    route add -net snullnet1 dev sn1
esac

Ab Kernel 2.2 ist es nicht notwendig, route aufzurufen, weil dies automatisch geschieht. Wenn der gewählte Adreßbereich nicht im Klasse-C-Bereich liegt, müssen Sie netmask 255.255.255.0 hinzufügen.

An dieser Stelle kann das “entfernte” Ende der Schnittstelle bereits erreicht werden. Der Bildschirmauszug zeigt, wie mein Rechner remote0 und remote1 über snull erreicht:


morgana% ping -c 2 remote0
64 bytes from 192.168.0.99: icmp_seq=0 ttl=64 time=1.6 ms
64 bytes from 192.168.0.99: icmp_seq=1 ttl=64 time=0.9 ms
2 packets transmitted, 2 packets received, 0% packet loss

morgana% ping -c 2 remote1
64 bytes from 192.168.1.88: icmp_seq=0 ttl=64 time=1.8 ms
64 bytes from 192.168.1.88: icmp_seq=1 ttl=64 time=0.9 ms
2 packets transmitted, 2 packets received, 0% packet loss

Beachten Sie, daß Sie keinen anderen Host in den beiden Netzwerken erreichen können, weil das Paket von Ihrem Rechner verworfen wird, nachdem die Adresse modifiziert und das Paket empfangen wurde. Ein Paket für 192.168.0.32 verläßt den Rechner beispielsweise durch sn0 und erscheint wieder bei sn1 mit der Ziel-Adresse 192.168.1.32, also keiner lokalen Adresse des Host-Rechners.

Die physikalische Übertragung der Pakete

Was die Datenübertragung angeht, gehören die snull-Schnittstellen zur Klasse der Ethernet-Schnittstellen.

snull emuliert Ethernet, weil die überwältigende Mehrheit der existierenden Netzwerke — zumindest die Segmente, mit denen eine Workstation verbunden ist, auf der Ethernet-Technologie basieren, sei es nun 10Base2, 10BaseT oder 100BaseT. Außerdem stellt der Kernel bereits eine verallgemeinerte Unterstützung für Ethernet-Geräte bereit, und es gibt keinen Grund, diese nicht zu verwenden. Der Vorteil eines Ethernet-Gerätes ist so offensichtlich, daß selbst die plip-Schnittstelle (die Schnittstelle, die den Drucker-Port verwendet), sich selbst als Ethernet-Gerät deklariert.

Es gibt noch einen letzten Vorteil der Verwendung von Ethernet in snull: Sie können die Schnittstelle mit tcpdump überprüfen, um die vorbeilaufenden Pakete im zu beobachten. Das Beobachten von Schnittstellen mit tcpdump ist eine hilfreiche Möglichkeit, um zu sehen, wie die beiden Schnittstellen arbeiten. (Beachten Sie, daß tcpdump auf 2.0-Kerneln nicht richtig funktioniert, falls die Schnittstellen von snull nicht als ethx erscheinen. Laden Sie den Treiber mit der Option eth=1, um die normalen Ethernet-Namen anstelle der Default snx-Namen zu verwenden.)

Wie bereits erwähnt, arbeitet snull nur mit IP-Paketen. Diese Beschränkung ist eine Folge der Tatsache, daß snull in den Paketen herumschnüffelt und diese sogar modifiziert, damit der Code funktioniert. Der Code verändert die Quelle, das Ziel, und die Prüfsumme im IP-Kopf jedes Pakets, ohne zu überprüfen, ob das Paket eigentlich wirklich IP-Informationen enthält. Diese Datenmodifikation auf die Schnelle zerstört Nicht-IP-Pakete. Wenn Sie andere Protokolle über snull übertragen wollen, müssen Sie den Code des Moduls entsprechend anpassen.