Mir schreiwen Schutz géint DDoS Attacken op XDP. Nuklear Deel

eXpress Data Path (XDP) Technologie erlaabt zoufälleg Trafficveraarbechtung op Linux Interfaces auszeféieren ier d'Päckchen an de Kernelnetzstack kommen. Uwendung vun XDP - Schutz géint DDoS Attacken (CloudFlare), komplex Filteren, Statistik Sammlung (Netflix). XDP Programmer ginn vun der eBPF virtueller Maschinn ausgefouert, sou datt se Restriktiounen op hire Code an déi verfügbare Kernelfunktiounen hunn ofhängeg vum Filtertyp.

Den Artikel soll d'Mängel vu ville Materialien op XDP ausfëllen. Als éischt bidden se e fäerdege Code deen direkt d'Features vum XDP ëmgeet: et ass virbereet fir d'Verifizéierung oder ass ze einfach fir Problemer ze verursaachen. Wann Dir dann probéiert Äre Code vun Null ze schreiwen, hutt Dir keng Ahnung wat Dir mat typesche Feeler maache musst. Zweetens, Weeër fir XDP lokal ze testen ouni VM an Hardware sinn net ofgedeckt, trotz der Tatsaach datt se hir eege Fallen hunn. Den Text ass geduecht fir Programméierer déi mat Netzwierker a Linux vertraut sinn, déi un XDP an eBPF interesséiert sinn.

An dësem Deel wäerte mir am Detail verstoen wéi den XDP-Filter zesummegesat ass a wéi et ze testen ass, da schreiwen mir eng einfach Versioun vum bekannte SYN Cookie Mechanismus um Paketveraarbechtungsniveau. Fir de Moment wäerte mir keng "wäiss Lëscht" erstellen
verifizéiert Clienten, halen Zähler a verwalten de Filter - genuch Logbicher.

Mir schreiwen am C - et ass net moudesch, awer et ass praktesch. All Code ass verfügbar op GitHub iwwer de Link um Enn an ass opgedeelt a Verpflichtungen no den Etappen, déi am Artikel beschriwwe ginn.

Disclaimer. Am Laf vun dësem Artikel gëtt eng Mini-Léisung entwéckelt fir DDoS-Attacke ofzewieren, well dëst eng realistesch Aufgab fir XDP a mäi Feld ass. Wéi och ëmmer, d'Haaptziel ass d'Technologie ze verstoen; dëst ass net e Guide fir fäerdege Schutz ze kreéieren. Den Tutorialcode ass net optimiséiert a léisst e puer Nuancen of.

XDP Kuerz Iwwerbléck

Ech wäert nëmmen d'Schlësselpunkte skizzéieren fir d'Dokumentatioun an d'bestehend Artikelen net ze duplizéieren.

Also, de Filtercode gëtt an de Kernel gelueden. Entréeën Pakete ginn un de Filter weidergeleet. Als Resultat muss de Filter eng Entscheedung treffen: de Paket an de Kärel (XDP_PASS), drop packen (XDP_DROP) oder schéckt et zréck (XDP_TX). De Filter kann de Package änneren, dëst gëllt besonnesch fir XDP_TX. Dir kënnt och de Programm ofbriechen (XDP_ABORTED) an de Package zrécksetzen, awer dëst ass analog assert(0) - fir Debugging.

D'eBPF (extended Berkley Packet Filter) virtuell Maschinn ass bewosst einfach gemaach, sou datt de Kernel kontrolléiere kann datt de Code net an d'Schleifen geet an d'Erënnerung vun anere Leit net beschiedegt. Kumulativ Restriktiounen a Kontrollen:

  • Loops (zréck) sinn verbueden.
  • Et gëtt e Stack fir Daten, awer keng Funktiounen (all C Funktiounen musse inlined sinn).
  • Erënnerungszougäng ausserhalb vum Stack a Paketbuffer sinn verbueden.
  • D'Codegréisst ass limitéiert, awer an der Praxis ass dëst net ganz bedeitend.
  • Nëmmen Uruff un speziell Kernelfunktiounen (eBPF Helfer) sinn erlaabt.

