Mehrere Netzwerkkarten im gleichen Subnetz unter Linux

Da hat man nun schon einen schönen HTTP/FTP/SMB/sonstwas-Server mit flotten Festplatten und diese verflixte Netzwerkkarte kann die Clients nur mit 100 (oder gar nur 10?) MBit/s beliefern ...

Wie lässt sich die Bandbreite erhöhen?

Die offensichtlichste und "einfachste" Variante dürfte sein, die vorhandene Netzwerkkarte gegen eine schnellere auszutauschen. Das bringt bei Ethernet gleich die (theoretisch) 10-fache Geschwindigkeit, man lädt den passenden Treiber und fertig. Oder auch nicht, denn man braucht schliesslich auch eine passende Gegenstelle (also einen Hub oder einen Switch), an die man diese Netzwerkkarte anschliessen kann.

Fast Ethernet mit 100 MBit/s ist heutzutage günstig zu haben und weit verbreitet. Wer noch mit 10 MBit Ethernet arbeitet, sollte wirklich diese Variante wählen. Beim Wechsel von Fast auf Gigabit Ethernet sieht die Sache hingegen noch anders aus. Sowohl die Netzwerkkarten als auch besonders die Switches sind ein gutes Stück teurer. Hat man, wie z.B. auf LAN-Parties, keinen Einfluss auf das Netzwerk, steht man weiter vor dem Problem, ob überhaupt passende Ports zur Verfügung stehen und ob diese einen zur Netzwerkkarte passenden Anschluss (TP oder Glasfaser) haben.

Das führt uns zur zweiten Variante: stecken wir doch einfach eine (oder mehrere) weitere Netzwerkkarte(n) in den Rechner! Dies bringt freilich nur die doppelte (oder entsprechend vielfache) Bandbreite, was aber ja schon ausreichend sein kann, zumal die meisten Rechner eine Gigabit-Karte eh nicht auslasten können. Vorausgesetzt natürlich, man hat noch ausreichend PCI-Slots auf dem Mainboard frei. Ansonsten bliebe einem noch die Möglichkeit, zu einer Netzwerkkarte zu greifen, die mehrere Anschlüsse auf einer Karte bietet.

Und wo ist das Problem?

Wir haben uns jetzt also für eine zweite Netzwerkkarte entschieden (die übrigens nicht vom selben Modell oder Hersteller wie die erste sein muss). Beide Karten stecken im Rechner, der/die Treiber ist/sind geladen und beide Karten haben eine IP. Egal, zu welcher IP wir verbinden, gehen die Daten aber immer nur über die zweite Karte wieder raus.

Was soll das?

Der Grund für dieses Verhalten liegt in der Routingtabelle. Anhand der Routingtabelle entscheidet das Betriebssystem, wohin es ein IP-Paket schicken soll.

In den kommenden Beispielen verwende ich folgende Daten:

KarteInterfaceMAC-AdresseIP-AdresseSubnetzmaske
1eth000:11:22:33:44:55192.168.1.1255.255.255.0
2eth1AA:BB:CC:DD:EE:FF192.168.1.2255.255.255.0

(Die Liste lässt sich mit weiteren Karten beliebig fortsetzen.)

Die Routingtabelle sollte jetzt ungefähr so aussehen:

root@server:~ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth1
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo
root@server:~ # 

Der Kernel geht die Tabelle von oben nach unten durch und nimmt den ersten passenden Eintrag. Und schon haben wir das Problem ... alles, was an 192.168.1.* gehen soll, wird also an eth1 geschickt.

Und wie kann man das ändern?

Indem man das Routing "verbessert" :-)

Wer noch nie einen Kernel selber kompiliert hat sollte an dieser Stelle das Handbuch zur Distribution, ein anderes HOWTO oder ähnliches zu Rate ziehen.

In der Kernelkonfiguration (bevorzugt mit make menuconfig bzw. make xconfig) findet man unter Networking Options die Option IP: advanced router. Aktiviert man diese, so erscheinen eine Reihe weiterer Optionen, unter anderem:

Weiter unten findet man noch eine Option, die sich auch mit der Lastverteilung beschäftigt:

Im Hauptmenu unter Network Device Support gibt es auch noch einen speziellen Treiber:

Die Hilfe verrät einem dazu, dass man damit so schicke Sachen machen kann, wie das Routing anhand der Quell-IP-Adresse (IP: policy routing), das abwechselnde Routing über unterschiedliche, gleichwertige Pfade (IP: equal cost multipath und ähnlich TEQL queue) und das "zusammenfassen" mehrerer Karten zu "einer" (Bonding driver support).

Im weiteren werde ich nur auf das IP: policy routing eingehen. Dieses hat den netten Effekt, dass eine IP einer Netzwerkkarte fest zugeordnet wird und man sich so als Client die Karte "aussuchen" kann (z.B. auch, um bequem die Performance zweier unterschiedlicher Netzwerkkarten zu vergleichen. :-) )

