Debuggen eines Netzwerks mit eBPF (RHEL 8 Beta)

Alles mit den vergangenen Ferien!

Wir haben uns entschlossen, unseren ersten Artikel nach den Ferien Linux zu widmen, dh unserem wunderbaren Linux-Administratorkurs , den wir in der Kohorte der dynamischsten Kurse haben, dh mit den relevantesten Materialien und Praktiken. Gut und dementsprechend bieten wir interessante Artikel und eine offene Lektion .

Gepostet von Matteo Croce
Originaltitel: Netzwerk-Debugging mit eBPF (RHEL 8 Beta)

Einführung

Networking ist eine aufregende Erfahrung, aber Probleme werden nicht immer vermieden. Die Fehlerbehebung kann schwierig sein, ebenso wie der Versuch, das falsche Verhalten zu reproduzieren, das „vor Ort“ auftritt.

Glücklicherweise gibt es Tools, die dabei helfen können: Netzwerk-Namespaces, virtuelle Maschinen, tc und netfilter . Einfache Netzwerkeinstellungen können mithilfe von Netzwerk-Namespaces und Veth-Geräten reproduziert werden. Komplexere Einstellungen erfordern die Verbindung virtueller Maschinen mit einer Software-Bridge und die Verwendung von Standard-Netzwerk-Tools wie iptables oder tc , um falsches Verhalten zu simulieren. Wenn beim iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable des SSH-Servers ein Problem mit ICMP-Antworten iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable , kann iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable im richtigen Namespace zur Lösung des Problems beitragen.

Dieser Artikel beschreibt die Behebung komplexer Netzwerkprobleme mit eBPF (Extended BPF) , einer erweiterten Version des Berkeley Packet Filters. eBPF ist eine relativ neue Technologie, das Projekt befindet sich in einem frühen Stadium, sodass die Dokumentation und das SDK noch nicht fertig sind. Hoffen wir jedoch auf Verbesserungen, zumal der XDP (eXpress Data Path) mit Red Hat Enterprise Linux 8 Beta ausgeliefert wird , das Sie jetzt herunterladen und ausführen können.

eBPF wird nicht alle Probleme lösen, aber es ist immer noch ein leistungsstarkes Netzwerk-Debugging-Tool, das Aufmerksamkeit verdient. Ich bin sicher, dass dies in der Zukunft der Netzwerke eine wirklich wichtige Rolle spielen wird.



Das Problem

Ich habe ein Open vSwitch (OVS) -Netzwerkproblem debuggt , das eine sehr komplizierte Installation beinhaltete: Einige TCP-Pakete wurden verstreut und in der falschen Reihenfolge zugestellt , und die Bandbreite der virtuellen Maschinen ging von stabilen 6 Gbit / s auf schwankende 2 bis 4 Gbit / s zurück. Die Analyse ergab, dass das erste TCP-Paket jeder Verbindung mit dem PSH-Flag in der falschen Reihenfolge gesendet wurde: nur das erste und nur eines pro Verbindung.

Ich habe versucht, diese Einstellung mit zwei virtuellen Maschinen zu reproduzieren, und nach vielen nftables und Suchanfragen stellte ich fest, dass weder iptables noch nftables TCP-Flags manipulieren können, während tc kann, sondern nur durch Überschreiben der Flags und Unterbrechen neuer Verbindungen und TCP im Allgemeinen.

Es könnte möglich sein, das Problem mit einer Kombination aus iptables , conntrack und tc zu lösen, aber ich entschied, dass dies ein großartiger Job für eBPF ist.

Was ist eBPF?

eBPF ist eine erweiterte Version des Berkeley Packet Filters. Sie bringt viele Verbesserungen zu BPF. Insbesondere können Sie damit in den Speicher schreiben und nicht nur lesen, sodass Pakete nicht nur gefiltert, sondern auch bearbeitet werden können.

Oft wird eBPF einfach als BPF bezeichnet, und BPF selbst wird als cBPF (klassischer (klassischer) BPF) bezeichnet, sodass das Wort „BPF“ je nach Kontext für beide Versionen verwendet werden kann: In diesem Artikel spreche ich immer über die erweiterte Version.

"Under the Hood" eBPF verfügt über eine sehr einfache virtuelle Maschine, die kleine Fragmente von Bytecode ausführen und einige Speicherpuffer bearbeiten kann. Es gibt Einschränkungen in eBPF, die es vor böswilliger Verwendung schützen:

  • Zyklen sind verboten, damit das Programm immer zu einer bestimmten Zeit beendet wird.
  • Es kann nur über den Stapel und den Arbeitspuffer auf den Speicher zugreifen.
  • Es können nur zulässige Kernelfunktionen aufgerufen werden.

