Über sieben Brücken musst Du gehn

Das Modul ng_bridge erlaubt es, verschiedene Datenkanäle über die MAC Adressen zu separieren. Dies funktioniert nicht besonders gut, wenn an einer Stelle sehr große Netze mit sehr vielen Teilnehmern angeschlossen sind. Dort möchte man die MAC Adressen nicht lernen. Mit interessanten Konsequenzen.

Problem

Das netgraph Modul ng_bridge verbindet verschiedene Teile des Netgraph-Netzwerkes wie ein Switch in einem Serverraum. Die einzelnen Teile sollen dabei nur die Daten bekommen, die auch für sie gedacht sich. Leider hat das Modul ein paar Schwächen in der Praxis.

  • Zunächst mal konnte das Modul nur mit einer Handvoll Anschlüssen umgehen. Die Limitierung habe ich aufgehoben.
  • Dann lernt das Modul sämtliche MAC Adressen an allen Anschlüssen, was hier ein Problem ist. Auch dafür gibt es eine Lösung.
  • Das Modul behandelt sämtliche Multicast- und Broadcast-Frames gleich: Sie gehen an jeden Anschluss raus. Das ist ein Problem.
  • Aufgrund der inneren Architektur ist das Modul nur single-threaded, was einen begrenzten Datendurchsatz mit sich bringt. Das ist ein weiteres Problem.

Sobald man an einigen Anschlüssen schwachbrüstige Leitungen hängt, treten Überlastungsprobleme auf. Traffic, der dort hin geschickt wird, sollte weitestgehends auch dort hingehen sollen. Traffic, der dort nichts zu suchen hat, sollte gar nicht erst in diese Richtung geschoben werden.

Mit der Einführung von uplink-Ports verringert sich massiv der interne Verwaltungsaufwand. Die MAC-Adressen in Richtung des Uplinks müssen nicht mehr gelernt werden. Und das hat Konsequenzen: Was passiert mit Frames, die an den Uplink geschickt werden müssen?

Eigentlich ist das ganz einfach, denn das Modul kennt die Ziel-MAC ja nicht, schickt es also an alle Links raus, auch an den Uplink. Damit ist der Frame dort, wo er hin soll. Allerdings landet er auch dort, wo er nicht hin soll: Alle anderen Teilnehmer an der Bridge bekommen den kompletten Uplink-Traffic aller ihrer Nachbar zu sehen.

Test

Also teste ich das mal aus. Zunächst wird eine bridge eingerichtet, die drei Downlinks (link1, link2, link3) und zwei Uplinks (uplink1 und uplink2) hat, die alle an virtuellen Ethernet-Interfaces hängen.

ngctl -f- <<END
mkpeer bridge x link10
mkpeer x eiface uplink1 ether
mkpeer x eiface link1 ether
mkpeer x eiface link2 ether
mkpeer x eiface link3 ether
mkpeer x eiface uplink2 ether
END

link10 ist nur temporär zur Erzeugung des gesamten Konstrukts vorhanden, er geht automatisch weg, wenn das Tool ngctl sich beendet

Alle Interfaces werden mit unterschiedlichen MAC Adressen bestückt.

ifconfig ngeth0 ether 00:00:00:00:00:01
ifconfig ngeth1 ether 00:00:00:00:01:01
ifconfig ngeth3 ether 00:00:00:00:02:01
ifconfig ngeth2 ether 00:00:00:00:02:01
ifconfig ngeth3 ether 00:00:00:00:03:01
ifconfig ngeth4 ether 00:00:00:00:04:01

Und dann wird der komplette Datenverkehr mit geschnitten.

tcpdump -eni ngeth0 > bridge.e0 &
tcpdump -eni ngeth1 > bridge.e1 &
tcpdump -eni ngeth2 > bridge.e2 &
tcpdump -eni ngeth3 > bridge.e3 &
tcpdump -eni ngeth4 > bridge.e4 & 

Um nicht jedes Interface in separate Routing-Umgebungen werfen zu müssen, nehme ich einfach für alle Interfaces unterschiedliche IP-Netze. Damit weiß der Kernel, welches Interface ich benutzen möchte.

ifconfig ngeth0 inet 192.168.0.10/24
ifconfig ngeth1 inet 192.168.1.11/24
ifconfig ngeth2 inet 192.168.2.12/24
ifconfig ngeth3 inet 192.168.3.13/24
ifconfig ngeth4 inet 192.168.4.14/24

Und dann muss der Kernel noch wissen, welche MAC Adresse er von welchem Interface aus ansprechen soll. Das erfolgt mit Hilfe von statischen ARP-Einträgen.