Den Design an Installatioun vun engem Filter gesäit esou aus:

  1. Quellcode (z.B kernel.c) ass an Objekt kompiléiert (kernel.o) ënner der eBPF virtuell Maschinnarchitektur. Zënter Oktober 2019 gëtt d'Kompilatioun op eBPF vum Clang ënnerstëtzt a versprach am GCC 10.1.
  2. Wann dësen Objektcode Uriff un Kernelstrukturen enthält (zum Beispill Dëscher a Konter), ginn hir IDen duerch Nullen ersat, dat heescht datt esou Code net ausgefouert ka ginn. Ier Dir an de Kernel lued, musst Dir dës Nullen duerch d'IDs vu spezifeschen Objeten ersetzen, déi duerch Kernelruffen erstallt ginn (link de Code). Dir kënnt dat mat externen Utilities maachen, oder Dir kënnt e Programm schreiwen, deen e spezifesche Filter verlinkt an lued.
  3. De Kernel verifizéiert de geluedene Programm. D'Feele vu Zyklen an Net-Traversal vu Paket- a Stackgrenze ginn iwwerpréift. Wann de Verifizéierer net beweise kann datt de Code richteg ass, gëtt de Programm refuséiert - Dir musst him fäeg sinn ze gefalen.
  4. No der erfollegräicher Verifikatioun kompiléiert de Kernel den eBPF Architekturobjektcode an de Systemarchitektur Maschinncode (just-in-time).
  5. De Programm befestegt un d'Interface a fänkt un Päck ze veraarbecht.

Zënter XDP am Kernel leeft, gëtt Debugging mat Trace Logbicher duerchgefouert an tatsächlech Päckchen déi de Programm filtert oder generéiert. Wéi och ëmmer, eBPF garantéiert datt de geluedene Code fir de System sécher ass, sou datt Dir mat XDP direkt op Ärem lokalen Linux experimentéiere kënnt.

Ëmweltvirbereedung

Assemblée

Clang kann net direkt Objektcode fir d'eBPF Architektur produzéieren, sou datt de Prozess aus zwee Schrëtt besteet:

  1. Kompiléiert C Code op LLVM Bytecode (clang -emit-llvm).
  2. Konvertéiert Bytecode an eBPF Objektcode (llc -march=bpf -filetype=obj).

Wann Dir e Filter schreift, sinn e puer Dateie mat Hëllefsfunktiounen a Makroen nëtzlech aus Kernel Tester. Et ass wichteg datt se mat der Kernel Versioun passen (KVER). Download hinnen op helpers/:

export KVER=v5.3.7
export BASE=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/tools/testing/selftests/bpf
wget -P helpers --content-disposition "${BASE}/bpf_helpers.h?h=${KVER}" "${BASE}/bpf_endian.h?h=${KVER}"
unset KVER BASE

Makefile fir Arch Linux (Kernel 5.3.7):

CLANG ?= clang
LLC ?= llc

KDIR ?= /lib/modules/$(shell uname -r)/build
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

CFLAGS = 
    -Ihelpers 
    
    -I$(KDIR)/include 
    -I$(KDIR)/include/uapi 
    -I$(KDIR)/include/generated/uapi 
    -I$(KDIR)/arch/$(ARCH)/include 
    -I$(KDIR)/arch/$(ARCH)/include/generated 
    -I$(KDIR)/arch/$(ARCH)/include/uapi 
    -I$(KDIR)/arch/$(ARCH)/include/generated/uapi 
    -D__KERNEL__ 
    
    -fno-stack-protector -O2 -g

xdp_%.o: xdp_%.c Makefile
    $(CLANG) -c -emit-llvm $(CFLAGS) $< -o - | 
    $(LLC) -march=bpf -filetype=obj -o $@

.PHONY: all clean

all: xdp_filter.o

