BPF für Einsteiger, Teil Null: klassisches BPF

Berkeley Packet Filters (BPF) – das ist eine Kerntechnologie von Linux, die in den letzten Jahren immer wieder in den Schlagzeilen englischsprachiger Fachzeitschriften auftaucht. Konferenzen sind voll von Vorträgen über die Nutzung und Entwicklung von BPF. David Miller, der Maintainer des Linux-Netzwerksystems, bezeichnet seinen Vortrag auf der Linux Plumbers 2018. „Dieser Vortrag handelt nicht von XDP“ (XDP ist eine der Anwendungen von BPF). Brendan Gregg hält Vorträge mit dem Titel Linux BPF Superkräfte. Toke Høiland-Jørgensen lacht, erklärt, dass der Kern jetzt ein Mikrokernel ist. Thomas Graf bewirbt die Idee, dass BPF – das ist JavaScript für den Kernel..

Auf Habr gibt es bisher keine systematische Beschreibung von BPF, und deshalb werde ich in einer Reihe von Artikeln die Geschichte der Technologie erzählen, die Architektur und Entwicklungstools beschreiben sowie Anwendungsgebiete und Praktiken der Nutzung von BPF umrissen. In diesem ersten Artikel der Reihe wird die Geschichte und Architektur des klassischen BPF erläutert, sowie die Geheimnisse der Funktionsweise enthüllt. tcpdump, seccomp, strace, und vieles mehr.

Die Entwicklung von BPF wird von der Linux-Netzgemeinschaft kontrolliert. Die meisten vorhandenen Anwendungen von BPF stehen im Zusammenhang mit Netzwerken und deshalb, mit Erlaubnis von @eucariot, habe ich die Reihe "BPF für die Kleinsten" genannt, zu Ehren der großartigen Serie. "Netzwerke für die Kleinsten".

Ein kurzer Rückblick auf die Geschichte von BPF (c)

Die moderne Technologie BPF ist eine verbesserte und erweiterte Version der alten Technologie des gleichen Namens, die mittlerweile zur Vermeidung von Verwirrung als klassisches BPF bezeichnet wird. Auf der Grundlage des klassischen BPF wurde das allgemein bekannte Tool tcpdump, der Mechanismus seccomp, sowie das weniger bekannte Modul xt_bpf für iptables und der Klassifikator cls_bpf. Im modernen Linux werden die klassischen BPF-Programme automatisch in die neue Form übersetzt, jedoch ist aus der Sicht des Benutzers die API unverändert geblieben, und neue Anwendungen des klassischen BPF, wie wir in diesem Artikel sehen werden, sind nach wie vor präsent. Aus diesem Grund, sowie um die Geschichte der Entwicklung des klassischen BPF in Linux nachzuvollziehen, wird deutlich, wie und warum es sich in die moderne Form entwickelt hat; ich habe mich entschieden, mit einem Artikel über klassisches BPF zu beginnen.

Ende der 1980er Jahre begannen Ingenieure des renommierten Lawrence Berkeley Laboratory, sich mit der Frage zu befassen, wie Netzwerpakete auf der damaligen Hardware effektiv gefiltert werden können. Die Grundidee der Paketfilterung, die ursprünglich in der Technologie CSPF (CMU/Stanford Packet Filter) umgesetzt wurde, bestand darin, überflüssige Pakete so früh wie möglich zu filtern, d.h. im Kernelraum, da dies das unnötige Kopieren von Daten in den Benutzerspeicher vermeidet. Um während der Ausführung von Benutzercode im Kernelraum Sicherheit zu gewährleisten, wurde eine virtuelle Maschine — Sandkasten — verwendet.

Allerdings waren die virtuellen Maschinen für die existierenden Filter für Maschinen mit Stack-Architektur ausgelegt und arbeiteten auf neuen RISC-Maschinen nicht so effizient. Schließlich wurde von den Ingenieuren der Berkeley Labs eine neue Technologie namens BPF (Berkeley Packet Filters) entwickelt, deren Architektur der virtuellen Maschine auf dem Motorola 6502 Prozessor basierte — dem Arbeitstier bekannter Produkte wie Apple II oder NES. Die neue virtuelle Maschine steigerte die Leistung der Filter um ein Vielfaches im Vergleich zu den bestehenden Lösungen.

Architektur der BPF-Maschine

Wir werden die Architektur in praktischen Beispielen kennenlernen. Zuvor sei jedoch gesagt, dass die Maschine zwei benutzerzugängliche 32-Bit-Register, einen Akkumulator A und ein Indexregister X, 64 Bytes Speicher (16 Wörter), die zum Schreiben und anschließenden Lesen zur Verfügung stehen, sowie ein kleines Kommandosystem zur Bearbeitung dieser Objekte hatte. In den Programmen standen auch Sprunganweisungen zur Verfügung, um bedingte Ausdrücke zu realisieren, wobei jedoch, um einen rechtzeitigen Abschluss des Programms zu garantieren, Sprünge nur nach vorne erlaubt waren; das Erstellen von Schleifen war somit untersagt.

Das allgemeine Schema zum Starten der Maschine ist folgendermassen: Der Benutzer erstellt ein Programm für die BPF-Architektur und lädt es mithilfe eines Kernmechanismus (zum Beispiel eines Systemaufrufs) hoch und verbindet das Programm mit einem zum Ereignisgenerator im Kernel (zum Beispiel, ein Ereignis ist das Eintreffen eines neuen Pakets auf der Netzwerkkarte). Bei Auftreten eines Ereignisses startet der Kernel ein Programm (zum Beispiel im Interpreter), während der Speicher des Systems entspricht einem dem Speicherbereich des Kernels (zum Beispiel, den Daten des eingetroffenen Pakets).

