XDP-n DDoS erasoen aurkako babesa idazten dugu. Parte nuklearra

eXpress Data Path (XDP) teknologiak Linux interfazeetan trafikoaren prozesamendu arbitrarioa ahalbidetzen du, paketeak nukleoaren sareko pilara sartu aurretik. XDP aplikazioa - DDoS erasoen aurkako babesa (CloudFlare), iragazki konplexuak, estatistika bilketa (Netflix). XDP programak eBPF makina birtualak exekutatzen ditu, eta, beraz, murrizketak dituzte beren kodean zein erabilgarri dauden nukleoaren funtzioetan, iragazki motaren arabera.

Artikuluak XDP-ko material ugariren gabeziak konpondu nahi ditu. Lehenik eta behin, XDPren ezaugarriak berehala gainditzen dituen prest egindako kodea eskaintzen dute: egiaztatzeko prestatua edo sinpleegia arazoak sortzeko. Geroago zure kodea hutsetik idazten saiatzen zarenean, ez da ulertzen zer egin behar den errore tipikoekin. Bigarrenik, ez ditu XDP lokalean probatzeko moduak VM eta hardwarerik gabe estaltzen, beren hutsuneak izan arren. Testua XDP eta eBPFn interesa duten sareak eta Linux ezagutzen dituzten programatzaileentzat da.

Zati honetan, zehatz-mehatz ulertuko dugu XDP iragazkia nola muntatzen den eta nola probatu, gero paketeen prozesatzeko mailan SYN cookie-en mekanismo ezagunaren bertsio sinple bat idatziko dugu. "zerrenda zuria" osatu arte
egiaztatutako bezeroak, gorde kontagailuak eta kudeatu iragazkia - nahikoa erregistro.

C-n idatziko dugu - hau ez da modan, praktikoa baizik. Kode guztia GitHub-en dago eskuragarri amaierako estekan eta artikuluan deskribatutako urratsen arabera konprometituetan banatzen da.

Lege-oharra. Artikuluan zehar, DDoS erasoak uxatzeko mini-soluzio bat garatuko da, hau XDP eta nire eremurako zeregin errealista delako. Hala ere, helburu nagusia teknologia ulertzea da, hau ez da prest egindako babesa sortzeko gida. Tutorial kodea ez dago optimizatuta eta ñabardura batzuk baztertzen ditu.

XDPren ikuspegi laburra

Gakoak bakarrik adieraziko ditut, dokumentazioa eta lehendik dauden artikuluak ez bikoizteko.

Beraz, iragazki-kodea nukleoan kargatzen da. Iragazkia sarrerako paketeei pasatzen zaie. Ondorioz, iragazkiak erabaki bat hartu behar du: paketea nukleora pasatzea (XDP_PASS), paketea bota (XDP_DROP) edo bidali itzuli (XDP_TX). Iragazkiak paketea alda dezake, hau bereziki egia da XDP_TX. Programa ere huts egin dezakezu (XDP_ABORTED) eta bota paketea, baina hau analogoa da assert(0) - arazketarako.

eBPF (Extended Berkley Packet Filter) makina birtuala nahita erraz egiten da, kernelak kodea ez duela begizta egiten eta ez duela kaltetzen besteen memoria egiaztatzeko. Murrizketa eta egiaztapen metatuak:

  • Debekatuta dago begiztak (atzera jauziak).
  • Datuetarako pila bat dago, baina ez dago funtziorik (C funtzio guztiak lerrokatuta egon behar dira).
  • Debekatuta dago pilatik eta paketeen bufferetik kanpoko memoriarako sarbideak.
  • Kodearen tamaina mugatua da, baina praktikan hori ez da oso esanguratsua.
  • Nukleoaren funtzio bereziak (eBPF laguntzaileak) bakarrik onartzen dira.