clean:
    rm -f ./*.o

KDIR enthält de Wee zu de Kernel Header, ARCH - Systemarchitektur. Weeër an Tools kënne liicht variéieren tëscht Verdeelungen.

Beispill vun Differenzen fir Debian 10 (Kernel 4.19.67)

# другая команда
CLANG ?= clang
LLC ?= llc-7

# другой каталог
KDIR ?= /usr/src/linux-headers-$(shell uname -r)
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

# два дополнительных каталога -I
CFLAGS = 
    -Ihelpers 
    
    -I/usr/src/linux-headers-4.19.0-6-common/include 
    -I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include 
    # далее без изменений

CFLAGS verbënnt e Verzeechnes mat Hëllefsheader a verschidde Verzeichnisser mat Kernel Header. Symbol __KERNEL__ heescht datt UAPI (Userspace API) Header fir de Kernelcode definéiert sinn, well de Filter am Kernel ausgefouert gëtt.

Stack Schutz kann ausgeschalt ginn (-fno-stack-protector), well den eBPF Code Verifizéierer nach ëmmer op Stack ausserhalb Verstéiss iwwerpréift. Et ass derwäert Optimisatiounen direkt unzeschléissen, well d'Gréisst vum eBPF Bytecode limitéiert ass.

Loosst eis mat engem Filter ufänken deen all Pakete passéiert an näischt mécht:

#include <uapi/linux/bpf.h>

#include <bpf_helpers.h>

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Equipe make sammelt xdp_filter.o. Wou probéieren ech et elo?

Teststand

De Stand muss zwee Schnëttplazen enthalen: op deenen et e Filter gëtt a vun deem Pakete geschéckt ginn. Dës musse vollwäerteg Linux-Geräter mat hiren eegene IP sinn fir ze kontrolléieren wéi regelméisseg Uwendungen mat eisem Filter funktionnéieren.

Apparater vum Typ veth (virtuell Ethernet) si fir eis gëeegent: dëst sinn e Paar virtuelle Netzwierkschnëttplazen "verbonne" direkt mateneen. Dir kënnt se esou erstellen (an dëser Sektioun all Kommandoen ip ausgefouert ginn root):

ip link add xdp-remote type veth peer name xdp-local

et ass xdp-remote и xdp-local - Apparat Nimm. Op xdp-local (192.0.2.1/24) gëtt e Filter befestegt, mat xdp-remote (192.0.2.2/24) erakommen Verkéier gëtt geschéckt. Wéi och ëmmer, et gëtt e Problem: d'Interfaces sinn op der selwechter Maschinn, a Linux wäert de Verkéier net un ee vun hinnen duerch déi aner schécken. Dir kënnt dëst mat schwieregen Regelen léisen iptables, awer si mussen Packagen änneren, wat onbequem ass fir Debugging. Et ass besser fir Netzwierknummraim ze benotzen (nodréiglech netns).

En Netzwierk Nummraum enthält eng Rei vun Interfaces, Routingtabellen, an NetFilter Regelen, isoléiert vun ähnlechen Objeten an aneren Netns. All Prozess leeft an engem Nummraum an huet nëmmen Zougang zu den Objete vun deem Netns. Par défaut huet de System en eenzegen Netznummraum fir all Objeten, sou datt Dir am Linux schaffe kënnt an net iwwer netns wësst.

Loosst eis en neien Nummraum erstellen xdp-test a réckelt et dohinner xdp-remote.

ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test

Da leeft de Prozess an xdp-test, wäert net "gesinn" xdp-local (et bleift Standard an Netns) a wann Dir e Paket op 192.0.2.1 schéckt, gëtt et duerch xdp-remote, well dëst déi eenzeg Interface op 192.0.2.0/24 ass fir dëse Prozess zougänglech. Dëst funktionnéiert och an de Géigendeel Richtung.

Wann Dir tëscht Netns plënnert, geet d'Interface erof a verléiert seng Adress. Fir den Interface an netns ze konfiguréieren, musst Dir lafen ip ... an dësem Kommando Nummraum ip netns exec:

ip netns exec xdp-test 
    ip address add 192.0.2.2/24 dev xdp-remote
ip netns exec xdp-test 
    ip link set xdp-remote up

Wéi Dir kënnt gesinn, ass dëst net anescht wéi d'Astellung xdp-local am Standard Nummraum:

    ip address add 192.0.2.1/24 dev xdp-local
    ip link set xdp-local up

Wann Dir leeft tcpdump -tnevi xdp-local, Dir kënnt gesinn, datt Pakete geschéckt vun xdp-test, ginn op dësen Interface geliwwert:

ip netns exec xdp-test   ping 192.0.2.1

Et ass bequem fir eng Shell ze starten xdp-test. De Repository huet e Skript deen d'Aarbecht mam Stand automatiséiert; zum Beispill kënnt Dir de Stand mam Kommando konfiguréieren sudo ./stand up an läschen et sudo ./stand down.

Tracing

De Filter ass mat engem Apparat wéi dësen assoziéiert:

ip -force link set dev xdp-local xdp object xdp_filter.o verbose

Schlëssel -force néideg fir en neie Programm ze verbannen wann en anere scho verlinkt ass. "Keng Neiegkeet ass gutt Neiegkeet" ass net iwwer dëst Kommando, d'Ausgab ass op jidde Fall voluminös. uginn verbose fakultativ, awer domat erschéngt e Bericht iwwer d'Aarbecht vum Code Verifizéierer mat der Versammlungsoplëschtung:

Verifier analysis:

0: (b7) r0 = 2
1: (95) exit

Unlink de Programm vum Interface:

ip link set dev xdp-local xdp off

An engem Skript sinn dës Kommandoen sudo ./stand attach и sudo ./stand detach.

Andeems Dir e Filter befestegt, kënnt Dir sécher sinn, datt ping funktionnéiert weider, awer funktionnéiert de Programm? Loosst eis Logbicher addéieren. Funktioun bpf_trace_printk() ähnlech wéi printf(), awer ënnerstëtzt nëmmen bis zu dräi Argumenter aner wéi d'Muster, an eng limitéiert Lëscht vun Spezifizéierer. Makro bpf_printk() vereinfacht den Uruff.

   SEC("prog")
   int xdp_main(struct xdp_md* ctx) {
+      bpf_printk("got packet: %pn", ctx);
       return XDP_PASS;
   }

D'Ausgab geet op de Kernel Trace Kanal, dee muss aktivéiert ginn:

echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk

Kuckt de Message Thread:

cat /sys/kernel/debug/tracing/trace_pipe

Béid vun dëse Kommandoen maachen en Uruff sudo ./stand log.

Ping soll elo Messagen wéi dës ausléisen:

<...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377

Wann Dir d'Output vum Verifizéierer genau kuckt, gesitt Dir komesch Berechnungen:

0: (bf) r3 = r1
1: (18) r1 = 0xa7025203a7465
3: (7b) *(u64 *)(r10 -8) = r1
4: (18) r1 = 0x6b63617020746f67
6: (7b) *(u64 *)(r10 -16) = r1
7: (bf) r1 = r10
8: (07) r1 += -16
9: (b7) r2 = 16
10: (85) call bpf_trace_printk#6
<...>

De Fakt ass datt eBPF Programmer keng Datensektioun hunn, sou datt deen eenzege Wee fir e Formatstring ze codéieren ass déi direkt Argumenter vu VM Kommandoen:

$ python -c "import binascii; print(bytes(reversed(binascii.unhexlify('0a7025203a74656b63617020746f67'))))"
b'got packet: %pn'

Aus dësem Grond bloatst Debugoutput immens de finalen Code.

Schéckt XDP Pakete

Loosst eis de Filter änneren: loosst et all erakommen Pakete zréckschécken. Dëst ass net korrekt aus engem Netz Siicht, well et néideg wier d'Adressen an den Header z'änneren, awer elo ass d'Aarbecht am Prinzip wichteg.

       bpf_printk("got packet: %pn", ctx);
-      return XDP_PASS;
+      return XDP_TX;
   }

Lancéiere tcpdump op xdp-remote. Et sollt identesch erausginn an erakommen ICMP Echo Ufro weisen an ophalen ICMP Echo Reply ze weisen. Mä et weist net. Et stellt sech eraus, datt fir Aarbecht XDP_TX am Programm op xdp-local ass néidegop de Pair Interface xdp-remote e Programm gouf och zougewisen, och wann et eidel war, an hie gouf opgewuess.

Wéi wosst ech dat?

Trace de Wee vun engem Package am Kernel De Perf Event Mechanismus erlaabt, iwwregens, déi selwecht virtuell Maschinn ze benotzen, dat heescht eBPF gëtt benotzt fir mat eBPF ze këmmeren.

Dir musst Gutt aus Béisen maachen, well et ass näischt anescht fir et aus ze maachen.

$ sudo perf trace --call-graph dwarf -e 'xdp:*'
   0.000 ping/123455 xdp:xdp_bulk_tx:ifindex=19 action=TX sent=0 drops=1 err=-6
                                     veth_xdp_flush_bq ([veth])
                                     veth_xdp_flush_bq ([veth])
                                     veth_poll ([veth])
                                     <...>

Wat ass Code 6?

$ errno 6
ENXIO 6 No such device or address

Funktioun veth_xdp_flush_bq() kritt e Feelercode vun veth_xdp_xmit(), wou Sich no ENXIO an fannen de Commentaire.

Loosst eis de Minimum Filter restauréieren (XDP_PASS) am Fichier xdp_dummy.c, fügen se an d'Makefile, verlinkt et xdp-remote:

ip netns exec remote 
    ip link set dev int xdp object dummy.o

Elo tcpdump weist wat erwaart gëtt:

62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64
62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64

Wann nëmmen ARPs amplaz ugewise ginn, musst Dir d'Filter ewechhuelen (dëst sudo ./stand detach), lass loossen ping, setzt dann d'Filteren a probéiert nach eng Kéier. De Problem ass datt de Filter XDP_TX valabel fir béid ARP a Stack
Nummraim xdp-test et fäerdeg bruecht huet d'MAC Adress 192.0.2.1 ze "vergiess", wäert et net fäeg sinn dës IP ze léisen.

Problemerklärung

Loosst eis op déi uginn Aufgab goen: e SYN Cookie Mechanismus op XDP ze schreiwen.

SYN Iwwerschwemmung bleift e populär DDoS Attack, d'Essenz vun deem ass wéi follegt. Wann eng Verbindung etabléiert ass (TCP Handshake), kritt de Server e SYN, verdeelt Ressourcen fir déi zukünfteg Verbindung, reagéiert mat engem SYNACK Paket a waart op en ACK. Den Ugräifer schéckt einfach SYN Pakete vu gefälschten Adressen an den Dausende pro Sekonn vun all Host an engem Multi-Dausend-staark Botnet. De Server ass gezwongen Ressourcen direkt no der Arrivée vum Paket ze verdeelen, awer verëffentlecht se no engem groussen Timeout; als Resultat sinn d'Erënnerung oder d'Limiten erschöpft, nei Verbindunge ginn net akzeptéiert, an de Service ass net verfügbar.

Wann Dir Ressourcen net op Basis vum SYN Paket verdeelt, awer nëmme mat engem SYNACK Paket reagéiert, wéi kann de Server dann verstoen datt den ACK Paket, dee spéider ukomm ass, op e SYN Paket bezitt deen net gespäichert gouf? No allem kann en Ugräifer och gefälschte ACKs generéieren. De Punkt vum SYN Cookie ass et ze codéieren seqnum Verbindung Parameteren als Hash vun Adressen, Häfen an änneren Salz. Wann den ACK et fäerdeg bruecht huet ze kommen ier d'Salz geännert gouf, kënnt Dir den Hash nach eng Kéier berechent a vergläicht mat acknum. Schmieden acknum den Ugräifer kann net, well d'Salz e Geheimnis enthält, a wäert net fäeg sinn duerch e limitéierten Kanal ze sortéieren.

De SYN Cookie ass scho laang am Linux Kernel implementéiert a ka souguer automatesch aktivéiert ginn wann SYNs ze séier an masseg ukommen.

Educatiounsprogramm iwwer TCP Handshake

TCP bitt Dateniwwerdroung als Stream vu Bytes, zum Beispill HTTP-Ufroe ginn iwwer TCP iwwerdroen. De Stroum gëtt a Stécker a Pakete iwwerdroen. All TCP Pakete hunn logesch Fändelen an 32-Bit Sequenznummeren:

  • D'Kombinatioun vu Fändelen bestëmmt d'Roll vun engem bestëmmte Package. De SYN Fändel bedeit datt dëst den éischte Paket vum Sender an der Verbindung ass. Den ACK Fändel bedeit datt de Sender all Verbindungsdaten bis zum Byte kritt huet acknum. E Paket kann e puer Fändelen hunn a gëtt duerch hir Kombinatioun benannt, zum Beispill e SYNACK Paket.

  • Sequenznummer (Seqnum) spezifizéiert den Offset am Datestroum fir den éischte Byte deen an dësem Paket iwwerdroe gëtt. Zum Beispill, wann am éischte Paket mat X Bytes vun Daten dës Zuel N war, am nächste Paket mat neien Daten wäert et N + X sinn. Um Ufank vun der Verbindung wielt all Partei dës Zuel zoufälleg.

  • Unerkennungsnummer (acknum) ass déiselwecht Offset wéi seqnum, awer et bestëmmt net d'Zuel vum Byte, deen iwwerdroe gëtt, awer d'Zuel vum éischte Byte vum Empfänger, deen de Sender net gesinn huet.

Um Ufank vun der Verbindung mussen d'Parteien averstane sinn seqnum и acknum. De Client schéckt e SYN Paket mat sengem seqnum = X. De Server reagéiert mat engem SYNACK Paket, wou et seng records seqnum = Y an Ausstellungen acknum = X + 1. De Client reagéiert op SYNACK mat engem ACK Paket, wou seqnum = X + 1, acknum = Y + 1. Duerno fänkt den aktuellen Datenübertragung un.

Wann de Peer d'Empfang vum Paket net unerkennt, schéckt TCP et no engem Timeout.

Firwat ginn SYN Cookien net ëmmer benotzt?

Als éischt, wann de SYNACK oder ACK verluer ass, musst Dir op e Re-Send waarden - d'Verbindungsetablissement verlangsamt. Zweetens, am SYN Package - an nëmmen an et! - eng Rei vun Optiounen ginn iwwerdroen, déi de weideren Operatioun vun der Verbindung beaflossen. Andeems Dir net un erakomm SYN Päckchen erënnert, ignoréiert de Server also dës Optiounen; de Client schéckt se net méi an den nächste Päckchen. TCP kann an dësem Fall funktionnéieren, awer op d'mannst an der éischter Etapp wäert d'Qualitéit vun der Verbindung erofgoen.

Aus enger Package Perspektiv muss en XDP Programm déi folgend maachen:

  • reagéiert op SYN mat SYNACK mat engem Cookie;
  • reagéiert op ACK mat RST (trennen);
  • déi verbleiwen Paketen ofleeën.

Pseudocode vum Algorithmus zesumme mam Package Parsing:

Если это не Ethernet,
    пропустить пакет.
Если это не IPv4,
    пропустить пакет.
Если адрес в таблице проверенных,               (*)
        уменьшить счетчик оставшихся проверок,
        пропустить пакет.
Если это не TCP,
    сбросить пакет.     (**)
Если это SYN,
    ответить SYN-ACK с cookie.
Если это ACK,
    если в acknum лежит не cookie,
        сбросить пакет.
    Занести в таблицу адрес с N оставшихся проверок.    (*)
    Ответить RST.   (**)
В остальных случаях сбросить пакет.

Eent (*) D'Punkten wou Dir den Zoustand vum System verwalten musst sinn markéiert - op der éischter Etapp kënnt Dir ouni si maachen andeems Dir einfach en TCP Handshake implementéiert mat der Generatioun vun engem SYN Cookie als Seqnum.

Op der Plaz (**), wa mir keen Dësch hunn, wäerte mir de Pak iwwersprangen.

Ëmsetzung TCP Handshake

Parsing de Package a verifizéiert de Code

Mir brauchen Netzwierk Header Strukturen: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) an TCP (uapi/linux/tcp.h). Ech konnt ni déi lescht konnektéieren wéinst Feeler am Zesummenhang mat atomic64_t, Ech hu missen déi néideg Definitiounen an de Code kopéieren.

All Funktiounen, déi fir d'Liesbarkeet am C zougewisen sinn, mussen am Uruffpunkt inlined sinn, well den eBPF Verifizéierer am Kärel verbueden Récksprangen, dat ass, tatsächlech, Loops a Funktiounsruffen.

#define INTERNAL static __attribute__((always_inline))

Macro LOG() deaktivéiert Dréckerei am Verëffentlechungsbau.

De Programm ass e Conveyor vu Funktiounen. Jidderee kritt e Paket an deem en Header vum passenden Niveau markéiert ass, zum Beispill, process_ether() erwaart datt et gefëllt gëtt ether. Baséierend op d'Resultater vun der Feldanalyse kann d'Funktioun de Paket op e méi héije Niveau passéieren. D'Resultat vun der Funktioun ass eng XDP Aktioun. Fir de Moment passéieren d'SYN an ACK Handler all Päck.

struct Packet {
    struct xdp_md* ctx;

    struct ethhdr* ether;
    struct iphdr* ip;
    struct tcphdr* tcp;
};

INTERNAL int process_tcp_syn(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp_ack(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp(struct Packet* packet) { ... }
INTERNAL int process_ip(struct Packet* packet) { ... }

INTERNAL int
process_ether(struct Packet* packet) {
    struct ethhdr* ether = packet->ether;

    LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));

    if (ether->h_proto != bpf_ntohs(ETH_P_IP)) {
        return XDP_PASS;
    }

    // B
    struct iphdr* ip = (struct iphdr*)(ether + 1);
    if ((void*)(ip + 1) > (void*)packet->ctx->data_end) {
        return XDP_DROP; /* malformed packet */
    }

    packet->ip = ip;
    return process_ip(packet);
}

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    struct Packet packet;
    packet.ctx = ctx;

    // A
    struct ethhdr* ether = (struct ethhdr*)(void*)ctx->data;
    if ((void*)(ether + 1) > (void*)ctx->data_end) {
        return XDP_PASS;
    }

    packet.ether = ether;
    return process_ether(&packet);
}