Ein Programm kann auf verschiedene Arten mithilfe von Debugging und Tracing in den Kernel geladen werden. In unserem Fall ist eBPF an der Arbeit mit Netzwerksubsystemen interessiert. Es gibt zwei Möglichkeiten, das eBPF-Programm zu verwenden:

  • Verbunden über XDP mit dem Anfang des Empfangspfads einer physischen oder virtuellen Netzwerkkarte;
  • Verbunden über tc mit qdisc in Eingabe oder Ausgabe.

Um ein eBPF-Programm zum Verbinden zu erstellen, schreiben Sie einfach C-Code und konvertieren Sie ihn in Bytecode. Das Folgende ist ein einfaches Beispiel für die Verwendung von XDP:

 SEC("prog") int xdp_main(struct xdp_md *ctx) { void *data_end = (void *)(uintptr_t)ctx->data_end; void *data = (void *)(uintptr_t)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct icmphdr *icmph = (struct icmphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if (icmph + 1 > data_end) return XDP_PASS; /* matched a pong packet */ if (eth->h_proto != ntohs(ETH_P_IP) || iph->protocol != IPPROTO_ICMP || icmph->type != ICMP_ECHOREPLY) return XDP_PASS; if (iph->ttl) { /* save the old TTL to recalculate the checksum */ uint16_t *ttlproto = (uint16_t *)&iph->ttl; uint16_t old_ttlproto = *ttlproto; /* set the TTL to a pseudorandom number 1 < x < TTL */ iph->ttl = bpf_get_prandom_u32() % iph->ttl + 1; /* recalculate the checksum; otherwise, the IP stack will drop it */ csum_replace2(&iph->check, old_ttlproto, *ttlproto); } return XDP_PASS; } char _license[] SEC("license") = "GPL"; 

Das obige Snippet ohne include Ausdrücke, Helfer und optionalen Code ist ein XDP-Programm, das die TTL der empfangenen ICMP-Echoantworten, nämlich Pongs, durch eine Zufallszahl ändert. Die Hauptfunktion erhält die Struktur xdp_md , die zwei Zeiger auf den Anfang und das Ende des Pakets enthält.

Um unseren Code in eBPF-Bytecode zu kompilieren, ist ein Compiler mit entsprechender Unterstützung erforderlich. Clang unterstützt dies und erstellt einen eBPF-Bytecode, indem bpf zur Kompilierungszeit als Ziel angegeben wird:

 $ clang -O2 -target bpf -c xdp_manglepong.c -o xdp_manglepong.o 

Mit dem obigen Befehl wird eine Datei erstellt, die auf den ersten Blick wie eine normale Objektdatei erscheint. Bei näherer Betrachtung stellt sich jedoch heraus, dass der angegebene Computertyp Linux eBPF und nicht der native Betriebssystemtyp ist:

 $ readelf -h xdp_manglepong.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF <--- HERE [...] 

Nachdem das eBPF-Programm den Wrapper einer regulären Objektdatei erhalten hat, kann es heruntergeladen und über XDP mit dem Gerät verbunden werden. Dies kann mit ip aus dem iproute2 Paket mit der folgenden Syntax erfolgen:

 # ip -force link set dev wlan0 xdp object xdp_manglepong.o verbose 

Dieser Befehl gibt die Ziel-wlan0-Schnittstelle an und überschreibt dank der Option -force den vorhandenen eBPF-Code, der bereits geladen wurde. Nach dem Laden des eBPF-Bytecodes verhält sich das System wie folgt:

 $ ping -c10 192.168.85.1 PING 192.168.85.1 (192.168.85.1) 56(84) bytes of data. 64 bytes from 192.168.85.1: icmp_seq=1 ttl=41 time=0.929 ms 64 bytes from 192.168.85.1: icmp_seq=2 ttl=7 time=0.954 ms 64 bytes from 192.168.85.1: icmp_seq=3 ttl=17 time=0.944 ms 64 bytes from 192.168.85.1: icmp_seq=4 ttl=64 time=0.948 ms 64 bytes from 192.168.85.1: icmp_seq=5 ttl=9 time=0.803 ms 64 bytes from 192.168.85.1: icmp_seq=6 ttl=22 time=0.780 ms 64 bytes from 192.168.85.1: icmp_seq=7 ttl=32 time=0.847 ms 64 bytes from 192.168.85.1: icmp_seq=8 ttl=50 time=0.750 ms 64 bytes from 192.168.85.1: icmp_seq=9 ttl=24 time=0.744 ms 64 bytes from 192.168.85.1: icmp_seq=10 ttl=42 time=0.791 ms --- 192.168.85.1 ping statistics --- 10 packets transmitted, 10 received, 0% packet loss, time 125ms rtt min/avg/max/mdev = 0.744/0.849/0.954/0.082 ms 

Jedes Paket durchläuft eBPF, wodurch letztendlich einige Änderungen vorgenommen werden und entschieden wird, ob das Paket verworfen oder übersprungen werden soll.

Wie eBPF helfen kann

Zurück zum ursprünglichen Netzwerkproblem, erinnern wir uns, dass es notwendig war, mehrere TCP-Flags zu markieren, eines pro Verbindung, und weder iptables noch tc konnten dies tun. Das Schreiben von Code für dieses Szenario ist überhaupt nicht schwierig: Konfigurieren Sie zwei virtuelle Maschinen, die über eine OVS-Bridge verbunden sind, und verbinden Sie den eBPF einfach mit einem der virtuellen VM-Geräte.

Es klingt nach einer großartigen Lösung, aber denken Sie daran, dass XDP nur die Verarbeitung empfangener Pakete unterstützt und das Verbinden des eBPF mit dem Empfangspfad der empfangenden virtuellen Maschine keine Auswirkungen auf den Switch hat.

Um dieses Problem zu lösen, muss eBPF mit tc geladen und mit dem Ausgabepfad der VM verbunden werden, da tc eBPF-Programme laden und mit qdisk verbinden kann. Um Pakete zu markieren, die den Host verlassen, muss eBPF mit der Ausgabe-qdisk verbunden sein.

Beim Laden des eBPF-Programms gibt es einige Unterschiede zwischen der XDP und der tc API: Standardmäßig unterschiedliche Abschnittsnamen, der Typ der Struktur des Arguments der Hauptfunktion, unterschiedliche Rückgabewerte. Das ist aber kein Problem. Unten finden Sie einen Ausschnitt eines Programms, das TCP beim Beitritt zu einer tc-Aktion markiert:

 #define RATIO 10 SEC("action") int bpf_main(struct __sk_buff *skb) { void *data = (void *)(uintptr_t)skb->data; void *data_end = (void *)(uintptr_t)skb->data_end; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct tcphdr *tcphdr = (struct tcphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if ((void *)(tcphdr + 1) > data_end) return TC_ACT_OK; /* skip non-TCP packets */ if (eth->h_proto != __constant_htons(ETH_P_IP) || iph->protocol != IPPROTO_TCP) return TC_ACT_OK; /* incompatible flags, or PSH already set */ if (tcphdr->syn || tcphdr->fin || tcphdr->rst || tcphdr->psh) return TC_ACT_OK; if (bpf_get_prandom_u32() % RATIO == 0) tcphdr->psh = 1; return TC_ACT_OK; } char _license[] SEC("license") = "GPL"; 

Die Kompilierung in Bytecode erfolgt wie im obigen XDP-Beispiel gezeigt wie folgt:

 clang -O2 -target bpf -c tcp_psh.c -o tcp_psh.o 

Aber der Download ist anders:

 # tc qdisc add dev eth0 clsact # tc filter add dev eth0 egress matchall action bpf object-file tcp_psh.o 

Jetzt wird der eBPF an der richtigen Stelle geladen und die Pakete, die die VM verlassen, werden markiert. Nach dem Überprüfen der in der zweiten VM empfangenen Pakete wird Folgendes angezeigt:



tcpdump bestätigt, dass der neue eBPF-Code funktioniert und bei ungefähr 1 von 10 TCP-Paketen das PSH-Flag gesetzt ist. Es wurden nur 20 Zeilen C-Code benötigt, um TCP-Pakete, die die virtuelle Maschine verlassen, selektiv zu markieren, den Fehler zu reproduzieren, der "im Kampf" auftritt, und alles ohne Neukompilieren oder sogar Neustarten! Dies vereinfachte die Überprüfung des Open vSwitch-Fixes erheblich , was mit anderen Tools nicht möglich war.

Fazit

eBPF ist eine ziemlich neue Technologie, und die Community hat eine klare Meinung zu ihrer Implementierung. Es ist auch erwähnenswert, dass Projekte, die auf eBPF basieren, beispielsweise bpfilter , immer beliebter werden. Infolgedessen beginnen viele Ausrüstungslieferanten, die eBPF-Unterstützung direkt in Netzwerkkarten zu implementieren.

eBPF wird nicht alle Probleme lösen, also missbrauchen Sie es nicht, aber es bleibt ein sehr leistungsfähiges Tool für das Netzwerk-Debugging und verdient Aufmerksamkeit. Ich bin sicher, dass dies in der Zukunft der Netzwerke eine wichtige Rolle spielen wird.

DAS ENDE

Wir warten hier auf Ihre Kommentare und laden Sie ein, unsere offene Lektion zu besuchen, in der Sie auch Fragen stellen können.

Source: https://habr.com/ru/post/de436528/


All Articles