Das oben Gesagte reicht uns aus, um mit Beispielen zu beginnen: Wir werden uns bei Bedarf mit dem System und dem Format der Befehle vertrautmachen. Wenn Sie jedoch sofort das Befehlsystem der virtuellen Maschine kennenlernen und alle ihre Möglichkeiten erfahren möchten, können Sie den Originalartikel lesen The BSD Packet Filter und/oder der ersten Hälfte der Datei Dokumentation/netzwerk/filtern.txt aus der Kernel-Dokumentation. Darüber hinaus kann die Präsentation libpcap: Eine Architektur und Optimierungsmethodik für die Paketaufnahme, in der McCanne, einer der Autoren von BPF, über die Entstehungsgeschichte spricht libpcap.

Wir wenden uns nun der Betrachtung aller wesentlichen Anwendungsbeispiele des klassischen BPF in Linux zu: tcpdump (libpcap), seccomp, xt_bpf, cls_bpf.

tcpdump

Die Entwicklung von BPF erfolgte parallel zur Entwicklung des Frontends zur Paketfilterung – dem allgemein bekannten Tool tcpdump. Und da dies das älteste und bekannteste Beispiel für die Verwendung von klassischem BPF ist, das auf vielen Betriebssystemen verfügbar ist, beginnen wir mit diesem Beispiel, um die Technologie zu erkunden.

(Alle Beispiele in diesem Artikel habe ich auf Linux 5.6.0-rc6. Die Ausgabe einiger Befehle wurde zur besseren Lesbarkeit bearbeitet.)

Beispiel: IPv6-Pakete beobachten

Angenommen, wir möchten alle IPv6-Pakete auf dem Interface ansehen. eth0Dafür können wir das Programm tcpdump mit dem einfachsten Filter ip6:

$ sudo tcpdump -i eth0 ip6

Dabei tcpdump kompiliert den Filter ip6 in Bytecode der BPF-Architektur und sendet ihn an den Kernel (vgl. Details im Abschnitt Tcpdump: Laden). Der geladene Filter wird für jedes Paket ausgeführt, das durch das Interface geht. eth0Wenn der Filter einen Wert ungleich null zurückgibt n, werden n Bytes des Pakets in den Benutzerspeicher kopiert, und wir sehen sie in der Ausgabe. tcpdump.

BPF für Einsteiger, Teil Null: klassisches BPF

Es stellt sich heraus, dass wir leicht herausfinden können, welcher Bytecode an den Kernel gesendet wurde, tcpdump indem wir das Tool selbst verwenden, tcpdumpwenn wir es mit der Option -d:

$ sudo tcpdump -i eth0 -d ip6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 3
(002) ret      #262144
(003) ret      #0

In der ersten Zeile führen wir den Befehl aus ldh [12], der als „lade in das Register“ entschlüsselt wird. A Halbe Sprache (16 Bit), die sich an Adresse 12 befindet, und die einzige Frage ist — welcher Speicher ist adressiert? Die Antwort liegt darin, dass wir an Adresse x beginnt (x+1)Byte des analysierten Netzwerkpakets. Wir lesen Pakete von der Ethernet-Schnittstelle eth0, das ist bedeutet, wie das Paket folgendermaßen aussieht (zur Vereinfachung nehmen wir an, dass im Paket keine VLAN-Tags vorhanden sind):

       6              6          2
|Ziel MAC|Quell MAC|Ether Type|...|

Das bedeutet, nach der Ausführung des Befehls ldh [12] wird im Register A das Feld Ether Type — der Typ des Pakets, das in diesem Ethernet-Frame übertragen wird. In Zeile 1 vergleichen wir den Inhalt des Registers A (Pakettyp) mit 0x86dd, das ist und es ist der Typ IPv6, der uns interessiert. In Zeile 1 gibt es neben dem Vergleichsbefehl noch zwei Spalten — jt 2 und jf 3 — Markierungen, auf die wir bei erfolgreichem Vergleich springen müssen (A == 0x86dd) und bei einem Misserfolg. Im Erfolgsfall (IPv6) springen wir zu Zeile 2, im Misserfolgsfall zu Zeile 3. In Zeile 3 endet das Programm mit Code 0 (Paket nicht kopieren), in Zeile 2 endet das Programm mit Code 262144 (kopiere mir maximal 256 Kilobyte des Pakets).

Ein etwas komplizierteres Beispiel: Wir schauen uns TCP-Pakete nach Zielport an

Schauen wir uns an, wie der Filter aussieht, der alle TCP-Pakete mit dem Zielport 666 kopiert. Wir betrachten den IPv4-Fall, da der IPv6-Fall einfacher ist. Nach der Durchsicht dieses Beispiels können Sie als Übung den Filter für IPv6 selbst untersuchen (ip6 und tcp dst port 666) und den Filter für den allgemeinen Fall (tcp dst port 666). Der Filter, der uns interessiert, sieht folgendermaßen aus:

$ sudo tcpdump -i eth0 -d ip and tcp dst port 666
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 10
(002) ldb      [23]
(003) jeq      #0x6             jt 4    jf 10
(004) ldh      [20]
(005) jset     #0x1fff          jt 10   jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 16]
(008) jeq      #0x29a           jt 9    jf 10
(009) ret      #262144
(010) ret      #0

Was die Zeilen 0 und 1 bewirken, wissen wir bereits. In Zeile 2 haben wir bereits überprüft, dass es sich um ein IPv4-Paket handelt (Ether Type = 0x800) und laden das Register A des 24. Bytes des Pakets. Unser Paket sieht aus wie

       14            8      1     1
|Ethernet-Header|IP-Felder|TTL|Protokoll|...|

Das bedeutet, dass wir das Register A des Protocol-Felds des IP-Headers laden, was sinnvoll ist, da wir nur TCP-Pakete kopieren wollen. Wir vergleichen das Protokoll mit 0x6 (IPPROTO_TCP) in Zeile 3. In den Zeilen 4 und 5 laden wir das Halbworte, das sich an Adresse 20 befindet, und überprüfen mit dem Befehl