Ech zéien Är Opmierksamkeet op d'Schecken markéiert A a B. Wann Dir A kommentéiert, wäert de Programm bauen, awer et gëtt e Verifizéierungsfehler beim Luede:

Verifier analysis:

<...>
11: (7b) *(u64 *)(r10 -48) = r1
12: (71) r3 = *(u8 *)(r7 +13)
invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0)
R7 offset is outside of the packet
processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

Error fetching program/map!

Schlëssel String invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Et gi Ausféierungsweeër wann den dräizéngten Byte vum Ufank vum Puffer ausserhalb vum Paket ass. Et ass schwéier aus der Oplëschtung ze verstoen iwwer wéi eng Linn mir schwätzen, awer et gëtt eng Instruktiounsnummer (12) an en Disassembler deen d'Zeilen vum Quellcode weist:

llvm-objdump -S xdp_filter.o | less

An dësem Fall weist et op d'Linn

LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));

wat mécht et kloer, datt de Problem ass ether. Et wier ëmmer esou.

Äntwert op SYN

D'Zil op dëser Etapp ass e korrekt SYNACK Paket mat engem fixen ze generéieren seqnum, deen an Zukunft duerch SYN Cookie ersat gëtt. All Ännerungen geschéien am process_tcp_syn() an Ëmgéigend Beräicher.

