Multicasting

Ein “Multicast”-Paket ist ein Netzwerk-Paket, das an mehr als einen, aber nicht alle Rechner im Netzwerk geschickt werden soll. Diese Funktionalität wird dadurch erreicht, daß spezielle Hardware-Adressen an Rechnergruppen zugewiesen werden. Pakete, die an eine dieser besonderen Adressen geschickt werden, sollten von allen Rechnern in der Gruppe empfangen werden. Bei Ethernet ist in Multicast-Adressen das niedrigstwertige Bit des ersten Adressen-Oktetts in der Zieladresse gesetzt, während dieses Bit bei keiner Karte in der Hardware-Adresse gesetzt ist.

Der schwierige Teil des Umgangs mit Rechnergruppen und Hardware-Adressen wird von den Applikationen und dem Kernel übernommen; der Treiber selbst muß sich nicht um diese Probleme kümmern.

Die Übertragung von Multicast-Paketen ist einfach, weil sie genau wie jedes andere Paket aussehen. Die Schnittstelle überträgt die Pakete über das Kommunikationsmedium, ohne sich die Ziel-Adresse anzusehen. Es ist die Aufgabe des Kernels, eine korrekte Hardware-Ziel-Adresse zuzuweisen; die Gerätemethode hard_header muß sich, wenn sie überhaupt existiert, die Daten, nicht ansehen, die sie verarbeiten soll.

Der Kernel verfolgt, welche Multicast-Adressen zu einem gegebenen Zeitpunkt interessant sind. Diese Liste kann sich häufig ändern, weil sie eine Funktion der zu einem gegebenen Zeitpunkt laufenden Applikationen und des Benutzerinteresses ist. Die Aufgabe des Treibers ist es, die Liste interessanter Multicast-Adressen entgegenzunehmen und dem Kernel die an diese Adressen geschickten Pakete zu übergeben. Wie der Treiber die Multicast-Liste implementiert, ist etwas davon abhängig, wie die zugrundeliegende Hardware funktioniert. Normalerweise gehört die Hardware, was das Multicasting angeht, zu einer von drei Klassen:

Der Kernel versucht, die Fähigkeiten von guten Schnittstellen auszunutzen und die dritte Klasse, die die flexibelste ist, so gut es geht zu unterstützen. Daher benachrichtigt der Kernel auch den Treiber, wenn sich die Liste der gültigen Multicast-Adressen geändert hat, und übergibt diese Liste an den Treiber, damit dieser dem Hardware-Filter die neue Information zur Verfügung stellen kann.

Kernel-Unterstützung für Multicasting

Im Folgenden finden Sie eine Zusammenfassung der Datenstrukturen und Funktionen aus dem Bereich des Multicasting:

void (*dev->set_multicast_list)(struct net_device *dev);

Diese Gerätemethode wird immer dann aufgerufen, wenn sich die Liste der Maschinenadressen des Gerätes ändert, außerdem aber auch, wenn dev->flags geändert worden ist, weil manche der Flags (z. B. IFF_PROMISC) auch eine Neuprogrammierung der Hardware-Filter verlangen. Die Methode bekommt einen Zeiger auf eine struct net_device als Argument und gibt void zurück. Ein Treiber, der an der Implementation dieser Methode nicht interessiert ist, kann das Feld auf NULL setzen.

struct dev_mc_list *dev->mc_list;

Dies ist eine verkettete Liste aller Multicast-Adressen, die zu diesem Gerät gehören. Die Definition dieser Struktur steht am Ende dieses Abschnittes.

int dev->mc_count;

Die Anzahl der Elemente in der verketteten Liste. Diese Information ist etwas redundant, aber es ist schneller, mc_count auf 0 zu prüfen, als die Liste abzusuchen.

IFF_MULTICAST

Wenn der Treiber diese Option in dev->flags nicht setzt, dann wird die Schnittstelle nicht um die Verarbeitung von Multicast-Paketen gebeten. Gleichwohl wird aber immer noch die Methode set_multicast_list aufgerufen, wenn sich dev->flags ändert, weil die Multicast-Liste auch geändert werden kann, während die Schnittstelle nicht aktiv ist.

IFF_ALLMULTI