Mit IP: equal cost multipath liesse sich eine automatische Verteilung der ausgehenden Verbindungen auf beide Karten erreichen. Zumindest im Kernel 2.2.x läuft dies aber nur IP bezogen, d.h. wenn der Kernel zum ersten Mal an eine bestimmte IP Daten liefert, dann entscheidet er sich einmal für eine der Karten/Routen und behält diese dann bei.

Die TEQL queue sorgt auch für eine Verteilung der Daten auf mehrere Leitungen, aber für jedes Paket einzeln. Dies klingt zwar erstmal wunderbar, aufgrund der Art, wie dies erreicht wird (ein neues Device teql0, dem die Netzwerkkarten zugeordnet werden), ergeben sich jedoch Probleme bei den ARP Anfragen (siehe unten), die ich mit der neuen arp_filter Option nicht lösen konnte.

Auch das "zusammenfassen" mehrerer Karten mit dem Bonding driver support hört sich nett an, benötigt aber eine passende Gegenstelle in Form eines Switches, der "Etherchannel" oder "Trunking" (oder wie es der jeweilige Hersteller auch immer gerade nennt) unterstützt und der auch entsprechend konfiguriert werden muss. Wer so etwas "rein zufällig" rumstehen oder das Geld dafür übrig hat, der kann jetzt eigentlich in der Bonding driver Dokumentation [*] weiterlesen und ist (auf Linux-Seite) so gut wie fertig. Wenn nicht: viel Spass beim Weiterlesen ... ;-)

[*] zu finden im Kernelsource, also üblicherweise unter /usr/src/linux/Documentation/networking/bonding.txt. Von Kernel 2.4.0 bis 2.4.14 fehlte die Datei übrigens, in diesem Fall wird (ausser einem neuen Kernel ;-) ) die bevorzugte Suchmaschine (Suche nach "bonding.txt") sicher weiterhelfen.

Wir kompilieren also einen neuen Kernel mit aktiviertem IP: policy routing. Wer es selber eventuell mal probieren will, kann auch die anderen Optionen aktivieren. Ausser einem etwas grösseren Kernel hat dies keine weiteren Auswirkungen, so lange man es nicht mit einer entsprechenden Konfiguration später aktiviert.

War das jetzt schon alles?

Leider nicht.

Nachdem der neue Kernel kompiliert, installiert und geladen wurde, muss unser neues Feature erst einmal konfiguriert werden. Dazu brauchen wir das iproute-Paket. Sofern dies nicht schon der verwendeten Distribution beiliegt, muss man es sich von hier runterladen und selber kompilieren. Ein einfaches

root@server:/usr/src/iproute2 # make

sollte zum Kompilieren ausreichen. Installiert werden muss per Hand mit

root@server:/usr/src/iproute2 # cp ip/ip ip/rtmon tc/tc /usr/sbin/

Das Programm ip ist das eigentliche Programm, was für die folgende Konfiguration benötigt wird.

Und wie konfiguriert man das nun endlich?

Noch die Routingtabelle in Erinnerung?

root@server:~ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth1
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo

Ein Eintrag ist eh zu viel. Den löschen wir jetzt einfach raus:

root@server:~ # route del -net 192.168.1.0 netmask 255.255.255.0 dev eth1

Wer noch mehr Karten hat wiederholt dies für alle bis auf eine. Dieser letzte Eintrag bleibt als "Standard"-Route für dieses Subnetz bestehen. In jedem Fall sollte die Routingtabelle im Anschluss (zumindest was das 192.168.1.0-Netz anbelangt) so aussehen:

root@server:~ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo

Im nächsten Schritt geht es an die eigentliche Konfiguration des "policy routing". Wir legen 2 neue Routingtabellen an:

root@server:~ # ip route add 192.168.1.0/24 table 100 dev eth0 src 192.168.1.1 protocol static
root@server:~ # ip route add 192.168.1.0/24 table 101 dev eth1 src 192.168.1.2 protocol static

Tabelle 100 routet nun Pakete an 192.168.1.0/24 über das Interface eth0, wobei noch als "bevorzugt zu verwendende" eigene IP-Adresse 192.168.1.1 eingetragen wird. Tabelle 101 macht dasselbe für Interface eth1 und dessen IP 192.168.1.2.

Dem Kernel muss aber auch noch gesagt werden, wann er welche der Tabellen verwenden soll. Dafür werden 2 entsprechende Regeln angelegt:

root@server:~ # ip rule add from 192.168.1.1 table 100
root@server:~ # ip rule add from 192.168.1.2 table 101

Voilà. Alles, was jetzt von der IP 192.168.1.1 kommt, wird in Tabelle 100 geschickt und von dort (sofern das Ziel auch im 192.168.1.0/24 Netz liegt) über eth0 rausgeschickt. Ein Paket mit der Quell-IP 192.168.1.2 geht hingegen in Tabelle 101 und wird, wie gewünscht, über eth1 weitergeleitet.

Nun sollte noch der Routing-Cache gelöscht werden:

root@server:~ # ip route flush cache

und schon sind wir mit der Konfiguration des Routing fertig. :-)

Pakete, die aus irgendeinem Grund (weitere IPs auf einem Interface, die nicht in der Routingtabelle stehen?) keine dieser IPs haben, nehmen die "Standard"-Route über eth0, die wir oben noch in der ursprünglichen Routingtabelle stehengelassen haben.

Wie sehe ich, ob die ganzen Einstellungen übernommen wurden?

Mit dem ip Programm lassen sich alle Tabellen und Regeln auch ausgeben.

Die Liste der Routingtabellen müsste jetzt so beginnen:

root@server:~ # ip route list table all
192.168.1.0/24 dev eth0  table 100  proto static  scope link  src 192.168.1.1
192.168.1.0/24 dev eth1  table 101  proto static  scope link  src 192.168.1.2
192.168.1.0/24 dev eth0  scope link
127.0.0.0/8 dev lo  scope link
[....]

Die ersten beiden Zeilen sind die beiden neu angelegten Tabellen, darunter kommen die Einträge aus der "normalen" Routingtabelle (siehe route -n). Anschliessend folgt noch eine Reihe weiterer Einträge, die hier nicht weiter wichtig sind.

Die Regelliste müsste schliesslich noch dies enthalten:

root@server:~ # ip rule list
0:	from all lookup local
32764:	from 192.168.1.2 lookup 101
32765:	from 192.168.1.1 lookup 100
32766:	from all lookup main
32767:	from all lookup default

Es klappt manchmal immer noch nicht!

Das ist nun noch ein anderes "Problemchen". Im Ethernet können die Pakete nicht anhand der IP-Adresse, sondern nur über die MAC Adresse der Netzwerkkarte zugestellt werden. Dazu wird das Address Resolution Protocol (ARP) verwendet, über das die Rechner die zu einer IP gehörige MAC-Adresse erfragen. Rechner A fragt also über einen Broadcast "Wer hat die IP 192.168.1.1?" und der entsprechende Rechner B antwortet darauf, so dass A jetzt dessen MAC-Adresse hat. Da unser Rechner mehrere Netzwerkkarten im gleichen Subnetz hat, empfängt er diesen Broadcast über alle diese Karten.

"Leider" (für uns) unterscheidet Linux in der Voreinstellung nicht, ob die IP-Adresse auch zu der Netzwerkkarte gehört, über die dieser Broadcast empfangen wurde und schickt jetzt auf jeder Netzwerkkarte eine Antwort mit der entsprechenden MAC-Adresse der jeweiligen Karte raus. Je nach, in welcher Reihenfolge die Pakete vom anfragenden Rechner empfangen werden, merkt sich dieser jetzt irgendeine der MAC-Adressen von Rechner B. Das Ergebnis könnte nun auf Rechner A (dem Client!) so aussehen:

root@client:~ # arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.1.1              ether   00:11:22:33:44:55   C                     eth0
192.168.1.2              ether   00:11:22:33:44:55   C                     eth0

Egal, an welche IP der Client nun sendet, das Paket wird in jedem Fall an die MAC-Adresse von eth0 (siehe Beispiel am Anfang, Achtung: das "eth0" in dieser Ausgabe ist die Netzwerkkarte des Client!).

Aber wie gesagt: dies ist nur die Voreinstellung und Dank /proc-Filesystem lässt sich das schnell ändern. Hier hat sich nur leider über die verschiedenen Kernelversion etwas geändert und ich kann nicht genau sagen, was ab welcher Version vorhanden ist. Es ist also ein Blick in /proc/sys/net/ipv4/conf/all/ nötig, ob sich dort eine dieser Dateien befindet:

Kernel 2.4.9 hat z.B. nur die erste, Kernel 2.2.19 hat beide und Kernel 2.2.14 hat nur die zweite ... wenn vorhanden, sollte man daher wohl arp_filter wählen, so dass die Befehle lauten:

root@server:~ # echo 1 >/proc/sys/net/ipv4/conf/all/arp_filter
root@server:~ # echo 1 >/proc/sys/net/ipv4/conf/eth0/arp_filter
root@server:~ # echo 1 >/proc/sys/net/ipv4/conf/eth1/arp_filter

Wenn arp_filter nicht vorhanden ist, so ist dies hier durch hidden zu ersetzen.

In beiden Fällen schaltet die erste Zeile die Funktion an sich frei und die folgenden Zeilen aktivieren es schliesslich für alle betroffenen Netzwerkkarten. Auf eth0 werden nun nur noch ARP-Anfragen beantwortet, die auch an eine IP von eth0 gerichtet sind usw.

War es das jetzt?

Ja! Es ist geschafft! :-)

Wenn nichts schiefgelaufen ist, dann sollten sämtliche Daten von und zu 192.168.1.1 nur über eth0 und alle Daten von und zu 192.168.1.2 nur über eth1 laufen.


Valid XHTML 1.1! Valid CSS!