In den Zeilen 4 und 5 laden wir Halbwörter von der Adresse 20 und verwenden den Befehl jset , ob eines der drei Flags — in der ausgegebenen Maske gesetzt ist. jset Die drei höchsten Bits wurden entfernt. Zwei dieser Bits zeigen uns an, ob das Paket Teil eines fragmentierten IP-Pakets ist und, falls ja, ob es sich um das letzte Fragment handelt. Das dritte Bit ist reserviert und muss null sein. Wir möchten weder unvollständige noch beschädigte Pakete überprüfen, daher prüfen wir alle drei Bits.

Zeile 6 ist die interessanteste in diesem Listing. Der Ausdruck ldxb 4*([14]&0xf) bedeutet, dass wir vier niedrigste Bits des fünfzehnten Bytes des Pakets, multipliziert mit 4, in das Register laden. Die vier niedrigsten Bits des fünfzehnten Bytes sind das Feld X Internet Header Length der IPv4-Überschrift, wo die Länge der Überschrift in Worten gespeichert ist, weshalb wir sie anschließend mit 4 multiplizieren müssen. Interessanterweise ist der Ausdruck 4*([14]&0xf) eine Kennzeichnung eines speziellen Adressierungsschemas, das nur in dieser Form für das Register verwendet werden kann. Das heißt, wir können nicht einfach Xldb 4*([14]&0xf) oder übertragen werden ldxb 5*([14]&0xf) benutzen (wir können nur einen anderen Offset angeben, zum Beispiel ldxb 4*([16]&0xf)). Offensichtlich wurde dieses Adressierungsschema in BPF eingeführt, um die Länge der IPv4-Überschrift im X (Indexregister) zu erhalten.

Somit versuchen wir in Zeile 7, ein halbes Wort von der Adresse (X+16) zu laden.. Wenn wir bedenken, dass der Ethernet-Header 14 Byte benötigt und X dass er die Länge des IPv4-Headers enthält, verstehen wir, dass in A der Zielport des TCP geladen wird:

       14           X           2             2
|Ethernet-Header|IP-Header|Quellport|Zielport|

Schließlich vergleichen wir in Zeile 8 den Zielport mit dem gesuchten Wert und geben in Zeilen 9 oder 10 das Ergebnis zurück – ob das Paket kopiert werden soll oder nicht.

Tcpdump: Laden

In den vorherigen Beispielen haben wir absichtlich nicht im Detail erklärt, wie wir den BPF-Bytecode in den Kernel laden, um Pakete zu filtern. Allgemein gesagt, tcpdump wurde er auf viele Systeme portiert und für die Filterung verwendet. tcpdump die Bibliothek verwendet libpcapUm kurz zu erklären, wie man einen Filter mit Hilfe von libpcap, müssen folgende Schritte unternommen werden:

Um zu sehen, wie die Funktion pcap_setfilter in Linux umgesetzt ist, verwenden wir strace (einige Zeilen wurden entfernt):

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

In den ersten beiden Ausgabenzeilen erstellen wir einen Raw-Socket um alle Ethernet-Frames zu lesen und binden ihn an das Interface eth0kompatibel sind, ist möglich. Von unseres ersten Beispiels. Wir wissen, dass der Filter ip aus vier BPF-Anweisungen bestehen wird, und in der dritten Zeile sehen wir, wie wir mit der Option SO_ATTACH_FILTER des Systemaufrufs erstellt, und wir werden im nächsten Abschnitt darüber sprechen, wie genau das geschieht. Dabei werden Datenstrukturen im Kernel erstellt, und für jede von ihnen setsockopt den Filter der Länge 4 laden und verbinden. Das ist unser Filter.

Es ist wichtig zu beachten, dass im klassischen BPF das Laden und Verbinden des Filters immer als atomare Operation erfolgt, während in der neuen BPF-Version das Laden des Programms und das Binden an den Ereignisgenerator zeitlich getrennt sind.

Die verborgene Wahrheit

Eine etwas genauere Version der Ausgabe sieht so aus:

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0xbeefbeefbeef}, 16) = 0
recvfrom(3, 0x7ffcad394257, 1, MSG_TRUNC, NULL, NULL) = -1 EAGAIN (Ressource vorübergehend nicht verfügbar)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

Wie bereits erwähnt, laden wir unseren Filter an der Zeile 5 und verbinden ihn mit dem Socket, aber was passiert in den Zeilen 3 und 4? Es stellt sich heraus, dass dies libpcap kümmert sich um uns, damit Pakete, die unseren Kriterien nicht entsprechen, nicht durch den Filter gelangen. Bibliothek anschließt Dummy-Filter ret #0 (alle Pakete verwerfen), setzt den Socket in den nicht-blockierenden Modus und versucht, alle Pakete auszulesen, die von vorherigen Filtern übrig geblieben sein könnten.

Zusammenfassend, um Pakete unter Linux mit klassischem BPF zu filtern, benötigt man einen Filter in Form einer Struktur vom Typ struct sock_fprog und einen geöffneten Socket, nach dem der Filter mit einem Systemaufruf an den Socket angefügt werden kann. setsockopt.

Interessanterweise kann der Filter an jeden Socket angefügt werden, nicht nur an raw. Hier ist Nummer 00 oder ein Programm, das alle Daten außer den ersten zwei Bytes aller eingehenden UDP-Datagramme abschneidet. (Kommentare habe ich im Code hinzugefügt, um den Artikel nicht zu überladen.)

Mehr über die Verwendung setsockopt zum Anschließen von Filtern finden Sie in der socket(7), und über das Schreiben eigener Filter vom Typ struct sock_fprog ohne Hilfe tcpdump werden wir im Abschnitt Programmieren von BPF mit eigenen Händen.

Klassisches BPF und das 21. Jahrhundert

BPF wurde 1997 in Linux integriert und blieb lange Zeit eine Arbeitskraft libpcap ohne nennenswerte Änderungen (Linux-spezifische Änderungen, selbstverständlich, wurden, aber sie haben das globale Bild nicht verändert). Die ersten ernsthaften Anzeichen dafür, dass sich BPF weiterentwickeln wird, traten 2011 auf, als Eric Dumazet vorschlug Patch, das einen Just-In-Time-Compiler in den Kernel integriert – einen Übersetzer, um den BPF-Bytecode in nativen Code umzuwandeln. x86_64 Code.