arp -s 192.168.0.11 00:00:00:00:01:01
arp -s 192.168.0.12 00:00:00:00:02:01
arp -s 192.168.0.13 00:00:00:00:03:01
arp -s 192.168.0.14 00:00:00:00:04:01
arp -s 192.168.0.20 00:00:00:00:20:00
arp -s 192.168.1.10 00:00:00:00:00:01
arp -s 192.168.1.12 00:00:00:00:02:01
arp -s 192.168.1.13 00:00:00:00:03:01
arp -s 192.168.1.14 00:00:00:00:04:01
arp -s 192.168.1.20 00:00:00:00:20:01
arp -s 192.168.2.10 00:00:00:00:00:01
arp -s 192.168.2.11 00:00:00:00:01:01
arp -s 192.168.2.13 00:00:00:00:03:01
arp -s 192.168.2.14 00:00:00:00:04:01
arp -s 192.168.2.20 00:00:00:00:20:02
arp -s 192.168.3.10 00:00:00:00:00:01
arp -s 192.168.3.11 00:00:00:00:01:01
arp -s 192.168.3.12 00:00:00:00:02:01
arp -s 192.168.3.14 00:00:00:00:04:01
arp -s 192.168.3.20 00:00:00:00:20:03
arp -s 192.168.4.10 00:00:00:00:00:01
arp -s 192.168.4.11 00:00:00:00:01:01
arp -s 192.168.4.12 00:00:00:00:02:01
arp -s 192.168.4.13 00:00:00:00:03:01
arp -s 192.168.4.20 00:00:00:00:20:03

Wie man erkennen kann sind hier mit den 20-er Adressen auch Einträge dabei, deren MACs dem System unbekannt sind. Spreche ich dagegen eine IP an, für die kein statischer ARP Eintrag vor liegt, wird es einen ARP-Broadcast geben. Damit sind alle Fälle abgedeckt.

Und dann kommen die Tests pro Interface:

ping -c 3 -W 1 192.168.0.10
ping -c 3 -W 1 192.168.0.11
ping -c 3 -W 1 192.168.0.12
ping -c 3 -W 1 192.168.0.13
ping -c 3 -W 1 192.168.0.14
ping -c 3 -W 1 192.168.0.20
ping -c 3 -W 1 192.168.0.21
ping -c 3 -W 1 192.168.1.10
ping -c 3 -W 1 192.168.1.11
ping -c 3 -W 1 192.168.1.12
ping -c 3 -W 1 192.168.1.13
ping -c 3 -W 1 192.168.1.14
ping -c 3 -W 1 192.168.1.20
ping -c 3 -W 1 192.168.1.21
ping -c 3 -W 1 192.168.2.10
ping -c 3 -W 1 192.168.2.11
ping -c 3 -W 1 192.168.2.12
ping -c 3 -W 1 192.168.2.13
ping -c 3 -W 1 192.168.2.14
ping -c 3 -W 1 192.168.2.20
ping -c 3 -W 1 192.168.2.21
ping -c 3 -W 1 192.168.3.10
ping -c 3 -W 1 192.168.3.11
ping -c 3 -W 1 192.168.3.12
ping -c 3 -W 1 192.168.3.13
ping -c 3 -W 1 192.168.3.14
ping -c 3 -W 1 192.168.3.20
ping -c 3 -W 1 192.168.3.21
ping -c 3 -W 1 192.168.4.10
ping -c 3 -W 1 192.168.4.11
ping -c 3 -W 1 192.168.4.12
ping -c 3 -W 1 192.168.4.13
ping -c 3 -W 1 192.168.4.14
ping -c 3 -W 1 192.168.4.20
ping -c 3 -W 1 192.168.4.21

Nachdem die Tests durch sind, kann das Testbed weg geworfen werden. Dazu genügt es, die virtuellen Ethernet-Interfaces abzuschalten. Ohne Verbindungen nach außen löscht sich die Bridge von allein.

ngctl shutdown ngeth0:
ngctl shutdown ngeth1:
ngctl shutdown ngeth2:
ngctl shutdown ngeth3:
ngctl shutdown ngeth4:

Mit dem Verschwinden der Interfaces beenden sich auch die Sniffer und schreiben die letzten empfangen Daten noch ins Logfile.

Was kommt dabei raus? Hier ein kleiner Blick in das Logfile von ngeth0:

12:35:07.109300 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 0, length 64
12:35:08.176378 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 1, length 64
12:35:09.197277 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 2, length 64 

Zu sehen sind die ersten drei Pings von 192.168.0.10 an 192.168.0.11. Man erkennt die MAC Adressen aus den statischen ARP Einträgen und natürlich kommt keine Antwort zurück, weil die IP ja absichtlich auf dem Zielinterface nicht konfiguriert wurde.

Im Logfile von ngeth1 finden sich die Gegenstücke:

12:35:07.109336 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 0, length 64
12:35:08.176528 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 1, length 64
12:35:09.197342 00:00:00:00:00:01 > 00:00:00:00:01:01, ethertype IPv4 (0x0800), length 98: 192.168.0.10 > 192.168.0.11: ICMP echo request, id 7173, seq 2, length 64

aber auch die Broadcasts kommen hier an

12:35:23.132078 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.21 tell 192.168.0.10, length 28
12:35:24.196809 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.21 tell 192.168.0.10, length 28
12:35:25.268910 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.21 tell 192.168.0.10, length 28

Auswertung

Alle Logfiles einzeln durch zu gehen, ist viel zu anstrengend, deswegen gibt es eine Kreuztabelle für jeden Test.

bridge classic 10

Die Tabelle enthält oben links die Quell-IP, in den Spalten die Ziel-IPs und in den Zeilen die Messtelle am jeweiligen Interface. Blau hinterlegt sind die Felder, die in jedem Fall erwartungskonform sind. Entweder wurden die Pakete dort versendet oder sollen dort ankommen.

Es zeigt in der ersten Zeile, dass von Interface mit der IP ...10 (ngeth0) Pakete an 11, 12, 13, 14 sowie an unbekannt (20) und broadcast (21) geschickt und gesehen werden. Pakete an sich selbst sieht das Interface nicht, denn das handelt der Kernel intern ab.

In der zweiten Zeile finden sich Pakete, die von der IP ...10 ausgehen und am Interface ngeth1 (also mit der IP ...11) gesehen werden. Das sind zunächst mal das Paket an das Interface selbst (korrekterweise), die Pakete an die anderen Ziele (12 und 13) sind nicht zu sehen, weil die Bridge die Ziel-MACs kennt. Das Paket an die 14 (einem weiteren Uplink) kennt Bridge ebensowenig wie die unbekannte MAC (20). Bei der 21 gibt es einen Broadcast, der ebenso zu sehen ist.

bridge classic 11

Bei den Paketen von den normalen Links wird es nun bunt.

Gelb hinterlegt sind hier diskussionswürdige Felder, beispielsweise die vom Downlink in Richtung Uplink gehen sollten. Da kein MAC Learning am Uplink stattfindet kann die Bridge nicht wissen, welche Pakete an welchen Uplink raus gehen sollen. Das gilt insbesondere für komplett unbekannte MACs, die ja auch in Richtung des Downlinks liegen könnten, aber noch nicht gelernt wurden.

Rot hinterlegt sind Fälle, die man eigentlich gar nicht haben möchte. Pakete, die vom Downlink in Richtung Uplink gehen, sollen nicht zu anderen Downlinks wieder raus gehen.

In gleicher Weise findet sich das Spiel auch für die anderen Quell-IPs darstellen:

bridge classic 12
bridge classic 13
bridge classic 14

Offenbar ist die Behandlung der unbekannten MACs ein ernsthaftes Problem.

Änderungen

Dank OpenSource ist eine Änderung möglich: Wenn das erste Interface, das an die Bridge gebunden wird, ein uplink-Interface ist, schaltet die Bridge in einen Restrictive Mode. Hier werden die unbekannten MACs nicht mehr an die Downlinks weiter gegeben.

Das Verhalten ist nicht unproblematisch, denn nun können Geräte am Downlink nicht mehr erreicht werden, wenn deren MAC längere Zeit nicht zu sehen war. Das kann besonders bei pausierenden Geräten zu einem Problem werden. Die MAC-Haltezeit sollte also in jedem Fall größer sein als die ARP-Timeouts. So sollten die die MAC-Tabellen ausreichend oft aktualisiert werden.

Alles was sich am Testbed nun ändert, ist der Anfang:

ngctl -f- <<END
mkpeer bridge x uplink10
mkpeer x eiface uplink1 ether
mkpeer x eiface link1 ether
mkpeer x eiface link2 ether
mkpeer x eiface link3 ether
mkpeer x eiface uplink2 ether
END 

Aus link10 wird uplink10. Das ist alles. Aber die Wirkung ist verblüffend:

bridge restrict 11

Alle gelben und roten Felder für die Quelle 11 sind weg. Es werden in Richtung des Downlinks nur noch entweder die bekannten MACs oder die Broadcasts geschickt. Das reduziert den Traffic erheblich.

Hier die Zusammenstellung aller Ergebnisse im restrictive Mode:

bridge restrict 10
bridge restrict 11
bridge restrict 12
bridge restrict 13
bridge restrict 14

Bis auf die Uplinks bekommt kein Link mehr Frames mit unbekannter Ziel-MAC.

Ein weiteres Teilziel geschafft.

Post a comment

Related content