MAC-Adreßauflösung

Eine interessante Frage bei der Ethernet-Kommunikation ist die Verbindung von MAC-Adressen (der eindeutigen Hardware-ID der Schnittstelle) und IP-Nummern. Die meisten Protokolle stehen vor einem ähnlichen Problem, aber wir gehen hier nur auf den Ethernet-Fall ein. Wir versuchen hier, dieses Thema vollständig zu beschreiben, weswegen wir drei Situationen demonstrieren werden: ARP, Ethernet-Header ohne ARP (wie bei plip) und Nicht-Ethernet-Header.

ARP mit Ethernet verwenden

Normalerweise wird die Adreßauflösung mittels ARP, dem Address Resolution Protocol, durchgeführt. Glücklicherweise kümmert sich der Kernel um ARP, und eine Ethernet-Schnittstelle muß nichts Besonderes dafür tun. Solange dev->addr und dev->addr_len beim Öffnen korrekt zugewiesen werden, muß sich der Treiber nicht um das Auflösen der IP-Nummern in physikalische Adressen kümmern. ether_setup weist die richtigen Gerätemethoden an dev->hard_header und dev->rebuild_header zu.

Obwohl der Kernel normalerweise die Details der Adreßauflösung (und das Zwischenspeichern der Ergebnisse) erledigt, ruft er den Treiber auf, um Hilfe beim Zusammenbauen des Pakets zu bekommen, denn es ist ja schließlich der Treiber, der die Details des Headers der physikalischen Schicht kennt, während die Autoren des Netzwerk-Codes versucht haben, den Rest des Kernels von diesem Wissen zu isolieren. Dazu ruft der Kernel die Methode hard_header des Treibers auf, um das Paket mit den Ergebnissen der ARP-Anfrage aufzubauen. Normalerweise müssen Ethernet-Treiber über diesen Vorgang nichts wissen — der gemeinsame Ethernet-Code erledigt alles.

Überschreiben von ARP

Einfache Punkt-zu-Punkt-Schnittstellen wie plip können zwar von Ethernet-Headern profitieren, wollen aber den Aufwand des Hin- und Hersendens von ARP-Paketen einsparen. Der Code von snull gehört in diese Klasse der Netzwerk-Geräte. snull kann kein ARP verwenden, weil der Treiber die IP-Adressen der übertragenen Pakete verändert und ARP-Pakete ebenfalls IP-Adressen austauschen. Obwohl wir ohne größere Probleme einen einfachen ARP-Antwortgenerator hätten implementieren können, ist es interessanter zu zeigen, wie man mit Headern der physikalischen Schicht direkt umgeht.

Wenn Ihr Gerät die üblichen Hardware-Header verwenden will, ohne ARP zu benutzen, müssen Sie die Methode dev->hard_header überschreiben. So macht snull das auch, und die neue Funktion ist recht kurz:


 
int snull_header(struct sk_buff *skb, struct net_device *dev,
                unsigned short type, void *daddr, void *saddr,
                unsigned int len)
{
    struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);

    eth->h_proto = htons(type);
    memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
    memcpy(eth->h_dest,   daddr ? daddr : dev->dev_addr, dev->addr_len);
    eth->h_dest[ETH_ALEN-1]   ⁁= 0x01;   /* dest sind wir xor 1 */
    return (dev->hard_header_len);
}

Die Funktion holt sich einfach nur die vom Kernel gelieferte Information und formatiert sie in einen Standard-Ethernet-Header. Sie setzt außerdem aus Gründen, die weiter unten beschrieben werden, ein Bit in der Ziel-Ethernet-Adresse um.

Wenn ein Paket von der Schnittstelle empfangen wird, wird der Hardware-Header auf verschiedene Weise von eth_type_trans benutzt. Wie haben den entsprechenden Aufruf schon in snull_rx gesehen:


skb->protocol = eth_type_trans(skb, dev);

Diese Funktion extrahiert den Protokoll-Identifikator (ETH_P_IP in diesem Fall) aus dem Ethernet-Header, weist einen Wert an skb->mac.raw zu, entfernt den Hardware-Header aus den Paketdaten (mit skb_pull) und setzt einen Wert für skb->pkt_type. Dieses letzte Feld hat beim Erzeugen eines skb den Wert PACKET_HOST (was bedeutet, daß das Paket zu diesem Rechner gehen soll), und eth_type_trans ändert den Wert auf die Ethernet-Ziel-Adresse. Wenn diese Adresse nicht mit der Adresse der Schnittstelle übereinstimmt, die das Paket empfangen soll, dann wird das Feld pkt_type auf PACKET_OTHERHOST gesetzt. Als Folge daraus verwirft netif_rx alle Pakete des Typs PACKET_OTHERHOST, sofern sich die Schnittstelle nicht im Promiscuous-Modus befindet. Aus diesem Grunde paßt snull_header auf, daß die Ziel-Hardware-Adresse zu der “empfangenden” Schnittstelle paßt.