Der JIT-Compiler war die erste in der Reihe von Änderungen: 2012 wurde eingeführt die Möglichkeit, Filter zu schreiben für seccomp, mit BPF im Januar 2013 eingeführt wurde ein Modul xt_bpf, der es ermöglicht, Regeln für iptables mithilfe von BPF zu schreiben, wurde im Oktober 2013 veröffentlicht ein und auch ein Modul cls_bpf, das mithilfe von BPF Traffic-Klassifizierer schreibt.

Wir werden all diese Beispiele in Kürze näher betrachten, aber zunächst ist es nützlich, zu lernen, wie man beliebige Programme für BPF schreibt und kompiliert, da die Möglichkeiten, die die Bibliothek bietet, libpcap begrenzt sind (ein einfaches Beispiel: Ein Filter, der generiert wird libpcap kann nur zwei Werte zurückgeben – 0 oder 0x40000) oder im Falle von seccomp überhaupt nicht anwendbar ist.

Programmieren von BPF mit eigenen Händen

Lassen Sie uns mit dem binären Format der BPF-Anweisungen vertrautmachen, es ist sehr einfach:

   16    8    8     32
| code | jt | jf |  k  |

Jede Anweisung nimmt 64 Bit ein, wobei die ersten 16 Bit der Opcode sind, gefolgt von zwei acht Bit Offsetwerten, jt und jf, und 32 Bit für das Argument K, dessen Zweck je nach Team variiert. Zum Beispiel hat das Team ret, das die Programmausführung abschließt, den Code 6, und der Rückgabewert stammt aus einer Konstante K. In der C-Sprache wird eine BPF-Anweisung als Struktur dargestellt

struct sock_filter {
        __u16   code;
        __u8    jt;
        __u8    jf;
        __u32   k;
}

und ein ganzes Programm wird als Struktur dargestellt

struct sock_fprog {
        unsigned short len;
        struct sock_filter *filter;
}

So können wir bereits Programme schreiben (die Codes der Anweisungen kennen wir beispielsweise aus [1]). So wird der Filter aussehen ip6 von unseres ersten Beispiels.:

struct sock_filter code[] = {
        { 0x28, 0, 0, 0x0000000c },
        { 0x15, 0, 1, 0x000086dd },
        { 0x06, 0, 0, 0x00040000 },
        { 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog prog = {
        .len = ARRAY_SIZE(code),
        .filter = code,
};

Programm prog können wir legal im Aufruf verwenden

setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))

Programme in Form von Maschinencodes zu schreiben, ist nicht besonders bequem, aber manchmal erforderlich (zum Beispiel zur Fehlersuche, zur Erstellung von Unit-Tests, zum Schreiben von Artikeln auf Habr usw.). Zur Vereinfachung werden in der Datei <linux/filter.h> Hilfs-Makros definiert – das gleiche Beispiel wie oben könnte neu geschrieben werden als

struct sock_filter code[] = {
        BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
        BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETH_P_IPV6, 0, 1),
        BPF_STMT(BPF_RET|BPF_K, 0x00040000),
        BPF_STMT(BPF_RET|BPF_K, 0),
}

Allerdings ist auch diese Variante nicht besonders praktisch. Das dachten sich auch die Entwickler des Linux-Kernels, weshalb sich im Verzeichnis tools/bpf des Kernels ein Assembler und Debugger zur Arbeit mit klassischem BPF finden lässt.

Die Assemblersprache ähnelt stark der Debug-Ausgabe tcpdump, aber zusätzlich können wir symbolische Labels angeben. Zum Beispiel hier ein Programm, das alle Pakete außer TCP/IPv4 verwirft:

$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0

Standardmäßig generiert der Assembler Code im Format , ,..., für unser Beispiel mit TCP ergibt sich

$ tools/bpf/bpf_asm /tmp/tcp-over-ipv4.bpf
6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 6,6 0 0 4294967295,6 0 0 0,

Für die Bequemlichkeit der C-Programmierer kann ein anderes Ausgabeformat verwendet werden:

$ tools/bpf/bpf_asm -c /tmp/tcp-over-ipv4.bpf
{ 0x28,  0,  0, 0x0000000c },
{ 0x15,  0,  3, 0x00000800 },
{ 0x30,  0,  0, 0x00000017 },
{ 0x15,  0,  1, 0x00000006 },
{ 0x06,  0,  0, 0xffffffff },
{ 0x06,  0,  0, 0000000000 },

Dieser Text kann in die Definition der Struktur vom Typ struct sock_filter, wie wir es zu Beginn dieses Abschnitts gemacht haben.

Linux-Erweiterungen und netsniff-ng

Neben den Standardanweisungen BPF bieten Linux und tools/bpf/bpf_asm unterstützen und eine benutzerdefinierte Menge. Im Wesentlichen dienen die Anweisungen zum Zugriff auf die Felder der Struktur struct sk_buff, die ein Netzwerkpaket im Kern beschreibt. Es gibt jedoch auch Anweisungen anderer Art, wie z. B. ldw cpu lädt das Ergebnis des Aufrufs der Kernel-Funktion A raw_smp_processor_id() . (In der neuen Version von BPF wurden diese benutzerdefinierten Erweiterungen erweitert, um den Programmen einen Satz von Kernel-Helpers bereitzustellen, um auf Speicher, Strukturen zuzugreifen und Ereignisse zu generieren.) Hier ist ein interessantes Beispiel für einen Filter, bei dem wir nur die Header von Paketen in den Benutzerspeicher kopieren, indem wir die Erweiterung verwendenpoff , Payload-Offset:ld poff ret a

BPF-Erweiterungen können nicht in

, aber es ist ein guter Grund, sich mit dem Paket von Dienstprogrammen vertraut zu machen tcpdumpnetsniff-ng , das unter anderem ein fortschrittliches Programm enthält, das neben der Filterung mittels BPF auch einen effektiven Traffic-Generator enthält und fortschrittlicher ist als , das unter anderem ein fortschrittliches Programm enthält, der BPF-Assembler mit dem Namen tools/bpf/bpf_asmbpfc . Das Paket enthält eine ziemlich detaillierte Dokumentation; siehe auch die Links am Ende des Artikels.. Das Paket enthält recht ausführliche Dokumentation, siehe auch die Links am Ende des Artikels.