Package Verifikatioun

Komesch genuch, hei ass déi bemierkenswäert Linn, oder éischter, de Kommentar dozou:

/* Required to verify checksum calculation */
const void* data_end = (const void*)ctx->data_end;

Wann Dir déi éischt Versioun vum Code schreift, gouf den 5.1 Kernel benotzt, fir de Verifizéierer vun deem en Ënnerscheed tëscht data_end и (const void*)ctx->data_end. Zu der Zäit vum Artikel ze schreiwen, Kernel 5.3.1 huet dëse Problem net. Vläicht huet de Compiler Zougang zu enger lokaler Variabel anescht wéi e Feld. Moral vun der Geschicht: Mat grousse Nestsituatiounen kann d'Vereinfachung vum Code hëllefen.

Nächst sinn Routine Längt Schecken fir d'Herrlechkeet vum Verifizéierer; O MAX_CSUM_BYTES drënner.

const u32 ip_len = ip->ihl * 4;
if ((void*)ip + ip_len > data_end) {
    return XDP_DROP; /* malformed packet */
}
if (ip_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

const u32 tcp_len = tcp->doff * 4;
if ((void*)tcp + tcp_len > (void*)ctx->data_end) {
    return XDP_DROP; /* malformed packet */
}
if (tcp_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

De Pak ausklappen

Mir fëllen an seqnum и acknum, set ACK (SYN ass schonn agestallt):

const u32 cookie = 42;
tcp->ack_seq = bpf_htonl(bpf_ntohl(tcp->seq) + 1);
tcp->seq = bpf_htonl(cookie);
tcp->ack = 1;

Tauscht TCP Ports, IP Adress an MAC Adressen. D'Standardbibliothéik ass net vum XDP Programm zougänglech, also memcpy() - e Makro deen d'Clang Intrinsik verstoppt.

const u16 temp_port = tcp->source;
tcp->source = tcp->dest;
tcp->dest = temp_port;

const u32 temp_ip = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = temp_ip;

struct ethhdr temp_ether = *ether;
memcpy(ether->h_dest, temp_ether.h_source, ETH_ALEN);
memcpy(ether->h_source, temp_ether.h_dest, ETH_ALEN);

Neiberechnung vu Kontrollsummen

IPv4- an TCP-Kontrollsummen erfuerderen d'Additioun vun all 16-Bit Wierder an den Header, an d'Gréisst vun den Header gëtt an hinnen geschriwwen, dat heescht onbekannt bei der Kompiléierungszäit. Dëst ass e Problem well de Verifizéierer net duerch déi normal Loop op déi verännerlech Grenz schläift. Awer d'Gréisst vun den Header ass limitéiert: bis zu 64 Bytes all. Dir kënnt eng Loop mat enger fixer Unzuel vun Iteratiounen maachen, déi fréi ophalen.

Ech feststellen, datt et RFC 1624 iwwer wéi een d'Kontrollsum deelweis ëmberechent, wann nëmmen déi fix Wierder vun de Packagen geännert ginn. Allerdéngs ass d'Method net universell, an d'Ëmsetzung wier méi schwéier ze erhalen.

Checksum Berechnung Funktioun:

#define MAX_CSUM_WORDS 32
#define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2)

INTERNAL u32
sum16(const void* data, u32 size, const void* data_end) {
    u32 s = 0;
#pragma unroll
    for (u32 i = 0; i < MAX_CSUM_WORDS; i++) {
        if (2*i >= size) {
            return s; /* normal exit */
        }
        if (data + 2*i + 1 + 1 > data_end) {
            return 0; /* should be unreachable */
        }
        s += ((const u16*)data)[i];
    }
    return s;
}

Obwuel size verifizéiert duerch den Uruffcode, ass déi zweet Ausgangsbedéngung noutwendeg fir datt de Verifizéierer d'Réalisatioun vun der Loop beweise kann.

Fir 32-Bit Wierder gëtt eng méi einfach Versioun implementéiert:

INTERNAL u32
sum16_32(u32 v) {
    return (v >> 16) + (v & 0xffff);
}

Tatsächlech d'Kontrollsummen nei berechnen an de Paket zréck schécken:

ip->check = 0;
ip->check = carry(sum16(ip, ip_len, data_end));

u32 tcp_csum = 0;
tcp_csum += sum16_32(ip->saddr);
tcp_csum += sum16_32(ip->daddr);
tcp_csum += 0x0600;
tcp_csum += tcp_len << 8;
tcp->check = 0;
tcp_csum += sum16(tcp, tcp_len, data_end);
tcp->check = carry(tcp_csum);

return XDP_TX;

Funktioun carry() mécht eng Kontrollsumme vun enger 32-Bit Zomm vu 16-Bit Wierder, laut RFC 791.

TCP Handshake Verifizéierung

De Filter stellt richteg eng Verbindung mat netcat, sprangen de finalen ACK, op deen Linux mat engem RST Paket geäntwert huet, well den Netzwierkstack den SYN net kritt huet - et gouf op SYNACK ëmgewandelt an zréck geschéckt - a vun der OS Siicht ass e Paket ukomm, deen net verbonne war oppe Verbindungen.

$ sudo ip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

Et ass wichteg mat vollwäertege Uwendungen ze kontrolléieren an ze iwwerwaachen tcpdump op xdp-remote well z.B. hping3 reagéiert net op falsch Kontrollsummen.

Aus XDP Siicht ass d'Verifizéierung selwer trivial. De Berechnung Algorithmus ass primitiv a méiglecherweis vulnérabel fir e sophistikéierten Ugräifer. De Linux Kernel, zum Beispill, benotzt de kryptographesche SipHash, awer seng Implementatioun fir XDP ass kloer iwwer den Ëmfang vun dësem Artikel.

Erschéngt fir nei TODOs am ​​Zesummenhang mat externer Interaktioun:

  • Den XDP Programm kann net späicheren cookie_seed (de geheimen Deel vum Salz) an enger globaler Variabel brauch Dir Späicheren am Kär, de Wäert an deem periodesch vun engem zouverléissege Generator aktualiséiert gëtt.

  • Wann de SYN Cookie am ACK Paket entsprécht, musst Dir net e Message drécken, awer erënnere mech un d'IP vum verifizéierte Client fir weider Päckchen dovunner weiderzeginn.

Legitim Client Verifikatioun:

$ sudoip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

D'Logbicher notéieren d'Ofschloss vum Scheck (flags=0x2 - dëst ass SYN, flags=0x10 ass ACK):

Ether(proto=0x800)
  IP(src=0x20e6e11a dst=0x20e6e11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x2)
Ether(proto=0x800)
  IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x10)
      cookie matches for client 20200c0

Och wann et keng Lëscht vu verifizéierte IPs gëtt, gëtt et kee Schutz vun der SYN Iwwerschwemmung selwer, awer hei ass d'Reaktioun op eng ACK Iwwerschwemmung, déi vum folgenden Kommando gestart gouf:

sudo ip netns exec xdp-test   hping3 --flood -A -s 1111 -p 2222 192.0.2.1

Log Entréen:

Ether(proto=0x800)
  IP(src=0x15bd11a dst=0x15bd11e proto=6)
    TCP(sport=3236 dport=2222 flags=0x10)
      cookie mismatch

Konklusioun

Heiansdo ginn eBPF am Allgemengen an XDP besonnesch méi als fortgeschratt Administratorinstrument presentéiert wéi als Entwécklungsplattform. Tatsächlech ass XDP en Tool fir d'Veraarbechtung vu Pakete vum Kernel ze stéieren, an net eng Alternativ zum Kernel Stack, wéi DPDK an aner Kernel Bypass Optiounen. Op der anerer Säit erlaabt XDP Iech eng zimlech komplex Logik ëmzesetzen, déi och einfach ze aktualiséieren ouni Ënnerbriechung an der Trafficveraarbechtung. De Verifizéierer erstellt keng grouss Probleemer; perséinlech géif ech dat net refuséieren fir Deeler vum Userspace Code.

Am zweeten Deel, wann d'Thema interessant ass, wäerte mir den Dësch vu verifizéierte Clienten an Trennungen ofgeschloss hunn, Compteur implementéieren an e Userspace Utility schreiwen fir de Filter ze managen.

Referenzen:

Source: will.com

Setzt e Commentaire