Dieses Flag wird von der Netzwerk-Software in dev->flags gesetzt, um dem Treiber mitzuteilen, daß er alle Multicast-Pakete aus dem Netzwerk entgegennehmen soll. Das ist der Fall, wenn Multicast-Routing eingeschaltet wird. Wenn dieses Flag gesetzt ist, dann sollte dev->mc_list nicht zur Filterung von Multicast-Paketen verwendet werden.

IFF_PROMISC

Dieses Flag wird in dev->flags gesetzt, wenn die Schnittstelle in den “Promiscuous-Modus” versetzt wird. In diesem Fall soll die Schnittstelle jedes Paket entgegennehmen, unabhängig von dev->mc_list.

Schließlich benötigen Treiber-Programmierer als letzte Information noch die Definition von struct dev_mc_list, die in <linux/netdevice.h> steht:


struct dev_mc_list {
    struct dev_mc_list   *next;          /* Naechste Adresse in der Liste */
    _ _u8                 dmi_addr[MAX_ADDR_LEN]; /* Hardware-Adresse */
    unsigned char        dmi_addrlen;    /* Adressenlaenge */
    int                  dmi_users;      /* Zahl der Benutzer */
    int                  dmi_gusers;     /* Zahl der Gruppen */
};

Weil Multicasting und Hardware-Adressen unabhängig von der eigentlichen Übertragung der Pakete sind, ist diese Struktur portabel über die verschiedenen Netzwerk-Implementierungen, denn jede Adresse wird durch einen String von Oktetten und eine Länge identifiziert, genau wie dev->dev_addr.

Eine typische Implementierung

Am besten läßt sich set_multicast_list durch ein wenig Pseudocode verstehen.

Die folgende Funktion ist eine typische Implementation von set_multicast_list in einem vollständigen Treiber (full-featured driver). Der Treiber ist insofern full-featured (ff) oder vollständig, als die Schnittstelle, die er ansteuert, einen komplexen Hardware-Paketfilter hat, der eine Tabelle von Multicast-Adressen verwaltet, die von diesem Rechner empfangen werden sollen. Die Maximalgröße dieser Tabelle ist VS_TABLE_SIZE.

Alle Funktionen, die das Präfix ff_ haben, sind Platzhalter für Hardware-spezifische Operationen.


void ff_set_multicast_list(struct net_device *dev)
{
    struct dev_mc_list *mcptr;

    if (dev->flags & IFF_PROMISC) {
        ff_get_all_packets();
        return;
    }
    /* Wenn wir mit mehr als einer Adresse umgehen koennen, holen wir uns   alle Multicast-Pakete und sortieren die in der Software.*/

    if (dev->flags & IFF_ALLMULTI || dev->mc_count > FF_TABLE_SIZE) {
        ff_get_all_multicast_packets();
        return;
    }
    /* Kein Multicast? Dann holen wir uns nur unseren eigenen Kram. */
    if (dev->mc_count == 0) {
        ff_get_only_own_packets();
        return;
    }
    /* Alle Multicast-Adressen im Hardware-Filter speichern */
    ff_clear_mc_list();
    for (mc_ptr = dev->mc_list; mc_ptr; mc_ptr = mc_ptr->next)
        ff_store_mc_address(mc_ptr->dmi_addr);
    ff_get_packets_in_multicast_list();
}
}

Diese Implementation kann vereinfacht werden, wenn die Schnittstelle keine Multicast-Tabelle für eingehende Pakete im Hardware-Filter ablegen kann. In diesem Fall hat VS_TABLE_SIZE den Wert 0, und die letzten vier Codezeilen werden nicht benötigt.

Wie wir bereits angedeutet haben, müssen auch Schnittstellen, die mit Multicast-Paketen nichts anfangen können, set_multicast_list implementieren, um über Veränderungen in dev->flags informiert zu werden. Wir bezeichnen so etwas als eine “unvollständige” (uv) Implementation. Diese Implementation ist sehr einfach, wie der folgende Code zeigt:



void uv_set_multicast_list(struct net_device *dev)
{
    if (dev->flags & IFF_PROMISC)
        uv_get_all_packets();
    else
        uv_get_only_own_packets();
}

Es ist wichtig, IFF_PROMISC zu behandeln, weil der Benutzer sonst tcpdump oder andere Netzwerk-Analyse-Programme nicht benutzen kann. Wenn die Schnittstelle eine Punkt-zu-Punkt-Verbindung bedient, ist es dagegen nicht notwendig, set_multicast_list zu implementieren, weil sie ohnehin jedes Paket bekommt.