seccomp

Wir haben bereits gelernt, BPF-Programme beliebiger Komplexität zu schreiben, und sind bereit, uns neue Beispiele anzusehen. Das erste davon ist die Technologie seccomp, die es ermöglicht, durch BPF-Filter die Vielzahl und die Argumente der Systemaufrufe zu steuern, die diesem Prozess und seinen Nachkommen zur Verfügung stehen.

Die erste Version von seccomp wurde 2005 in den Kernel eingefügt und erfreute sich zunächst keiner großen Beliebtheit, da sie lediglich die Möglichkeit bot, die Menge der verfügbaren Systemaufrufe für den Prozess auf Folgendes zu beschränken: read, write, exit und sigreturn, und ein Prozess, der gegen diese Regeln verstieß, wurde durch SIGKILLbeendet. Im Jahr 2012 wurde jedoch die Möglichkeit eingeführt, BPF-Filter in seccomp zu verwenden, die es ermöglichen, eine Vielzahl erlaubter Systemaufrufe zu definieren und sogar Prüfungen ihrer Argumente durchzuführen. (Interessanterweise war einer der ersten Nutzer dieser Funktionalität Chrome, und derzeit entwickeln die Chrome-Entwickler ein KRSI-Mechanismus, der auf der neuen Version von BPF basiert und die Anpassung von Linux Security Modules ermöglicht.) Links zur zusätzlichen Dokumentation finden Sie am Ende des Artikels.

Beachten Sie, dass auf Habr bereits Artikel über die Verwendung von seccomp veröffentlicht wurden. Vielleicht möchten einige von Ihnen diese vor (oder anstelle von) der Lektüre der folgenden Abschnitte lesen. In dem Artikel Container und Sicherheit: seccomp werden Beispiele zur Verwendung von seccomp, sowohl in der Version von 2007 als auch in der Version mit BPF (Filter werden mithilfe von libseccomp generiert) vorgestellt. Es wird die Beziehung zwischen seccomp und Docker erläutert und viele nützliche Links sind enthalten. In dem Artikel Isolieren von Daemons mit systemd oder ‚Sie brauchen dafür kein Docker!‘ wird unter anderem erklärt, wie Sie schwarze oder weiße Listen von Systemaufrufen für Daemons, die von systemd verwaltet werden, hinzufügen können.

Als Nächstes schauen wir uns an, wie wir Filter für seccomp in reinem C und mithilfe der Bibliothek libseccomp schreiben und laden können und welche Vor- und Nachteile jede Variante hat. Zum Schluss sehen wir uns an, wie seccomp von dem Programm strace.

Filter für seccomp schreiben und laden

Wir können bereits BPF-Programme schreiben und werden daher zunächst den Programmierschnittstellen von seccomp betrachten. Der Filter kann auf Prozessebene gesetzt werden, wobei alle Kindprozesse die Einschränkungen übernehmen. Dies geschieht über den Systemaufruf seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

wobei &filter — ist ein Zeiger auf die bereits bekannte Struktur struct sock_fprog, d.h. das BPF-Programm.

Wie unterscheiden sich die Programme für seccomp von den Programmen für Sockets? Durch den übergebenen Kontext. Im Falle von Sockets wurde uns ein Speicherbereich übergeben, der das Paket enthält, während uns für seccomp eine Struktur des Typs übergeben wird

struct seccomp_data {
    int   nr;
    __u32 arch;
    __u64 instruction_pointer;
    __u64 args[6];
};

Hier nr — ist die Nummer des aufgerufenen Systemaufrufs, arch — die aktuelle Architektur (darüber später), .) Wir können dieses Rezept mit dem vorherigen kombinieren, um den Test im Debugger zu starten: — bis zu sechs Argumente des Systemaufrufs, und instruction_pointer — ist der Zeiger auf die Anweisung im Benutzerspeicher, die diesen Systemaufruf vorgenommen hat. Um beispielsweise die Nummer des Systemaufrufs in ein Register zu laden, A müssen wir sagen

ldw [0]

Für seccomp-Programme gibt es weitere Besonderheiten, zum Beispiel ist der Zugriff auf den Kontext nur für 32-Bit-Ausrichtung möglich und es können keine Halbworte oder Bytes geladen werden — beim Versuch, den Filter zu laden, ldh [0] Systemaufruf seccomp gibt zurück EINVAL. Die Überprüfung der geladenen Filter erfolgt durch die Funktion seccomp_check_filter() Kerne. (Lustigerweise wurde im ursprünglichen Commit, der die Seccomp-Funktionalität hinzufügte, vergessen, das Recht zum Verwenden der Anweisung einzufügen, mod (Restwert) und jetzt ist sie für Seccomp BPF-Programme nicht verfügbar, da ihre Hinzufügung brechen würde ABI.)

Im Grunde genommen wissen wir bereits alles, um Seccomp-Programme zu schreiben und zu lesen. Die Logik eines Programms ist normalerweise wie eine schwarze oder weiße Liste von Systemaufrufen aufgebaut, zum Beispiel das Programm

ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0

überprüft eine schwarze Liste von vier Systemaufrufen mit den Nummern 304, 176, 239, 279. Was sind das für Systemaufrufe? Wir können das nicht genau sagen, da wir nicht wissen, für welche Architektur das Programm geschrieben wurde. Daher empfehlen die Autoren von Seccomp anzufangen, alle Programme mit einer Überprüfung der Architektur (die aktuelle Architektur wird im Kontext als Feld der Struktur arch struct seccomp_data ) angegeben.) Mit der Architekturüberprüfung würde der Anfang des Beispiels folgendermaßen aussehen:ld [4] jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64

und dann würden unsere Nummern der Systemaufrufe bestimmte Werte erhalten.

