ProHoster > Blog > uprava > Na XDP pišemo zaštitu od DDoS napada. Nuklearni dio
Na XDP pišemo zaštitu od DDoS napada. Nuklearni dio
Tehnologija eXpress Data Path (XDP) omogućuje proizvoljnu obradu prometa na Linux sučeljima prije nego što paketi uđu u mrežni stog kernela. Primjena XDP - zaštita od DDoS napada (CloudFlare), složeni filteri, prikupljanje statistike (Netflix). XDP programe izvršava eBPF virtualni stroj i stoga imaju ograničenja i na njihov kod i na dostupne funkcije kernela, ovisno o vrsti filtra.
Namjera članka je nadoknaditi nedostatke brojnih materijala o XDP-u. Prvo, oni pružaju gotov kod koji odmah zaobilazi značajke XDP-a: pripremljen za provjeru ili previše jednostavan da uzrokuje probleme. Kada kasnije pokušate napisati vlastiti kod od nule, nema razumijevanja što učiniti s tipičnim pogreškama. Drugo, ne pokriva načine lokalnog testiranja XDP-a bez VM-a i hardvera, unatoč činjenici da oni imaju svoje zamke. Tekst je namijenjen programerima poznavateljima mreža i Linuxa koje zanimaju XDP i eBPF.
U ovom ćemo dijelu detaljno razumjeti kako se sastavlja XDP filter i kako ga testirati, zatim ćemo napisati jednostavnu verziju dobro poznatog mehanizma SYN kolačića na razini obrade paketa. Dok ne formiramo "bijelu listu"
provjereni klijenti, voditi brojače i upravljati filtrom - dovoljno zapisa.
Napisat ćemo u C - ovo nije moderno, ali praktično. Sav kod je dostupan na GitHubu na poveznici na kraju i podijeljen je u commitove prema koracima opisanim u članku.
Odricanje. U tijeku članka razvit će se mini rješenje za odbijanje DDoS napada jer je to realan zadatak za XDP i moje područje. Međutim, glavni cilj je razumjeti tehnologiju, ovo nije vodič za stvaranje gotove zaštite. Kôd vodiča nije optimiziran i izostavlja neke nijanse.
Kratak pregled XDP-a
Navest ću samo ključne točke kako ne bih duplirao dokumentaciju i postojeće članke.
Dakle, kod filtera se učitava u kernel. Filtru se prosljeđuju dolazni paketi. Kao rezultat toga, filter mora donijeti odluku: proslijediti paket kernelu (XDP_PASS), ispusti paket (XDP_DROP) ili poslati natrag (XDP_TX). Filter može mijenjati paket, ovo posebno vrijedi za XDP_TX. Također možete srušiti program (XDP_ABORTED) i ispustite paket, ali ovo je analogno assert(0) - za otklanjanje pogrešaka.
eBPF (prošireni Berkley Packet Filter) virtualni stroj namjerno je napravljen jednostavnim kako bi kernel mogao provjeriti da se kod ne ponavlja i ne oštećuje tuđu memoriju. Kumulativna ograničenja i provjere:
Loopovi (skokovi unazad) su zabranjeni.
Postoji stog za podatke, ali nema funkcija (sve C funkcije moraju biti ugrađene).
Zabranjeni su pristupi memoriji izvan stoga i međuspremnika paketa.
Veličina koda je ograničena, ali u praksi to nije previše značajno.
Dopuštene su samo posebne funkcije jezgre (eBPF pomoćnici).
Razvoj i instaliranje filtra izgleda ovako:
izvorni kod (npr. kernel.c) kompajlira u objekt (kernel.o) za arhitekturu eBPF virtualnog stroja. Od listopada 2019. kompajliranje u eBPF podržava Clang i obećano je u GCC 10.1.
Ako u ovom objektnom kodu postoje pozivi strukturama kernela (na primjer, tablicama i brojačima), umjesto njihovih ID-ova postoje nule, odnosno takav se kod ne može izvršiti. Prije učitavanja u kernel, ove nule moraju biti zamijenjene ID-ovima specifičnih objekata kreiranih kroz kernel pozive (povežite kod). To možete učiniti s vanjskim uslužnim programima ili možete napisati program koji će povezati i učitati određeni filtar.
Kernel provjerava program koji se učitava. Provjerava odsutnost ciklusa i neizlazak granica paketa i hrpe. Ako verifikator ne može dokazati da je kod ispravan, program se odbija - mora se moći zadovoljiti.
Nakon uspješne provjere, kernel kompajlira objektni kod eBPF arhitekture u strojni kod arhitekture sustava (just-in-time).
Program je priključen na sučelje i počinje s obradom paketa.
Budući da XDP radi u jezgri, otklanjanje pogrešaka provodi se zapisnicima praćenja i, zapravo, paketima koje program filtrira ili generira. Međutim, eBPF čuva preuzeti kod na sigurnom za sustav, tako da možete eksperimentirati s XDP-om izravno na svom lokalnom Linuxu.
Priprema okoliša
zbor
Clang ne može izravno izdati objektni kod za eBPF arhitekturu, tako da se proces sastoji od dva koraka:
Kompajlirajte C kod u LLVM bajt kod (clang -emit-llvm).
Pretvori bajt kod u eBPF objektni kod (llc -march=bpf -filetype=obj).
Prilikom pisanja filtra dobro će doći nekoliko datoteka s pomoćnim funkcijama i makroima iz kernel testova. Važno je da odgovaraju verziji kernela (KVER). Preuzmite ih na helpers/:
KDIR sadrži put do zaglavlja kernela, ARCH - Arhitektura sustava. Putovi i alati mogu se malo razlikovati između distribucija.
Primjer razlike za 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 uključuju direktorij s pomoćnim zaglavljima i nekoliko direktorija sa zaglavljima jezgre. Simbol __KERNEL__ znači da su zaglavlja UAPI (API korisničkog prostora) definirana za kod kernela, budući da se filtar izvršava u kernelu.
Zaštita skupa može se onemogućiti (-fno-stack-protector) jer verifikator eBPF koda ionako provjerava da li nije izvan granica stoga. Trebali biste odmah omogućiti optimizacije jer je veličina bajt koda eBPF ograničena.
Počnimo s filtrom koji propušta sve pakete i ne radi ništa:
Momčad make prikuplja xdp_filter.o. Gdje ga sada možete testirati?
Ispitno postolje
Stalak bi trebao sadržavati dva sučelja: na kojem će biti filter i s kojeg će se slati paketi. To moraju biti puni Linux uređaji s vlastitim IP-ovima kako bismo provjerili kako obične aplikacije rade s našim filtrom.
Uređaji poput veth (virtualni Ethernet) prikladni su za nas: oni su par virtualnih mrežnih sučelja "povezanih" izravno jedno s drugim. Možete ih izraditi ovako (u ovom odjeljku sve naredbe ip izvedeno iz root):
ip link add xdp-remote type veth peer name xdp-local
Ovdje xdp-remote и xdp-local — imena uređaja. Na xdp-local (192.0.2.1/24) bit će priložen filtar, sa xdp-remote (192.0.2.2/24) poslat će se dolazni promet. Međutim, postoji problem: sučelja su na istom stroju i Linux neće slati promet jednom od njih preko drugog. Možete to riješiti lukavim pravilima iptables, ali će morati promijeniti pakete, što je nezgodno prilikom otklanjanja pogrešaka. Bolje je koristiti mrežne prostore imena (mrežni prostori imena, dalje netns).
Prostor imena mreže sadrži skup sučelja, tablica usmjeravanja i NetFilter pravila koja su izolirana od sličnih objekata u drugim mrežama. Svaki proces se izvodi u nekom prostoru imena i dostupni su mu samo objekti ove mreže. Prema zadanim postavkama, sustav ima jedinstven mrežni prostor imena za sve objekte, tako da možete raditi na Linuxu i ne znati za netns.
Kreirajmo novi imenski prostor xdp-test i preseli se tamo xdp-remote.
ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test
Zatim se pokreće proces xdp-test, neće "vidjeti" xdp-local (ostat će u netns prema zadanim postavkama) i kada šalje paket na 192.0.2.1 proći će ga kroz xdp-remote, jer je to jedino sučelje na 192.0.2.0/24 dostupno ovom procesu. Ovo također radi i obrnuto.
Prilikom prelaska između mreža, sučelje pada i gubi adresu. Za postavljanje sučelja u netns, morate pokrenuti ip ... u ovom prostoru imena naredbi 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
Kao što vidite, ovo se ne razlikuje od postavljanja xdp-local u zadanom prostoru imena:
ip address add 192.0.2.1/24 dev xdp-local
ip link set xdp-local up
Ako trči tcpdump -tnevi xdp-local, možete vidjeti da su paketi poslani s xdp-test, isporučuju se na ovo sučelje:
ip netns exec xdp-test ping 192.0.2.1
Prikladno je pokrenuti školjku xdp-test. Repozitorij ima skriptu koja automatizira rad sa postoljem, na primjer, možete postaviti postolje naredbom sudo ./stand up i uklonite ga sudo ./stand down.
traganje
Filtar je pričvršćen na uređaj ovako:
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
ključ -force potreban za povezivanje novog programa ako je drugi već povezan. "Nema vijesti je dobra vijest" ne odnosi se na ovu naredbu, izlaz je ionako obiman. naznačiti verbose izborno, ali uz njega se pojavljuje izvješće o radu verifikatora koda s popisom asemblera:
Verifier analysis:
0: (b7) r0 = 2
1: (95) exit
Odvojite program od sučelja:
ip link set dev xdp-local xdp off
U skripti su to naredbe sudo ./stand attach и sudo ./stand detach.
Vezivanjem filtra možete to osigurati ping nastavlja raditi, ali radi li program? Dodajmo logotipe. Funkcija bpf_trace_printk() slično printf(), ali podržava samo do tri argumenta osim uzorka i ograničen popis specifikacija. Makro bpf_printk() pojednostavljuje poziv.
Iz tog razloga izlaz za otklanjanje pogrešaka uvelike povećava rezultirajući kod.
Slanje XDP paketa
Promijenimo filtar: neka šalje sve dolazne pakete natrag. To je netočno s mrežnog gledišta, jer bi bilo potrebno promijeniti adrese u zaglavljima, ali sada je važan rad u načelu.
Pokreni tcpdump na xdp-remote. Trebao bi prikazati identičan odlazni i dolazni ICMP Echo Request i prestati prikazivati ICMP Echo Reply. Ali ne vidi se. Ispostavilo se da djeluje XDP_TX u programu za xdp-localnužanza uparivanje sučelja xdp-remote također je dodijeljen program, čak i ako je bio prazan, i podignut je.
Kako sam znao?
Praćenje putanje paketa u kernelu mehanizam perf događaja omogućuje, usput, korištenje istog virtualnog stroja, odnosno eBPF se koristi za rastavljanje s eBPF-om.
Od zla morate napraviti dobro, jer se od njega ne može napraviti ništa drugo.
Ako se umjesto toga prikazuje samo ARP, trebate ukloniti filtre (ovo čini sudo ./stand detach), neka ping, zatim instalirajte filtre i pokušajte ponovo. Problem je u tome što filter XDP_TX također utječe na ARP, a ako stek
imenski prostori xdp-test uspio "zaboraviti" MAC adresu 192.0.2.1, neće moći riješiti ovu IP adresu.
Formuliranje problema
Prijeđimo na navedeni zadatak: napisati mehanizam SYN kolačića na XDP.
Do sada, SYN poplava ostaje popularan DDoS napad, čija je suština sljedeća. Kada se veza uspostavi (TCP rukovanje), poslužitelj prima SYN, dodjeljuje resurse za buduću vezu, odgovara SYNACK paketom i čeka ACK. Napadač jednostavno šalje SYN pakete s lažnih adresa u količini od tisuća u sekundi sa svakog hosta u više tisuća botnet-a. Poslužitelj je prisiljen alocirati resurse odmah po dolasku paketa, ali ga oslobađa nakon dugog vremenskog ograničenja, kao rezultat toga, memorija ili ograničenja su iscrpljeni, nove veze se ne prihvaćaju, usluga je nedostupna.
Ako ne dodijelite resurse na SYN paketu, nego samo odgovorite sa SYNACK paketom, kako onda poslužitelj može razumjeti da ACK paket koji je došao kasnije pripada SYN paketu koji nije spremljen? Uostalom, napadač također može generirati lažne ACK-ove. Bit SYN kolačića je kodiranje u seqnum parametri veze kao hash adresa, portova i promjene soli. Ako je ACK uspio stići prije promjene soli, možete ponovno izračunati hash i usporediti ga acknum. lažan acknum napadač ne može, budući da sol uključuje tajnu, i neće imati vremena pregledati je zbog ograničenog kanala.
SYN kolačići implementirani su u Linux kernel već dugo vremena i mogu se čak automatski omogućiti ako SYN-ovi stignu prebrzo i u velikom broju.
Obrazovni program o TCP rukovanju
TCP osigurava prijenos podataka kao tok bajtova, na primjer, HTTP zahtjevi se prenose preko TCP-a. Tok se prenosi dio po dio u paketima. Svi TCP paketi imaju logičke oznake i 32-bitne redne brojeve:
Kombinacija zastavica definira ulogu određenog paketa. Oznaka SYN znači da je ovo prvi paket pošiljatelja na vezi. ACK oznaka znači da je pošiljatelj primio sve podatke o vezi do bajta. acknum. Paket može imati nekoliko zastavica i nazvan je prema njihovoj kombinaciji, na primjer, SYNACK paket.
Broj sekvence (seqnum) određuje pomak u toku podataka za prvi bajt koji se šalje u ovom paketu. Na primjer, ako je u prvom paketu s X bajtova podataka taj broj bio N, u sljedećem paketu s novim podacima to će biti N+X. Na početku razgovora svaka strana odabire ovaj broj nasumično.
Broj potvrde (acknum) - isti pomak kao seqnum, ali ne određuje broj poslanog bajta, već broj prvog bajta od primatelja, koji pošiljatelj nije vidio.
Na početku spajanja strane se moraju dogovoriti seqnum и acknum. Klijent šalje SYN paket sa svojim seqnum = X. Poslužitelj odgovara SYNACK paketom, gdje zapisuje svoje seqnum = Y i razotkriva acknum = X + 1. Klijent odgovara na SYNACK s ACK paketom, gdje seqnum = X + 1, acknum = Y + 1. Nakon toga počinje stvarni prijenos podataka.
Ako sugovornik ne potvrdi primitak paketa, TCP ga ponovno šalje nakon isteka vremena.
Zašto se SYN kolačići ne koriste uvijek?
Prvo, ako se izgubi SYNACK ili ACK, morat ćete pričekati ponovno slanje - uspostava veze se usporava. Drugo, u SYN paketu - i samo u njemu! - prenosi se niz opcija koje utječu na daljnji rad veze. Ne pamteći dolazne SYN pakete, poslužitelj stoga ignorira ove opcije, u sljedećim paketima klijent ih više neće slati. TCP u ovom slučaju može raditi, ali barem u početnoj fazi kvaliteta veze će se smanjiti.
Što se tiče paketa, XDP program bi trebao raditi sljedeće:
odgovoriti na SYN sa SYNACK s kolačićem;
odgovoriti na ACK s RST (prekinuti vezu);
ispustite druge pakete.
Pseudokod algoritma zajedno s raščlanjivanjem paketa:
Если это не Ethernet,
пропустить пакет.
Если это не IPv4,
пропустить пакет.
Если адрес в таблице проверенных, (*)
уменьшить счетчик оставшихся проверок,
пропустить пакет.
Если это не TCP,
сбросить пакет. (**)
Если это SYN,
ответить SYN-ACK с cookie.
Если это ACK,
если в acknum лежит не cookie,
сбросить пакет.
Занести в таблицу адрес с N оставшихся проверок. (*)
Ответить RST. (**)
В остальных случаях сбросить пакет.
Jedan (*) označene su točke u kojima trebate upravljati stanjem sustava - u prvoj fazi možete i bez njih jednostavnim implementiranjem TCP rukovanja uz generiranje SYN kolačića kao seqnuma.
Na stranici (**), dok nemamo stol, paket ćemo preskočiti.
Implementacija TCP rukovanja
Raščlanjivanje paketa i provjera koda
Potrebne su nam strukture zaglavlja mreže: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) i TCP (uapi/linux/tcp.h). Posljednji nisam mogao spojiti zbog grešaka povezanih s njim atomic64_t, morao sam kopirati potrebne definicije u kod.
Sve funkcije koje se razlikuju u C-u radi čitljivosti moraju biti ugrađene na mjesto poziva, budući da eBPF verifikator u kernelu zabranjuje povratne skokove, odnosno, zapravo, petlje i pozive funkcija.
Program je cjevovod funkcija. Svaki prima paket u kojem je označeno zaglavlje odgovarajuće razine, na primjer, process_ether() čekaju da budu ispunjeni ether. Na temelju rezultata analize polja, funkcija može prenijeti paket na višu razinu. Rezultat funkcije je XDP akcija. Dok rukovatelji SYN i ACK propuštaju sve pakete.
Ključni niz invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): postoje staze izvršenja kada je trinaesti bajt od početka međuspremnika izvan paketa. Teško je iz popisa reći o kojoj liniji govorimo, ali postoji broj instrukcije (12) i disassembler koji prikazuje retke izvornog koda:
što jasno daje do znanja da je problem ether. Uvijek bi bilo tako.
Odgovorite na SYN
Cilj u ovoj fazi je generirati ispravan SYNACK paket s fiksnim seqnum, koji će u budućnosti biti zamijenjen kolačićem SYN. Sve promjene se događaju u process_tcp_syn() i okolice.
Provjera paketa
Začudo, evo najznamenitijeg retka, odnosno komentara na njega:
Prilikom pisanja prve verzije koda korišten je kernel 5.1 za čiji je verifikator postojala razlika između data_end и (const void*)ctx->data_end. U vrijeme pisanja, kernel 5.3.1 nije imao ovaj problem. Možda je kompajler pristupao lokalnoj varijabli drugačije nego polju. Moral - kod velikog ugniježđivanja može pomoći pojednostavljenje koda.
Daljnje rutinske provjere duljina za slavu verifikatora; O MAX_CSUM_BYTES u nastavku.
Zamijenite TCP portove, IP i MAC adrese. Standardna knjižnica nije dostupna iz programa XDP, tako da memcpy() — makronaredba koja skriva Clang intrinsik.
IPv4 i TCP kontrolni zbrojevi zahtijevaju dodavanje svih 16-bitnih riječi u zaglavlja, a veličina zaglavlja je zapisana u njima, odnosno u trenutku kompilacije je nepoznata. To je problem jer verifikator neće preskočiti normalnu petlju sve do granične varijable. Ali veličina zaglavlja je ograničena: do 64 bajta svako. Možete napraviti petlju s fiksnim brojem ponavljanja, koja može završiti ranije.
Napominjem da postoji RFC 1624 o tome kako ponovno djelomično izračunati kontrolni zbroj ako se promijene samo fiksne riječi paketa. Međutim, metoda nije univerzalna, a provedbu bi bilo teže održati.
Funkcija izračuna kontrolne sume:
#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;
}
Iako size provjerava pozivni kod, drugi izlazni uvjet je neophodan kako bi verifikator mogao dokazati kraj petlje.
Za 32-bitne riječi implementirana je jednostavnija verzija:
INTERNAL u32
sum16_32(u32 v) {
return (v >> 16) + (v & 0xffff);
}
Zapravo ponovno izračunavanje kontrolnih zbrojeva i slanje paketa natrag:
Funkcija carry() čini kontrolni zbroj od 32-bitnog zbroja 16-bitnih riječi, prema RFC 791.
Provjera TCP rukovanja
Filtar ispravno uspostavlja vezu s netcat, preskačući konačni ACK, na što je Linux odgovorio RST paketom, budući da mrežni stog nije primio SYN - pretvoren je u SYNACK i poslan natrag - a sa stajališta OS-a, stigao je paket koji nije vezane uz otvorene veze.
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer
Važno je provjeriti s punopravnim aplikacijama i promatrati tcpdump na xdp-remote jer npr. hping3 ne reagira na netočne kontrolne zbrojeve.
SYN kolačić
Sa stajališta XDP-a sama provjera je trivijalna. Algoritam izračuna je primitivan i vjerojatno ranjiv na sofisticiranog napadača. Linux kernel, na primjer, koristi kriptografski SipHash, ali njegova implementacija za XDP očito je izvan opsega ovog članka.
Pojavilo se za nove TODO-ove koji se odnose na vanjsku interakciju:
XDP program ne može pohraniti cookie_seed (tajni dio soli) u globalnoj varijabli potrebna vam je kernel pohrana čija će se vrijednost povremeno ažurirati iz pouzdanog generatora.
Ako se SYN kolačić u ACK paketu podudara, ne morate ispisivati poruku, ali zapamtite IP verificiranog klijenta kako biste dalje preskakali pakete s njega.
Ponekad se eBPF općenito, a posebno XDP predstavljaju više kao napredni administratorski alat nego razvojna platforma. Doista, XDP je alat za ometanje obrade kernel paketa, a ne alternativa kernel stacku, poput DPDK-a i drugih opcija zaobilaženja kernela. S druge strane, XDP vam omogućuje implementaciju prilično složene logike, koja se, osim toga, lako ažurira bez pauze u obradi prometa. Verifikator ne stvara velike probleme, osobno ga ne bih odbio za dijelove koda korisničkog prostora.
U drugom dijelu, ako je tema zanimljiva, dopunit ćemo tablicu verificiranih klijenata i prekinuti veze, implementirati brojače i napisati userspace utility za upravljanje filterom.