Iragazki bat garatzea eta instalatzea honelakoa da:

  1. iturburu kodea (adib. kernel.c) objektura konpilatzen (kernel.o) eBPF makina birtualeko arkitekturarako. 2019ko urritik aurrera, eBPF-ra biltzea Clang-ek onartzen du eta GCC 10.1-en agintzen du.
  2. Objektu kode honetan kernel egituretarako deiak badaude (adibidez, tauletara eta kontagailuetara), haien IDen ordez zeroak daude, hau da, ezin da kode hori exekutatu. Kernelean kargatu aurretik, zero hauek nukleo-deien bidez sortutako objektu espezifikoen IDekin ordezkatu behar dira (lotu kodea). Kanpoko utilitateekin egin dezakezu, edo iragazki zehatz bat lotu eta kargatuko duen programa bat idatzi dezakezu.
  3. Nukleoak kargatzen ari den programa egiaztatzen du. Ziklorik ez dagoen eta paketeen eta pila-mugetatik irteterik ez dagoen egiaztatzen du. Egiaztatzaileak ezin badu frogatu kodea zuzena denik, programa baztertu egiten da - bat pozik egon behar da.
  4. Egiaztatu ondoren, nukleoak eBPF arkitekturako objektu-kodea sistema-arkitekturako makina-kodean konpilatzen du (just-in-time).
  5. Programa interfazeari atxikita dago eta paketeak prozesatzen hasten da.

XDP nukleoan exekutatzen denez, arazketa arrastoen erregistroen bidez egiten da eta, hain zuzen ere, programak iragazten edo sortzen dituen paketeen bidez. Hala ere, eBPF-k deskargatutako kodea seguru mantentzen du sistemarako, beraz, XDPrekin esperimentatu dezakezu zure tokiko Linux-en.

Ingurumena prestatzea

muntaia

Clang-ek ezin du zuzenean igorri objektu-koderik eBPF arkitekturarako, beraz, prozesuak bi urrats ditu:

  1. Konpilatu C kodea LLVM bytecodera (clang -emit-llvm).
  2. Bihurtu bytecode eBPF objektu-kodera (llc -march=bpf -filetype=obj).