Wir schreiben und laden Filter für Seccomp mithilfe von

Wir schreiben und laden Filter für seccomp mithilfe von libseccomp

Das Schreiben von Filtern in Maschinencodes oder für BPF-Assembler ermöglicht eine vollständige Kontrolle über das Ergebnis, aber manchmal ist es vorzuziehen, tragbaren und/oder lesbaren Code zu haben. Dabei hilft uns die Bibliothek libseccomp, die eine standardisierte Schnittstelle für das Erstellen von Blacklists oder Whitelists bietet.

Lassen Sie uns beispielsweise ein Programm schreiben, das eine vom Benutzer gewählte Binärdatei ausführt, nachdem wir zuvor eine Blacklist von Systemaufrufen aus dem oben genannten Artikel (das Programm wurde zur besseren Lesbarkeit vereinfacht; die vollständige Version ist hier):

#include <seccomp.h>
#include <unistd.h>
#include <err.h>

static int sys_numbers[] = {
        __NR_mount,
        __NR_umount2,
       // ... еще 40 системных вызовов ...
        __NR_vmsplice,
        __NR_perf_event_open,
};

int main(int argc, char **argv)
{
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);

        for (size_t i = 0; i < sizeof(sys_numbers)/sizeof(sys_numbers[0]); i++)
                seccomp_rule_add(ctx, SCMP_ACT_TRAP, sys_numbers[i], 0);

        seccomp_load(ctx);

        execvp(argv[1], &argv[1]);
        err(1, "execlp: %s", argv[1]);
}

Zunächst definieren wir ein Array sys_numbers , das über 40 Nummern von Systemaufrufen zum Blockieren enthält. Danach initialisieren wir den Kontext ctx und teilen der Bibliothek mit, dass wir standardmäßig (SCMP_ACT_ALLOW) alle Systemaufrufe erlauben möchten (es ist einfacher, Blacklists zu erstellen). Dann fügen wir nacheinander alle Systemaufrufe aus der Blacklist hinzu. Als Reaktion auf einen Systemaufruf aus der Liste fordern wir SCMP_ACT_TRAP, in diesem Fall wird seccomp dem Prozess das Signal SIGSYS mit einer Beschreibung, welcher spezifische Systemaufruf die Regeln verletzt hat. Schließlich laden wir das Programm in den Kernel mit Hilfe von seccomp_load, das das Programm kompiliert und es mithilfe des Systemaufrufs mit dem Prozess verbindet seccomp(2).

Um die Kompilierung erfolgreich abzuschließen, muss das Programm mit der Bibliothek verlinkt werden libseccomp, zum Beispiel:

cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccomp

Beispiel für einen erfolgreichen Start:

$ ./seccomp_lib echo ok
ok

Beispiel für einen blockierten Systemaufruf:

$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Bad system call

Wir verwenden strace, um weitere Einzelheiten zu erfahren:

$ sudo strace -e seccomp ./seccomp_lib mount -t bpf bpf /tmp
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=50, filter=0x55d8e78428e0}) = 0
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xboobdeadbeef, si_syscall=__NR_mount, si_arch=AUDIT_ARCH_X86_64} ---
+++ durch SIGSYS beendet (Kernabbild erstellt) +++
Bad system call

woher wir wissen können, dass das Programm aufgrund der Verwendung eines verbotenen Systemaufrufs beendet wurde mount(2).

Zusammenfassend haben wir einen Filter unter Verwendung der Bibliothek geschrieben, libseccomp, wobei wir nicht triviale Codes in vier Zeilen untergebracht haben. Im obigen Beispiel kann bei einer Vielzahl von Systemaufrufen die Ausführungszeit deutlich verringert werden, da die Überprüfung einfach eine Liste von Vergleichen ist. Zur Optimierung wurde kürzlich in libseccomp ein Patch integriert, das die Unterstützung des Filterattributs hinzufügt SCMP_FLTATR_CTL_OPTIMIZE. Wenn dieses Attribut auf 2 gesetzt wird, wird der Filter in ein Programm zur binären Suche umgewandelt.

Wenn Sie sehen möchten, wie Filter mit binärer Suche funktionieren, werfen Sie einen Blick auf ein einfaches Skript, das solche Programme in BPF-Assembler basierend auf einer Liste von Systemaufrufnummern generiert, zum Beispiel:

$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, bad
jgt #6, check8
jeq #1, bad
jeq #3, bad
ret #0x7fff0000
check8:
jeq #8, bad
jeq #13, bad
ret #0x7fff0000
bad: ret #0

Nichts wesentlichs Schnelleres lässt sich nicht schreiben, da BPF-Programme keine Sprünge durch Einrückung durchführen können (wir können zum Beispiel nicht jmp A oder jmp [label+X]) und daher sind alle Sprünge statisch.

seccomp und strace

Jeder kennt das Dienstprogramm strace — ein unverzichtbares Werkzeug zur Analyse des Verhaltens von Prozessen unter Linux. Viele haben jedoch auch von Leistungsproblemen bei der Verwendung dieses Dienstprogramms gehört. Das liegt daran, dass strace dies durch ptrace(2), und in diesem Mechanismus können wir nicht angeben, auf welcher bestimmten Menge von Systemaufrufen wir den Prozess anhalten möchten, d.h., zum Beispiel die Befehle

$ time strace du /usr/share/ >/dev/null 2>&1

real    0m3.081s
user    0m0.531s
sys     0m2.073s

und

$ time strace -e open du /usr/share/ >/dev/null 2>&1

real    0m2.404s
user    0m0.193s
sys     0m1.800s

arbeiten in etwa gleich schnell, obwohl wir im zweiten Fall nur einen Systemaufruf nachverfolgen möchten.

Neue Option --seccomp-bpf, eingeführt in strace Version 5.3, ermöglicht es, den Prozess erheblich zu beschleunigen, und die Startzeit unter Nachverfolgung eines Systemaufrufs ist bereits mit der Zeit eines regulären Starts vergleichbar:

$ time strace --seccomp-bpf -e open du /usr/share/ >/dev/null 2>&1