Wenn Ihre Schnittstelle eine Punkt-zu-Punkt-Verbindung ist, dann werden Sie an unerwarteten Multicast-Paketen wenig Spaß haben. Um dieses Problem zu verhindern, müssen Sie daran denken, daß eine Ziel-Adresse, deren erstes Oktett im niedrigstwertigen Bit eine 0 hat, an einen einzelnen Rechner gerichtet ist (d. h. der Typ ist PACKET_HOST oder PACKET_OTHERHOST). Der plip-Treiber verwendet 0xfc als erstes Oktett seiner Hardware-Adresse, snull dagegen verwendet 0x00. Beide Adressen führen aber zu einer funktionierenden, Ethernet-artigen Punkt-zu-Punkt-Verbindung.

Nicht-Ethernet-Header

Wir haben gerade gesehen, daß der Hardware-Header einige Informationen über die Ziel-Adresse hinaus enthält. Die wichtigste davon ist das Kommunikationsprotokoll. Als nächstes beschreiben wir, wie Hardware-Header verwendet werden können, um wichtige Informationen einzukapseln. Wenn Sie nähere Details wissen möchten, können Sie in den Kernel-Quellen oder in der technischen Dokumentation des jeweiligen Übertragungsmediums nachschlagen. Die meisten Treiber-Autoren können diese Erläuterungen überspringen und einfach nur die Ethernet-Implementation verwenden.

Es muß jedoch nicht jedes Protokoll alle Informationen bereitstellen. Eine Punkt-zu-Punkt-Verbindung wie plip oder snull muß nicht unbedingt ganze Ethernet-Header transportieren, ist deswegen aber nicht weniger allgemein. Die Gerätemethode hard_header, deren Implementation wir oben in Form von snull_header gesehen haben), bekommt die Zustellungsinformationen — sowohl auf Protokoll-Ebene als auch die Hardware-Adresse — vom Kernel. Dazu kommt die 16 Bit große Protokoll-Nummer. IP hat beispielsweise den Wert ETH_P_IP. Vom Treiber wird erwartet, daß er sowohl die Paketdaten als auch die Protokoll-Nummer korrekt an den Zielrechner überträgt. In einer Punkt-zu-Punkt-Verbindung könnten die Adressen im Hardware-Header weggelassen und statt dessen nur die Protokoll-Nummer übertragen werden, weil die Zustellung auch ohne Quell- und Ziel-Adresse funktioniert. Eine reine IP-Verbindung bräuchte sogar gar keinen Hardware-Header zu senden.

Wenn das Paket am anderen Ende der Verbindung entgegengenommen wird, dann muß die Empfangsfunktion skb->protocol, skb->pkt_type und skb->mac.raw korrekt setzen.

skb->mac.raw ist ein char-Zeiger, der vom Adreßauflösungsmechanismus verwendet wird, der in den höheren Schichten des Netzwerk-Codes (z. B. net/ipv4/arp.c) implementiert ist. Er muß auf eine Rechneradresse zeigen, die zu dev->type paßt. Die möglichen Werte für den Gerätetyp sind in linux/if_arp.h definiert. Ethernet-Schnittstellen verwenden beispielsweise AARPHRD_ETHER. eth_type_trans bearbeitet den Ethernet-Header empfangener Pakete folgendermaßen:


skb->mac.raw=skb->data;
skb_pull(skb,dev->hard_header_len);

Im einfachsten Fall (eine Punkt-zu-Punkt-Verbindung ohne Header) kann skb->mac.raw auf einen statischen Puffer zeigen, der die Hardware-Adresse dieser Schnittstelle enthält; protocol kann auf ETH_P_IP gesetzt werden, und packet-type behält den Default-Wert PACKET_HOST.

Weil jeder Hardware-Typ seine Eigenheiten hat, ist es schwierig, weitere und nähere Ratschläge als das, was wir hier schon geschrieben haben, zu geben. Der Kernel ist aber voll von Beispielen. Schauen Sie sich zum Beispiel den AppleTalk-Treiber (drivers/net/appletalk/cops.c), die Infrarot-Treiber (wie drivers/net/irda/smc_ircc.c) oder den PPP-Treiber (drivers/net/ppp_generic.c) an.