Iragazkia idaztean, funtzio osagarriak eta makroak dituzten fitxategi pare bat ondo etorriko dira nukleoaren probetatik. Garrantzitsua da nukleoaren bertsioarekin bat etortzea (KVER). Deskargatu itzazu 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 Arch Linux-erako (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 nukleoko goiburuetarako bidea dauka, ARCH - Sistemaren arkitektura. Bideak eta tresnak apur bat alda daitezke banaketaren artean.

Desberdintasunen adibidea 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 sartu goiburu laguntzaileak dituen direktorio bat eta nukleoaren goiburuak dituzten hainbat direktorio. Ikurra __KERNEL__ esan nahi du UAPI (erabiltzaile-espazioko API) goiburuak definituta daudela nukleoaren kodearentzat, iragazkia nukleoan exekutatzen baita.

Pila babesa desgaitu daiteke (-fno-stack-protector) eBPF kode egiaztatzaileak pila-mugetatik kanpo ez dauden egiaztatzen duelako. Optimizazioak berehala gaitu behar dituzu, eBPF bytecodearen tamaina mugatua baita.

Has gaitezen pakete guztiak pasatzen dituen eta ezer egiten ez duen iragazki batekin:

#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";

Team make biltzen du xdp_filter.o. Non probatu dezakezu orain?

proba-bankua

Standak bi interfaze izan behar ditu: zeinetan iragazki bat egongo den eta zeinetatik paketeak bidaliko diren. Hauek Linux gailu osoak izan behar dute beren IP-a dutenak, ohiko aplikazioek gure iragazkiarekin nola funtzionatzen duten egiaztatzeko.

Veth (Ethernet birtuala) bezalako gailuak egokiak dira guretzat: elkarren artean zuzenean “konektatuta” dauden sare birtualeko interfaze pare bat dira. Horrela sor ditzakezu (atal honetan, komando guztiak ip batetik egina root):

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

Hemen xdp-remote и xdp-local — gailuen izenak. On xdp-local (192.0.2.1/24) iragazki bat erantsiko da, rekin xdp-remote (192.0.2.2/24) sarrerako trafikoa bidaliko da. Hala ere, arazo bat dago: interfazeak makina berean daude, eta Linuxek ez dio trafikoa bidaliko horietako bati bestearen bidez. Arau zailak erabiliz konpondu dezakezu iptables, baina paketeak aldatu beharko dituzte, eta hori deserosoa da arazketan. Hobe da sareko izen-eremuak erabiltzea (sareko izen-eremuak, sare gehiago).

Sarearen izen-eremuak beste sare batzuetan antzeko objektuetatik isolatuta dauden interfaze, bideratze-taulak eta NetFilter arau multzo bat dauka. Prozesu bakoitza izen-espazio batzuetan exekutatzen da, eta netn honen objektuak soilik daude eskuragarri. Lehenespenez, sistemak sareko izen-espazio bakarra du objektu guztientzat, beraz Linux-en lan egin dezakezu eta netns-en berri ez izan.

Sortu dezagun izen-eremu berri bat xdp-test eta hara mugitu xdp-remote.

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

Ondoren, prozesua martxan xdp-test, ez du "ikusiko" xdp-local (netns-en geratuko da lehenespenez) eta pakete bat bidaltzean 192.0.2.1-era pasatuko da xdp-remote, hori baita prozesu honetarako eskuragarri dagoen 192.0.2.0/24 interfaze bakarra. Horrek ere alderantziz funtzionatzen du.

Netns artean mugitzean, interfazea behera egiten du eta helbidea galtzen du. Netns-en interfaze bat konfiguratzeko, exekutatu behar duzu ip ... komando izen-espazio honetan 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

Ikus dezakezunez, hau ez da ezarpenaren desberdina xdp-local izen-espazio lehenetsian:

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

Korrika eginez gero tcpdump -tnevi xdp-local, hemendik bidalitako paketeak ikus ditzakezu xdp-test, interfaze honetara entregatzen dira:

ip netns exec xdp-test   ping 192.0.2.1

Erosoa da shell bat exekutatzeko xdp-test. Biltegiak standarekin lana automatizatzen duen script bat du, adibidez, standa konfigura dezakezu komandoarekin sudo ./stand up eta kendu sudo ./stand down.

trazadura

Iragazkia gailuari honela lotuta dago:

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

gakoa -force programa berri bat lotu behar da dagoeneko beste bat lotuta badago. "Albisterik ez da albiste ona" ez da komando honi buruz, irteera handia da hala ere. adierazi verbose aukerakoa, baina horrekin batera kode-egiaztapenaren lanari buruzko txostena agertzen da muntatzaileen zerrendarekin:

Verifier analysis:

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

Kendu programa interfazetik:

ip link set dev xdp-local xdp off

Gidoian, hauek dira komandoak sudo ./stand attach и sudo ./stand detach.

Iragazkia lotuz, hori ziurtatu dezakezu ping lanean jarraitzen du, baina programak funtzionatzen du? Gehi ditzagun logotipoak. Funtzioa bpf_trace_printk() antzekoak printf(), baina ereduaz gain hiru argumentu baino ez ditu onartzen, eta zehatzaileen zerrenda mugatu bat. Makroa bpf_printk() deia errazten du.

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

Irteera nukleoaren arrastoaren kanalera doa, eta hori gaitu behar da:

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

Ikusi mezuen fluxua:

cat /sys/kernel/debug/tracing/trace_pipe

Bi talde hauek deia egiten dute sudo ./stand log.

Ping-ek honelako mezuak sortu beharko lituzke bertan:

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

Egiaztatzailearen irteera ondo begiratuz gero, kalkulu bitxiak nabarituko dituzu:

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
<...>

Kontua da eBPF programek ez dutela datu-atal bat, beraz, formatu-katea kodetzeko modu bakarra VM komandoen berehalako argumentuak dira:

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

Hori dela eta, arazketa-irteerak asko puzten du ondoriozko kodea.

XDP paketeak bidaltzen

Alda dezagun iragazkia: utzi sarrerako pakete guztiak itzultzeko. Hau okerra da sarearen ikuspuntutik, goiburuetako helbideak aldatzea beharrezkoa litzatekeelako, baina orain printzipioz lana garrantzitsua da.

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

korrika tcpdump on xdp-remote. Irteerako eta sarrerako ICMP Echo Request berdina erakutsi beharko luke eta ICMP Echo Reply erakusteari utzi behar dio. Baina ez da erakusten. Funtzionatzen du XDP_TX programan xdp-local mustinterfazea parekatzeko xdp-remote programa bat ere esleitu zen, hutsik bazegoen ere, eta planteatu zen.

Nola jakin nuen?

Nukleoan pakete baten bidea trazatzea perf events mekanismoak, bide batez, makina birtual bera erabiltzea ahalbidetzen du, hau da, eBPF erabiltzen da eBPFrekin desmuntatzeko.

Gaizkiari ongi egin behar diozu, ez baitago bertzerik egin.

$ 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])
                                     <...>

Zer da 6 kodea?

$ errno 6
ENXIO 6 No such device or address

Funtzioa veth_xdp_flush_bq() akats-kodea lortzen du veth_xdp_xmit(), non bilatu ENXIO eta aurkitu iruzkin bat.

Berrezarri gutxieneko iragazkia (XDP_PASS) fitxategian xdp_dummy.c, gehitu Makefile-ra, lotu xdp-remote:

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

Orain tcpdump espero dena erakusten du:

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

Horren ordez ARP bakarrik erakusten bada, iragazkiak kendu behar dituzu (horrek egiten du sudo ./stand detach), utzi ping, ondoren instalatu iragazkiak eta saiatu berriro. Arazoa da iragazkia XDP_TX ARPri ere eragiten dio, eta pila bada
izen-espazioak xdp-test 192.0.2.1 MAC helbidea "ahaztea" lortu zuen, ezin izango du IP hau konpondu.

Arazoaren formulazioa

Goazen adierazitako zereginera: XDP-n SYN cookie mekanismo bat idaztea.

Orain arte, SYN uholdeak DDoS eraso ezagun bat izaten jarraitzen du, eta honen funtsa honakoa da. Konexio bat ezartzen denean (TCP handshake), zerbitzariak SYN bat jasotzen du, etorkizuneko konexio baterako baliabideak esleitzen ditu, SYNACK pakete batekin erantzuten du eta ACK baten zain geratzen da. Erasotzaileak SYN paketeak bidaltzen ditu helbide faltsuetatik milaka segundoko ostalari bakoitzeko milaka botnet batean. Zerbitzaria paketea iristean berehala baliabideak esleitzera behartuta dago, baina denbora luze baten ondoren askatzen du, ondorioz, memoria edo mugak agortzen dira, konexio berriak ez dira onartzen, zerbitzua ez dago erabilgarri.

SYN paketeari baliabideak esleitzen ez badituzu, baina SYNACK pakete batekin soilik erantzuten baduzu, nola uler dezake zerbitzariak geroago etorritako ACK paketea gorde ez den SYN paketearena dela? Azken finean, erasotzaile batek ACK faltsuak ere sor ditzake. SYN cookiearen funtsa kodetzea da seqnum konexio-parametroak helbideen, ataken eta gatz aldakorren hash gisa. ACK-ak gatza aldatu baino lehen iristea lortu badu, hash-a berriro kalkula dezakezu eta konparatu acknum. faltsu acknum erasotzaileak ezin du, gatzak sekretua barne hartzen baitu, eta ez du hura ordenatzeko astirik izango kanal mugatua dela eta.

SYN cookieak denbora luzez ezarri dira Linux nukleoan eta automatikoki ere gaitu daitezke SYNak azkarregi eta masiboki iristen badira.

TCP esku-emateari buruzko hezkuntza-programa

TCP-k datuen transferentzia ematen du byte-korronte gisa, adibidez, HTTP eskaerak TCP bidez transmititzen dira. Korrontea zatiz pieza igortzen da paketeetan. TCP pakete guztiek bandera logikoak eta 32 biteko sekuentzia-zenbakiak dituzte:

  • Banderen konbinazioak pakete jakin baten eginkizuna definitzen du. SYN banderak esan nahi du igorlearen lehenengo paketea dela konexioan. ACK banderak esan nahi du igorleak konexio-datu guztiak jaso dituela byte bateraino. acknum. Pakete batek hainbat bandera izan ditzake eta haien konbinazioaren arabera du izena, adibidez, SYNACK pakete bat.

  • Sekuentzia-zenbakiak (seqnum) pakete honetan bidaltzen den lehen bytearen datu-korrontearen desplazamendua zehazten du. Esaterako, X byte-ko datuen lehen paketean zenbaki hori N bazen, datu berriak dituen hurrengo paketean N+X izango da. Deiaren hasieran, alde bakoitzak zenbaki hau ausaz aukeratzen du.

  • Onarpen-zenbakia (acknum) - seqnum-en desplazamendu bera, baina ez du igorritako bytearen zenbakia zehazten, igorleak ikusi ez zuen hartzailearen lehen bytearen zenbakia baizik.

Konexioaren hasieran, alderdiek ados jarri behar dute seqnum и acknum. Bezeroak SYN pakete bat bidaltzen du bere seqnum = X. Zerbitzariak SYNACK pakete batekin erantzuten du, non berea idazten duen seqnum = Y eta agerian uzten du acknum = X + 1. Bezeroak SYNACK-i ACK pakete batekin erantzuten dio, non seqnum = X + 1, acknum = Y + 1. Horren ondoren, benetako datuen transferentzia hasten da.

Solaskideak ez badu paketea jaso duela aitortzen, TCPk berriro bidaltzen du denbora-mugan.

Zergatik ez dira beti erabiltzen SYN cookieak?

Lehenik eta behin, SYNACK edo ACK bat galtzen bada, berriro bidaltzeko itxaron beharko duzu - konexioaren ezarpena moteldu egiten da. Bigarrenik, SYN paketean - eta bertan bakarrik! - konexioaren funtzionamenduari eragiten dioten hainbat aukera transmititzen dira. Sarrerako SYN paketeez gogoratzen ez denez, zerbitzariak aukera hauek baztertzen ditu, hurrengo paketeetan bezeroak ez ditu gehiago bidaliko. TCP kasu honetan funtziona dezake, baina hasierako fasean behintzat, konexioaren kalitatea gutxituko da.

Paketeei dagokienez, XDP programa batek honako hau egin beharko luke:

  • erantzun SYN-ri SYNACK cookiearekin;
  • erantzun ACK RST-rekin (konexioa hautsi);
  • bota beste pakete batzuk.

Algoritmoaren pseudokodea paketeen analisiarekin batera:

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

Bat (*) sistemaren egoera kudeatu behar dituzun puntuak markatuta daude; lehen fasean, horiek gabe egin dezakezu TCP esku-ematea besterik gabe inplementatu SYN cookie bat sekuentzia gisa sortuz.

Gunean (**), mahairik ez dugun bitartean, paketea saltatuko dugu.

TCP handshake ezarpena

Paketeen analisia eta kodea egiaztatzea

Sarearen goiburuko egiturak behar ditugu: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) eta TCP (uapi/linux/tcp.h). Azkena ezin izan dudan konektatu lotutako akatsengatik atomic64_t, beharrezko definizioak kodean kopiatu behar izan ditut.

Irakurgarritasunagatik C-n bereizten diren funtzio guztiak dei-gunean sartu behar dira, nukleoko eBPF egiaztatzaileak atzera-jauziak debekatzen baititu, hau da, begiztak eta funtzio-deiak.

#define INTERNAL static __attribute__((always_inline))

Makroa LOG() inprimatzea desgaitzen du bertsioaren eraikuntza batean.

Programa funtzioen kanalizazioa da. Bakoitzak pakete bat jasotzen du eta bertan dagokion mailaren goiburua nabarmentzen da, adibidez, process_ether() betetzeko zain ether. Eremu-analisiaren emaitzetan oinarrituta, funtzioak paketea maila altuago batera transferi dezake. Funtzioaren emaitza XDP ekintza bat da. SYN eta ACK kudeatzaileek pakete guztiak pasatzen uzten dituzten bitartean.

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);
}

A eta B markatutako egiaztapenei erreparatzen diet. A iruzkintzen baduzu, programa eraikiko da, baina egiaztapen-errore bat egongo da kargatzean:

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!

Gako-katea invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): exekuzio bideak daude bufferaren hasieratik hamahirugarren bytea paketetik kanpo dagoenean. Zaila da zerrendatik zein lerrotaz ari garen esatea, baina badaude instrukzio-zenbaki bat (12) eta iturburu-kodearen lerroak erakusten dituen desmuntatzaile bat:

llvm-objdump -S xdp_filter.o | less

Kasu honetan, lerroa seinalatzen du

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

horrek argi uzten du arazoa dela ether. Beti izango litzateke horrela.

Erantzun SYN

Etapa honetan helburua SYNACK pakete zuzen bat sortzea da, finko batekin seqnum, etorkizunean SYN cookieak ordezkatuko duena. Aldaketa guztiak urtean gertatzen dira process_tcp_syn() eta inguruak.

Paketea egiaztatzea

Bitxia bada ere, hona hemen lerrorik aipagarriena, edo hobeto esanda, honen iruzkin bat:

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

Kodearen lehen bertsioa idaztean, 5.1 nukleoa erabili zen, zeinaren egiaztatzailerako aldea zegoen artean data_end и (const void*)ctx->data_end. Idazteko unean, 5.3.1 nukleoak ez zuen arazo hau. Beharbada, konpilatzaileak tokiko aldagai batera sartzen ari zen eremu batean ez bezala. Morala - habia handi batean, kodea sinplifikatzeak lagun dezake.

Luzeen ohiko egiaztapen gehiago egiaztatzailearen aintzarako; O MAX_CSUM_BYTES azpian.

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 */
}

Paketea zabaldu

Betetzen dugu seqnum и acknum, ezarri ACK (SYN dagoeneko ezarrita):

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

Trukatu TCP portuak, IP eta MAC helbideak. Liburutegi estandarra ez dago eskuragarri XDP programatik, beraz memcpy() — Clang intrinsik-a ezkutatzen duen makroa.

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);

Checksumaren birkalkulua

IPv4 eta TCP checksumek goiburuetan 16 biteko hitz guztiak gehitzea eskatzen dute, eta goiburuen tamaina idatzita dago horietan, hau da, konpilazioaren unean ezezaguna da. Arazo bat da egiaztatzaileak ez duelako begizta normala saltatuko muga-aldagaira arte. Baina goiburuen tamaina mugatua da: gehienez 64 byte bakoitzak. Begizta bat egin dezakezu iterazio kopuru finko batekin, goiz amaitu daitekeena.

badagoela ohartzen naiz RFC 1624 checksum-a partzialki nola birkalkulatu paketeen hitz finkoak bakarrik aldatzen badira. Hala ere, metodoa ez da unibertsala, eta inplementazioa mantentzea zailagoa izango litzateke.

Checksum kalkulu funtzioa:

#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;
}

Nahiz eta size dei-kodeak egiaztatuta, bigarren irteera-baldintza beharrezkoa da egiaztatzaileak begiztaren amaiera froga dezan.

32 biteko hitzetarako, bertsio sinpleago bat ezartzen da:

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

Egia esan, checksumak berriro kalkulatzen eta paketea itzuli egiten da:

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;

Funtzioa carry() checksum bat egiten du 32 biteko hitzen 16 biteko batura batetik, RFC 791-ren arabera.

TCP esku-harremana egiaztatzea

Iragazkiak behar bezala ezartzen du konexioa netcat, azken ACK-a saltatuz, eta Linux-ek RST pakete batekin erantzun zion, sareko pilak ez baitzuen SYN jaso -SYNACK-era bihurtu eta itzultzen zen- eta OSaren ikuspuntutik, ez zen pakete bat iritsi zen. konexio irekiekin erlazionatuta.

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

Garrantzitsua da erabateko aplikazioekin egiaztatzea eta behatzea tcpdump on xdp-remote zeren, adibidez, hping3 ez die erantzuten kontrol-sumatu okerrei.

XDPren ikuspuntutik, egiaztapena bera hutsala da. Kalkulu-algoritmoa primitiboa da eta seguruenik erasotzaile sofistikatu baten aurrean zaurgarria da. Linux kernelak, adibidez, SipHash kriptografikoa erabiltzen du, baina XDPrako inplementazioa artikulu honen esparrutik kanpo dago argi eta garbi.

Kanpoko elkarrekintzarekin lotutako TODO berrietarako agertu da:

  • XDP programa ezin da gorde cookie_seed (gatzaren zati sekretua) aldagai global batean, kernel-denda bat behar duzu, zeinaren balioa aldian-aldian eguneratuko den sorgailu fidagarri batetik.

  • ACK paketeko SYN cookiea bat badator, ez duzu mezurik inprimatu beharrik, baina gogoratu egiaztatutako bezeroaren IPa bertatik paketeak gehiago saltatzeko.

Bezero legitimo batek baliozkotzea:

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

Erregistroek txekearen pasabidea erregistratu zuten (flags=0x2 SYN da, flags=0x10 ACK da):

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

Egiaztatutako IP zerrendarik ez dagoen bitartean, ez da SYN flood beraren aurkako babesik egongo, baina hona hemen komando honek abiarazitako ACK uholdearen erreakzioa:

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

Erregistroko sarrerak:

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

Ondorioa

Batzuetan, eBPF orokorrean eta XDP, bereziki, garapen-plataforma bat baino administratzaile aurreratuen tresna gisa aurkezten dira. Izan ere, XDP nukleo-paketeen prozesamendua oztopatzeko tresna bat da, eta ez nukleoaren pilaren alternatiba bat, DPDK eta nukleoaren saihesteko beste aukera batzuk bezala. Bestalde, XDP-k logika konplexu samarra ezartzeko aukera ematen du, eta, gainera, erraz eguneratzen da trafikoa prozesatzeko pausarik gabe. Egiaztatzaileak ez du arazo handirik sortzen, pertsonalki ez nioke uko egingo erabiltzaile-espazio kodearen zatiei.

Bigarren zatian, gaia interesgarria bada, egiaztatutako bezeroen taula osatuko dugu eta konexioak hautsiko ditugu, kontagailuak ezarri eta iragazkia kudeatzeko erabiltzaile-espaziorako utilitate bat idatziko dugu.

erreferentziak:

Iturria: www.habr.com

Gehitu iruzkin berria