real    0m0.148s
user    0m0.017s
sys     0m0.131s

$ time du /usr/share/ >/dev/null 2>&1

real    0m0.140s
user    0m0.024s
sys     0m0.116s

(Hier gibt es natürlich eine kleine Täuschung, da wir nicht den primären Systemaufruf dieses Befehls nachverfolgen. Wenn wir beispielsweise newfsstat, dann strace würde es genauso stark verlangsamen wie ohne --seccomp-bpf.)

Wie funktioniert diese Option? Ohne sie strace verbindet sich mit dem Prozess und startet ihn mit Hilfe von PTRACE_SYSCALL. Wenn der gesteuerte Prozess einen (beliebigen) Systemaufruf ausführt, wird die Kontrolle übergeben strace, der die Argumente des Systemaufrufs betrachtet und ihn mit Hilfe von PTRACE_SYSCALL. Nach einiger Zeit beendet der Prozess den Systemaufruf und beim Verlassen wird die Kontrolle wieder übergeben strace, der die Rückgabewerte betrachtet und den Prozess mit Hilfe von PTRACE_SYSCALL, usw.

BPF für Einsteiger, Teil Null: klassisches BPF

Mit seccomp lässt sich dieser Prozess jedoch genau so optimieren, wie wir es uns wünschen. Wenn wir also nur auf den Systemaufruf schauen wollen, Xkönnen wir einen BPF-Filter schreiben, der für X den Rückgabewert SECCOMP_RET_TRACEliest, und für solche Aufrufe, die uns nicht interessieren, — SECCOMP_RET_ALLOW:

ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000

In diesem Fall strace startet man den Prozess zunächst als PTRACE_CONT, für jeden Systemaufruf wird unser Filter ausgeführt; wenn der Systemaufruf nicht X, dann fährt der Prozess fort, aber wenn es sich um X, übergibt seccomp die Kontrolle strace, der die Argumente betrachtet und den Prozess als PTRACE_SYSCALL (da seccomp keine Möglichkeit bietet, ein Programm beim Verlassen des Systemaufrufs zu starten). Sobald der Systemaufruf zurückkehrt, strace wird der Prozess mit Hilfe von PTRACE_CONT neu gestartet und wartet auf neue Nachrichten von seccomp.

BPF für Einsteiger, Teil Null: klassisches BPF

Bei Verwendung der Option --seccomp-bpf gibt es zwei Einschränkungen. Erstens, es ist nicht möglich, sich an einen bereits bestehenden Prozess anzuhängen (Option -p Programme strace), da dies von seccomp nicht unterstützt wird. Zweitens, es gibt keine Möglichkeit, nicht auf untergeordnete Prozesse zuzugreifen, da die seccomp-Filter von allen untergeordneten Prozessen geerbt werden, ohne die Möglichkeit, dies zu deaktivieren.

Ein paar zusätzliche Informationen darüber, wie genau strace arbeitet mit seccomp kann man dem entnehmen einem aktuellen Bericht. Am interessantesten für uns ist, dass der klassische BPF in Form von seccomp immer noch verwendet wird.

xt_bpf

Lassen Sie uns nun zurück in die Welt der Netzwerke reisen.

Vorgeschichte: vor langer Zeit, im Jahr 2007, wurde im Kernel ein Modul xt_u32 für netfilter eingeführt. Es wurde ähnlich wie ein noch älterer Verkehrsklassifizierer cls_u32 geschrieben und erlaubte es, beliebige binäre Regeln für iptables durch die folgenden einfachen Operationen zu schreiben: 32 Bit aus dem Paket zu laden und darauf eine Reihe arithmetischer Operationen anzuwenden. Zum Beispiel,

sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"

Lädt 32 Bit des IP-Headers, beginnend mit dem Offset 6, und wendet eine Maske an 0xFF (nimmt das niedrigste Byte). Dies ist das Feld protocol des IP-Headers und wir vergleichen es mit 1 (ICMP). In einer Regel können viele Prüfungen kombiniert werden und man kann auch den Operator verwenden @ – gehe X Bytes nach rechts. Zum Beispiel prüft die Regel

iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"

ob die TCP-Sequenznummer ungleich ist. 0x29Ich werde nicht weiter ins Detail gehen, da bereits klar ist, dass es unangenehm ist, solche Regeln von Hand zu schreiben. Im Artikel BPF — der vergessene Bytecode, es gibt mehrere Links mit Beispielen zur Verwendung und Generierung von Regeln für xt_u32. Siehe auch die Links am Ende dieses Artikels.

Seit 2013 kann anstelle des Moduls xt_u32 ein auf BPF basierendes Modul verwendet werden xt_bpf. Für alle, die bis hierhin gelesen haben, sollte das Prinzip seiner Funktionsweise klar sein: Ausführen von BPF-Bytecode als iptables-Regeln. Ein neues Regel kann man zum Beispiel so erstellen:

iptables -A INPUT -m bpf --bytecode  -j LOG

hier <байткод> — das ist der Code im Format der Ausgabe von Assembler bpf_asm standardmäßig, zum Beispiel,

$ cat /tmp/test.bpf
ldb [9]
jneq #17, ignore
ret #1
ignore: ret #0

$ bpf_asm /tmp/test.bpf
4,48 0 0 9,21 0 1 17,6 0 0 1,6 0 0 0,

# iptables -A INPUT -m bpf --bytecode "$(bpf_asm /tmp/test.bpf)" -j LOG

In diesem Beispiel filtern wir alle UDP-Pakete. Der Kontext für das BPF-Programm im Modul xt_bpf, verweist natürlich auf die Paketdaten, im Fall von iptables – auf den Beginn des IPv4-Headers. Der Rückgabewert aus dem BPF-Programm ist boolesch, wobei false und bedeutet, dass das Paket nicht übereinstimmte.

