Pišemo zaštitu od DDoS napada na XDP. Nuklearni dio
Tehnologija eXpress Data Path (XDP) omogućava da se izvrši nasumična obrada prometa na Linux sučeljima prije nego što paketi uđu u mrežni stog kernela. Primena XDP-a - zaštita od DDoS napada (CloudFlare), složeni filteri, prikupljanje statistike (Netflix). XDP programe izvršava eBPF virtuelna mašina, tako da imaju ograničenja i na njihov kod i na dostupne funkcije kernela u zavisnosti od tipa filtera.
Članak ima za cilj da nadoknadi nedostatke brojnih materijala o XDP-u. Prvo, oni daju gotov kod koji odmah zaobilazi karakteristike XDP-a: pripremljen za verifikaciju ili previše jednostavan da izazove probleme. Kada kasnije pokušate da napišete sopstveni kod od nule, nema razumevanja šta da radite sa tipičnim greškama. Drugo, ne pokriva načine lokalnog testiranja XDP-a bez VM-a i hardvera, uprkos činjenici da oni imaju svoje zamke. Tekst je namijenjen programerima koji poznaju mreže i Linux koji su zainteresirani za XDP i eBPF.
U ovom dijelu ćemo detaljno razumjeti kako se XDP filter sastavlja i kako ga testirati, zatim ćemo napisati jednostavnu verziju dobro poznatog mehanizma SYN kolačića na nivou obrade paketa. Dok ne formiramo "bijelu listu"
verifikovani klijenti, vodite brojače i upravljajte filterom - dovoljno dnevnika.
Pisaćemo u C - ovo nije moderno, ali praktično. Sav kod je dostupan na GitHubu na linku na kraju i podijeljen je na urezivanja prema koracima opisanim u članku.
Odricanje od odgovornosti. U toku ovog članka biće razvijeno mini rešenje za odbijanje DDoS napada, jer je to realan zadatak za XDP i moju oblast. Međutim, glavni cilj je razumjeti tehnologiju; ovo nije vodič za kreiranje gotove zaštite. Kod tutorijala nije optimizovan i izostavlja neke nijanse.
Kratak pregled XDP-a
Navešću samo ključne tačke kako ne bih duplirao dokumentaciju i postojeće članke.
Dakle, kod filtera se učitava u kernel. Filteru se prosljeđuju dolazni paketi. Kao rezultat toga, filter mora donijeti odluku: proslijediti paket kernelu (XDP_PASS), ispusti paket (XDP_DROP) ili ga pošalji nazad (XDP_TX). Filter može mijenjati paket, što posebno vrijedi za XDP_TX. Također možete srušiti program (XDP_ABORTED) i odbacite paket, ali ovo je analogno assert(0) - za otklanjanje grešaka.
Virtuelna mašina eBPF (prošireni Berkley Packet Filter) namerno je pojednostavljena tako da kernel može da proveri da se kod ne vrti u petlji i da ne ošteti memoriju drugih ljudi. Kumulativna ograničenja i provjere:
Loops (skokovi nazad) su zabranjeni.
Postoji stog za podatke, ali nema funkcija (sve C funkcije moraju biti umetnute).
Pristup memoriji izvan steka i bafera paketa je zabranjen.
Veličina koda je ograničena, ali u praksi to nije mnogo značajno.
Dozvoljene su samo posebne funkcije kernela (eBPF pomoćnici).
Dizajniranje i ugradnja filtera izgleda ovako:
Izvorni kod (npr kernel.c) kompajlira u objekt (kernel.o) za arhitekturu virtuelne mašine eBPF. Od oktobra 2019. Clang podržava kompajliranje u eBPF i obećava ga 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 kod se ne može izvršiti. Prije učitavanja u kernel, ove nule moraju biti zamijenjene ID-ovima određenih objekata kreiranih kroz pozive kernela (povezati kod). To možete učiniti s vanjskim uslužnim programima ili možete napisati program koji će povezati i učitati određeni filter.
Kernel provjerava program koji se učitava. Provjerava odsustvo ciklusa i neizlazak granica paketa i steka. Ako verifikator ne može dokazati da je kod ispravan, program se odbacuje - mora se moći ugoditi njemu.
Nakon uspješne verifikacije, kernel kompajlira objektni kod eBPF arhitekture u mašinski kod arhitekture sistema (baš na vrijeme).
Program je povezan sa interfejsom i počinje da obrađuje pakete.
Pošto XDP radi u kernelu, otklanjanje grešaka se zasniva na evidenciji praćenja i, u stvari, na paketima koje program filtrira ili generiše. Međutim, eBPF čuva preuzeti kod bezbednim za sistem, tako da možete eksperimentisati sa XDP-om direktno na vašem lokalnom Linux-u.
Priprema okoline
Montaža
Clang ne može direktno izdati objektni kod za eBPF arhitekturu, tako da se proces sastoji od dva koraka:
Prevedite C kod u LLVM bajt kod (clang -emit-llvm).
Pretvori bajt kod u eBPF objektni kod (llc -march=bpf -filetype=obj).
Prilikom pisanja filtera dobro će doći nekoliko datoteka s pomoćnim funkcijama i makroima iz testova kernela. Važno je da odgovaraju verziji kernela (KVER). Preuzmite ih na helpers/:
KDIR sadrži putanju do zaglavlja kernela, ARCH — arhitektura sistema. Putevi i alati mogu se neznatno 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čiti direktorij s pomoćnim zaglavljima i nekoliko direktorija sa zaglavljima kernela. Simbol __KERNEL__ znači da su UAPI (userspace API) zaglavlja definirana za kod kernela, budući da se filter izvršava u kernelu.
Zaštita steka se može onemogućiti (-fno-stack-protector) jer eBPF verifikator koda ionako provjerava da nije izvan granica steka. Trebali biste odmah omogućiti optimizacije, jer je veličina eBPF bajtkoda ograničena.
Počnimo s filterom koji propušta sve pakete i ne radi ništa:
tim make prikuplja xdp_filter.o. Gdje ga sada možete testirati?
Test stalak
Stalak treba da sadrži dva interfejsa: na kojem će biti filter i sa kojih će se slati paketi. To moraju biti potpuni Linux uređaji sa vlastitim IP adresama kako bi provjerili kako obične aplikacije rade s našim filterom.
Uređaji kao što je veth (virtualni Ethernet) su prikladni za nas: oni su par virtuelnih mrežnih interfejsa koji su „povezani” direktno jedan na drugi. Možete ih kreirati ovako (u ovom odjeljku sve naredbe ip izvedeno od root):
ip link add xdp-remote type veth peer name xdp-local
to je xdp-remote и xdp-local — nazivi uređaja. On xdp-local (192.0.2.1/24) filter će biti pričvršćen, sa xdp-remote (192.0.2.2/24) dolazni saobraćaj će biti poslat. Međutim, postoji problem: sučelja su na istom stroju, a Linux neće slati promet jednom od njih preko drugog. Ovo možete riješiti škakljivim pravilima iptables, ali će morati promijeniti pakete, što je nezgodno prilikom otklanjanja grešaka. Bolje je koristiti mrežne prostore imena (mrežni prostori imena, dalje mreže).
Mrežni imenski prostor sadrži skup sučelja, tablica rutiranja i NetFilter pravila koja su izolirana od sličnih objekata u drugim mrežama. Svaki proces se pokreće u nekom imenskom prostoru i samo objekti ove mreže su mu dostupni. Podrazumevano, sistem ima jedan mrežni prostor imena za sve objekte, tako da možete raditi na Linuxu i ne znati za netns.
Kreirajmo novi prostor imena xdp-test i preseliti se tamo xdp-remote.
ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test
Zatim se proces pokreće xdp-test, neće "vidjeti" xdp-local (podrazumevano će ostati u netns) i prilikom slanja paketa na 192.0.2.1 će ga proći kroz xdp-remote, jer je to jedini interfejs na 192.0.2.0/24 koji je dostupan ovom procesu. Ovo radi i obrnuto.
Kada se krećete između mreža, interfejs se spušta i gubi adresu. Da biste podesili sučelje u netns, morate pokrenuti ip ... u ovom imenskom prostoru naredbe 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 podešavanja xdp-local u zadanom imenskom prostoru:
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 iz xdp-test, isporučuju se na ovaj interfejs:
ip netns exec xdp-test ping 192.0.2.1
Zgodno je pokrenuti školjku xdp-test. Repozitorijum ima skriptu koja automatizuje rad sa postoljem, na primer, možete podesiti štand komandom sudo ./stand up i uklonite ga sudo ./stand down.
praćenje
Filter je povezan sa uređajem poput ovog:
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
Ključ -force potrebno za povezivanje novog programa ako je drugi već povezan. "Nijedna vijest nije dobra vijest" se ne odnosi na ovu naredbu, rezultat je ionako obiman. ukazati verbose opciono, ali uz njega se pojavljuje izvještaj o radu verifikatora koda sa listingom asemblera:
Verifier analysis:
0: (b7) r0 = 2
1: (95) exit
Prekinite vezu programa sa interfejsom:
ip link set dev xdp-local xdp off
U skripti su to naredbe sudo ./stand attach и sudo ./stand detach.
Vezivanjem filtera možete se uvjeriti u to ping nastavlja da radi, ali da li program funkcioniše? Hajde da dodamo logotipe. Funkcija bpf_trace_printk() slicno printf(), ali podržava samo do tri argumenta osim uzorka i ograničenu listu specifikacija. Makro bpf_printk() pojednostavljuje poziv.
Iz tog razloga, izlaz za otklanjanje grešaka uvelike naduvava rezultujući kod.
Slanje XDP paketa
Promijenimo filter: neka šalje sve dolazne pakete nazad. Ovo je netačno sa mrežne tačke gledišta, jer bi bilo potrebno promijeniti adrese u zaglavljima, ali sada je važan princip rada.
Pokreni tcpdump na xdp-remote. Trebao bi prikazati identičan odlazni i dolazni ICMP eho zahtjev i prestati prikazivati ICMP eho odgovor. Ali to se ne pokazuje. Ispostavilo se da za posao XDP_TX u programu za xdp-localje neophodnoza uparivanje interfejsa xdp-remote program je također dodijeljen, čak i ako je bio prazan, i on je podignut.
Kako sam znao?
Praćenje putanje paketa u kernelu mehanizam perf događaja omogućava, inače, korištenje iste virtuelne mašine, odnosno eBPF se koristi za rastavljanje sa eBPF-om.
Od zla morate napraviti dobro, jer se od njega ne može ništa drugo napraviti.
Ako se umjesto toga prikazuje samo ARP, trebate ukloniti filtere (ovo čini sudo ./stand detach), neka ping, zatim instalirajte filtere i pokušajte ponovo. Problem je što filter XDP_TX utiče i na ARP, a ako stek
imenski prostori xdp-test uspeo da "zaboravi" MAC adresu 192.0.2.1, neće moći da reši ovu IP adresu.
Izjava o problemu
Pređimo na navedeni zadatak: napisati mehanizam SYN kolačića na XDP.
Do sada, SYN flood ostaje popularan DDoS napad, čija je suština sljedeća. Kada se veza uspostavi (TCP rukovanje), server prima SYN, dodeljuje resurse za buduću vezu, odgovara SYNACK paketom i čeka ACK. Napadač jednostavno šalje SYN pakete sa lažnih adresa u količini od hiljada u sekundi sa svakog hosta više hiljada botneta. Server je primoran da dodijeli resurse odmah po dolasku paketa, ali ga otpušta nakon dužeg vremena čekanja, kao rezultat toga, memorija ili ograničenja su iscrpljena, nove veze se ne prihvaćaju, usluga je nedostupna.
Ako ne dodijelite resurse na osnovu SYN paketa, već samo odgovorite sa SYNACK paketom, kako onda server može razumjeti da se ACK paket koji je stigao kasnije odnosi na SYN paket koji nije sačuvan? Na kraju krajeva, napadač također može generirati lažne ACK-ove. Smisao SYN kolačića je da ga kodira seqnum parametri veze kao hash adresa, portova i promjenjive soli. Ako je ACK uspio stići prije promjene soli, možete ponovo izračunati hash i uporediti sa acknum. lažno acknum napadač ne može, jer sol uključuje tajnu, i neće imati vremena da je razvrstane zbog ograničenog kanala.
SYN kolačići su implementirani u Linux kernel već duže vrijeme i čak se mogu automatski omogućiti ako SYN stignu prebrzo i masovno.
Edukativni program o TCP rukovanju
TCP omogućava 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 zastavice i 32-bitne sekvence:
Kombinacija zastavica definira ulogu određenog paketa. Oznaka SYN znači da je ovo prvi paket pošiljaoca na vezi. Oznaka ACK znači da je pošiljalac primio sve podatke o vezi do jednog bajta. acknum. Paket može imati nekoliko zastavica i nazvan je po njihovoj kombinaciji, na primjer, SYNACK paket.
Broj sekvence (seqnum) specificira pomak u toku podataka za prvi bajt koji se šalje u ovom paketu. Na primjer, ako je u prvom paketu sa X bajtova podataka ovaj broj bio N, u sljedećem paketu sa novim podacima bit će N+X. Na početku veze, svaka strana bira ovaj broj nasumično.
Broj potvrde (acknum) - isti pomak kao seqnum, ali ne određuje broj poslanog bajta, već broj prvog bajta od primaoca, koji pošiljalac nije vidio.
Na početku veze, strane se moraju dogovoriti seqnum и acknum. Klijent šalje SYN paket sa svojim seqnum = X. Server odgovara SYNACK paketom, gdje upisuje svoj seqnum = Y i izlaže acknum = X + 1. Klijent odgovara na SYNACK sa ACK paketom, gdje seqnum = X + 1, acknum = Y + 1. Nakon toga počinje stvarni prijenos podataka.
Ako sagovornik ne potvrdi prijem paketa, TCP ga ponovo šalje po isteku vremena.
Zašto se SYN kolačići ne koriste uvijek?
Prvo, ako se izgubi SYNACK ili ACK, morat ćete pričekati ponovno slanje - uspostavljanje veze se usporava. Drugo, u SYN paketu - i samo u njemu! - prenosi se niz opcija koje utiču na dalji rad veze. Ne pamteći dolazne SYN pakete, server tako ignoriše ove opcije, u sledećim paketima klijent ih više neće slati. TCP može raditi u ovom slučaju, ali barem u početnoj fazi, kvaliteta veze će se smanjiti.
Što se tiče paketa, XDP program bi trebao učiniti sljedeće:
odgovoriti na SYN sa SYNACK s kolačićem;
odgovorite ACK sa RST (prekinite vezu);
ispusti druge pakete.
Pseudokod algoritma zajedno sa raščlanjivanjem paketa:
Если это не Ethernet,
пропустить пакет.
Если это не IPv4,
пропустить пакет.
Если адрес в таблице проверенных, (*)
уменьшить счетчик оставшихся проверок,
пропустить пакет.
Если это не TCP,
сбросить пакет. (**)
Если это SYN,
ответить SYN-ACK с cookie.
Если это ACK,
если в acknum лежит не cookie,
сбросить пакет.
Занести в таблицу адрес с N оставшихся проверок. (*)
Ответить RST. (**)
В остальных случаях сбросить пакет.
Jedan (*) Označene su tačke na kojima treba da upravljate stanjem sistema - u prvoj fazi možete bez njih jednostavnim implementiranjem TCP rukovanja sa generisanjem SYN kolačića kao sekvence.
Na mjestu (**), dok nemamo tabelu, preskočićemo paket.
Implementacija TCP rukovanja
Parsiranje paketa i provjera koda
Potrebne su nam strukture mrežnog zaglavlja: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) i TCP (uapi/linux/tcp.h). Posljednji nisam mogao spojiti zbog grešaka u vezi s njim atomic64_t, morao sam kopirati potrebne definicije u kod.
Sve funkcije koje se razlikuju u C-u zbog čitljivosti moraju biti umetnute na mjesto poziva, pošto eBPF verifikator u kernelu zabranjuje skokove unatrag, odnosno, u stvari, petlje i pozive funkcija.
Makro LOG() onemogućuje štampanje u verziji izdanja.
Program je niz funkcija. Svaki prima paket u kojem je istaknuto zaglavlje odgovarajućeg nivoa, na primjer, process_ether() čeka da se popuni ether. Na osnovu rezultata analize polja, funkcija može proslijediti paket na viši nivo. Rezultat funkcije je XDP akcija. Za sada, SYN i ACK rukovaoci propuštaju sve pakete.
Key string invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): postoje putevi izvršenja kada je trinaesti bajt od početka bafera izvan paketa. Iz listinga je teško zaključiti o kojoj liniji je riječ, ali postoji broj instrukcije (12) i disassembler koji prikazuje redove izvornog koda:
što jasno daje do znanja da je problem ether. Uvek bi bilo tako.
Odgovorite SYN
Cilj u ovoj fazi je generirati ispravan SYNACK paket sa fiksnim seqnum, koji će u budućnosti biti zamijenjen SYN kolačićem. Sve promjene se dešavaju u process_tcp_syn() i okolnim područjima.
Provjera paketa
Začudo, evo najupečatljivijeg reda, odnosno komentara na njega:
Prilikom pisanja prve verzije koda korišteno je jezgro 5.1, za čiji verifikator je postojala razlika između data_end и (const void*)ctx->data_end. U vrijeme pisanja ovog teksta, kernel 5.3.1 nije imao ovaj problem. Možda je kompajler pristupao lokalnoj varijabli drugačije od polja. Moral - na velikom gniježđenju, pojednostavljivanje koda može pomoći.
Dalje rutinske provjere dužina za slavu verifikatora; O MAX_CSUM_BYTES ispod.
IPv4 i TCP kontrolni sumi zahtijevaju dodavanje svih 16-bitnih riječi u zaglavlja, a veličina zaglavlja je upisana u njima, odnosno nepoznata u vrijeme kompajliranja. Ovo je problem jer verifikator neće proći kroz normalnu petlju do granice varijable. Ali veličina zaglavlja je ograničena: do 64 bajta svako. Možete napraviti petlju sa fiksnim brojem iteracija, koje se mogu završiti ranije.
Napominjem da postoji RFC 1624 o tome kako djelimično ponovo izračunati kontrolnu sumu ako se promijene samo fiksne riječi paketa. Međutim, metoda nije univerzalna, a implementaciju bi bilo teže održavati.
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 provjereno pozivnim kodom, 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 preračunavanje kontrolnih suma i slanje paketa nazad:
funkcija carry() pravi kontrolnu sumu od 32-bitne sume 16-bitnih riječi, prema RFC 791.
TCP provjera rukovanja
Filter ispravno uspostavlja vezu sa netcat, preskačući konačni ACK, na koji je Linux odgovorio sa RST paketom, budući da mrežni stog nije primio SYN - bio je konvertovan u SYNACK i poslat nazad - i sa stanovišta OS-a, stigao je paket koji nije bio povezan sa 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 monitorom tcpdump na xdp-remote jer npr. hping3 ne odgovara na netačne kontrolne sume.
SYN kolačić
Sa stanovišta XDP-a, sama provjera je trivijalna. Algoritam proračuna je primitivan i vjerovatno ranjiv na sofisticiranog napadača. Linux kernel, na primjer, koristi kriptografski SipHash, ali njegova implementacija za XDP je očigledno izvan okvira ovog članka.
Pojavio se za nove TODO-e vezane za vanjsku interakciju:
XDP program ne može pohraniti cookie_seed (tajni dio soli) u globalnoj varijabli, potrebno vam je skladište kernela čija će vrijednost biti periodično ažurirana iz pouzdanog generatora.
Ako se SYN kolačić u ACK paketu podudara, ne morate ispisivati poruku, već zapamtite IP provjerenog klijenta kako biste dalje preskočili pakete s njega.
Ponekad se eBPF općenito i XDP posebno predstavljaju više kao napredni administratorski alat nego kao razvojna platforma. Zaista, XDP je alat za ometanje obrade paketa od strane kernela, a ne alternativa stogu kernela, kao što je DPDK i druge opcije zaobilaženja kernela. S druge strane, XDP vam omogućava implementaciju prilično složene logike, koju je, osim toga, lako ažurirati bez prekida u obradi prometa. Verifikator ne stvara velike probleme; lično, ne bih to odbio za dijelove koda korisničkog prostora.
U drugom dijelu, ako je tema interesantna, upotpunit ćemo tabelu provjerenih klijenata i prekinuti veze, implementirati brojače i napisati uslužni program korisničkog prostora za upravljanje filterom.