Es ist klar, dass das Modul xt_bpf komplexere Filter als im obigen Beispiel unterstützt. Schauen wir uns echte Beispiele von Cloudflare an. Bis vor kurzem verwendeten sie das Modul xt_bpf zum Schutz vor DDoS-Angriffen. In dem Artikel Introducing the BPF Tools Sie erklären, wie (und warum) sie BPF-Filter generieren und veröffentlichen Links zu einer Sammlung von Werkzeugen zur Erstellung solcher Filter. Zum Beispiel kann man mit dem Tool bpfgen ein BPF-Programm erstellen, das DNS-Anfragen auf den Namen abgleicht habr.com:

$ ./bpfgen --assembly dns -- habr.com
ldx 4*([0]&0xf)
ld #20
add x
tax

lb_0:
    ld [x + 0]
    jneq #0x04686162, lb_1
    ld [x + 4]
    jneq #0x7203636f, lb_1
    ldh [x + 8]
    jneq #0x6d00, lb_1
    ret #65535

lb_1:
    ret #0

Im Programm laden wir zunächst in das Register X die Adresse des Anfangs der Zeichenkette x04habrx03comx00 innerhalb des UDP-Datagramms und überprüfen dann die Anfrage: 0x04686162 "x04hab" usw.

Kurz danach veröffentlichte Cloudflare den Code des p0f-Compilers -> BPF. In dem Artikel Introducing the p0f BPF compiler erklären sie, was p0f ist und wie man p0f-Signaturen in BPF umwandelt:

$ ./bpfgen p0f -- 4:64:0:0:*,0::ack+:0
39,0 0 0 0,48 0 0 8,37 35 0 64,37 0 34 29,48 0 0 0,
84 0 0 15,21 0 31 5,48 0 0 9,21 0 29 6,40 0 0 6,
...

Derzeit verwendet Cloudflare nicht mehr xt_bpf, da sie auf XDP umgestiegen sind – eine der Möglichkeiten, die neue Version von BPF zu nutzen, siehe L4Drop: XDP DDoS Mitigations.

cls_bpf

Das letzte Beispiel für die Verwendung von klassischem BPF im Kernel ist der Klassifikator cls_bpf für das Traffic-Control-System in Linux, das Ende 2013 in Linux hinzugefügt wurde und konzeptionell den alten ersetzt hat cls_u32.

Wir werden jedoch nicht auf die Funktionsweise eingehen cls_bpf, da uns das hinsichtlich des Wissens über klassisches BPF nichts nützt – wir haben bereits alle Funktionalitäten kennengelernt. Außerdem werden wir in zukünftigen Artikeln, die sich mit Extended BPF beschäftigen, immer wieder auf diese Klassifikation stoßen.

Ein weiterer Grund, nicht über die Verwendung von klassischem BPF zu sprechen, cls_bpf liegt darin, dass im Vergleich zu Extended BPF in diesem Fall das Anwendungsgebiet erheblich eingeschränkt ist: klassische Programme können den Inhalt von Paketen nicht verändern und können den Zustand zwischen den Aufrufen nicht speichern.

Es ist also an der Zeit, Abschied von klassischem BPF zu nehmen und einen Blick in die Zukunft zu werfen.

Abschied von classic BPF

Wir haben uns angesehen, wie die 1990 entwickelte BPF-Technologie erfolgreich ein Vierteljahrhundert überstand und kontinuierlich neue Anwendungen fand. Doch ähnlich wie der Übergang von Stackmaschinen zu RISC, der zur Entwicklung des klassischen BPF führte, erlebten wir in den 2000er Jahren den Übergang von 32-Bit- zu 64-Bit-Maschinen, wodurch das klassische BPF veraltet erschien. Außerdem sind die Möglichkeiten des klassischen BPF stark eingeschränkt: Abgesehen von der veralteten Architektur haben wir keine Möglichkeit, den Zustand zwischen den Aufrufen von BPF-Programmen zu speichern, können nicht direkt mit dem Benutzer interagieren und der Kontakt zum Kernel ist auf das Lesen einer begrenzten Anzahl von Feldern der Struktur beschränkt. sk_buff und das Starten von einfachsten Hilfsfunktionen, es ist nicht möglich, den Inhalt von Paketen zu ändern oder sie weiterzuleiten.

Tatsächlich ist von dem klassischen BPF in Linux nur noch die API-Schnittstelle geblieben; im Kernel werden alle klassischen Programme, seien es Socket-Filter oder Seccomp-Filter, automatisch in das neue Format, Extended BPF, übersetzt. (In dem nächsten Artikel werden wir erklären, wie genau dies geschieht.)

Der Übergang zu einer neuen Architektur begann im Jahr 2013, als Alexey Starovoitov ein Upgrade-Schema für BPF vorschlug. Im Jahr 2014 erschienen dann die entsprechenden Patches im Kernel. Soweit ich verstehe, war ursprünglich nur geplant, die Architektur und den JIT-Compiler zu optimieren, um eine effizientere Nutzung auf 64-Bit-Maschinen zu ermöglichen. Stattdessen bildeten diese Optimierungen die Grundlage für ein neues Kapitel in der Entwicklung von Linux.

Weitere Artikel in dieser Serie werden über die Architektur und Anwendungen der neuen Technologie berichten, die ursprünglich als internal BPF, dann als extended BPF und nun als einfach BPF bekannt ist.

Links

  1. Steven McCanne und Van Jacobson, "The BSD Packet Filter: Eine neue Architektur für die Paketaufnahme auf Benutzerebene" https://www.tcpdump.org/papers/bpf-usenix93.pdf
  2. Steven McCanne, "libpcap: Eine Architektur und Optimierungsmethodik für die Paketaufnahme" https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. IPtable U32 Match Tutorial.
  5. BPF – der vergessene Bytecode: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. Einführung des BPF-Tools: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. Eine Übersicht über seccomp: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: Container und Sicherheit: seccomp
  11. habr: Isolierung von Daemons mit systemd oder "Docker braucht man dafür nicht!"
  12. Paul Chaignon, "strace —seccomp-bpf: ein Blick unter die Haube" https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. , das unter anderem ein fortschrittliches Programm enthält: http://netsniff-ng.org/

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster