Aħna niktbu protezzjoni kontra attakki DDoS fuq XDP. Parti nukleari

It-teknoloġija eXpress Data Path (XDP) tippermetti li jsir proċessar tat-traffiku bl-addoċċ fuq interfaces Linux qabel ma l-pakketti jidħlu fil-munzell tan-netwerk tal-kernel. Applikazzjoni ta 'XDP - protezzjoni kontra attakki DDoS (CloudFlare), filtri kumplessi, ġbir ta' statistika (Netflix). Il-programmi XDP huma esegwiti mill-magna virtwali eBPF, għalhekk għandhom restrizzjonijiet kemm fuq il-kodiċi tagħhom kif ukoll fuq il-funzjonijiet tal-kernel disponibbli skont it-tip ta 'filtru.

L-artikolu huwa maħsub biex jimla n-nuqqasijiet ta 'bosta materjali fuq XDP. L-ewwelnett, jipprovdu kodiċi lest li immedjatament jevita l-karatteristiċi ta 'XDP: huwa ppreparat għall-verifika jew huwa sempliċi wisq biex jikkawża problemi. Meta mbagħad tipprova tikteb il-kodiċi tiegħek mill-bidu, m'għandekx idea x'għandek tagħmel bi żbalji tipiċi. It-tieni, modi biex tittestja lokalment XDP mingħajr VM u hardware mhumiex koperti, minkejja l-fatt li għandhom in-nases tagħhom stess. It-test huwa maħsub għal programmaturi familjari man-netwerking u Linux li huma interessati fl-XDP u l-eBPF.

F'din il-parti, se nifhmu fid-dettall kif il-filtru XDP huwa mmuntat u kif tittestjah, allura aħna se niktbu verżjoni sempliċi tal-mekkaniżmu magħruf tal-cookies SYN fil-livell tal-ipproċessar tal-pakkett. Għadna mhux se noħolqu "lista bajda".
klijenti verifikati, iżommu counters u jimmaniġġjaw il-filtru - zkuk biżżejjed.

Aħna se niktbu f'Ċ - mhix moda, iżda hija prattika. Il-kodiċi kollu huwa disponibbli fuq GitHub permezz tal-link fl-aħħar u huwa maqsum f'commits skont l-istadji deskritti fl-artikolu.

Ċaħda ta 'responsabbiltà. Matul dan l-artikolu, ser niżviluppa mini-soluzzjoni biex inbiegħdu l-attakki DDoS, għaliex dan huwa kompitu realistiku għal XDP u l-qasam ta 'għarfien espert tiegħi. Madankollu, l-għan ewlieni huwa li tifhem it-teknoloġija; din mhix gwida għall-ħolqien ta 'protezzjoni lesta. Il-kodiċi tutorja mhuwiex ottimizzat u jħalli barra xi sfumaturi.

Ħarsa ġenerali fil-qosor XDP

Se niddeskrivi biss il-punti ewlenin sabiex ma nidduplikax id-dokumentazzjoni u l-artikoli eżistenti.

Għalhekk, il-kodiċi tal-filtru jitgħabba fil-qalba. Pakketti deħlin huma mgħoddija lill-filtru. Bħala riżultat, il-filtru għandu jieħu deċiżjoni: jgħaddi l-pakkett fil-qalba (XDP_PASS), qatra pakkett (XDP_DROP) jew ibgħatha lura (XDP_TX). Il-filtru jista 'jbiddel il-pakkett, dan huwa speċjalment veru għal XDP_TX. Tista' wkoll tħassar il-programm (XDP_ABORTED) u reset il-pakkett, iżda dan huwa analogu assert(0) - għad-debugging.

Il-magna virtwali eBPF (Extended Berkley Packet Filter) hija magħmula deliberatament sempliċi sabiex il-qalba tkun tista 'tiċċekkja li l-kodiċi ma jagħmilx loop u ma jagħmilx ħsara lill-memorja ta' nies oħra. Restrizzjonijiet u kontrolli kumulattivi:

  • Loops (b'lura) huma pprojbiti.
  • Hemm munzell għad-dejta, iżda l-ebda funzjoni (il-funzjonijiet C kollha għandhom ikunu inlined).
  • L-aċċessi għall-memorja barra l-munzell u l-buffer tal-pakkett huma pprojbiti.
  • Id-daqs tal-kodiċi huwa limitat, iżda fil-prattika dan mhux sinifikanti ħafna.
  • Sejħiet biss għal funzjonijiet speċjali tal-kernel (helpers eBPF) huma permessi.

Id-disinn u l-installazzjoni ta 'filtru jidher bħal dan:

  1. Kodiċi tas-sors (eż kernel.c) hija miġbura f'oġġett (kernel.o) għall-arkitettura tal-magni virtwali eBPF. Minn Ottubru 2019, il-kumpilazzjoni għal eBPF hija appoġġata minn Clang u mwiegħda fil-GCC 10.1.
  2. Jekk dan il-kodiċi tal-oġġett fih sejħiet għal strutturi tal-kernel (per eżempju, tabelli u counters), l-IDs tagħhom jiġu sostitwiti b'żeri, li jfisser li tali kodiċi ma jistgħux jiġu esegwiti. Qabel it-tagħbija fil-kernel, għandek bżonn tissostitwixxi dawn iż-żeri bl-IDs ta 'oġġetti speċifiċi maħluqa permezz ta' sejħiet tal-kernel (link il-kodiċi). Tista 'tagħmel dan b'utilitajiet esterni, jew tista' tikteb programm li jgħaqqad u jgħabbi filtru speċifiku.
  3. Il-qalba jivverifika l-programm mgħobbi. In-nuqqas ta 'ċikli u n-nuqqas li jinqabżu l-konfini tal-pakketti u l-munzell huma kkontrollati. Jekk il-verifikatur ma jistax jipprova li l-kodiċi huwa korrett, il-programm jiġi miċħud - jeħtieġ li tkun tista' togħġbok.
  4. Wara verifika b'suċċess, il-kernel jikkompila l-kodiċi tal-oġġett tal-arkitettura eBPF f'kodiċi tal-magni għall-arkitettura tas-sistema (just-in-time).
  5. Il-programm jehmeż mal-interface u jibda jipproċessa l-pakketti.

Peress li XDP jaħdem fil-qalba, id-debugging jitwettaq bl-użu ta 'ġurnali ta' traċċa u, fil-fatt, pakketti li l-programm jiffiltra jew jiġġenera. Madankollu, eBPF jiżgura li l-kodiċi mniżżel huwa sigur għas-sistema, sabiex tkun tista 'tesperimenta bl-XDP direttament fuq il-Linux lokali tiegħek.

Tħejjija għall-Ambjent

Assemblea

Clang ma jistax jipproduċi direttament kodiċi tal-oġġett għall-arkitettura eBPF, għalhekk il-proċess jikkonsisti f'żewġ passi:

  1. Ikkompila kodiċi C għal bytecode LLVM (clang -emit-llvm).
  2. Ikkonverti bytecode għal kodiċi tal-oġġett eBPF (llc -march=bpf -filetype=obj).

Meta tikteb filtru, koppja ta 'fajls b'funzjonijiet awżiljarji u macros se jkunu utli minn testijiet tal-qalba. Huwa importanti li jaqblu mal-verżjoni tal-kernel (KVER). Niżżelhom fuq 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 għal 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 fih it-triq għall-headers tal-kernel, ARCH — arkitettura tas-sistema. Il-mogħdijiet u l-għodod jistgħu jvarjaw kemmxejn bejn id-distribuzzjonijiet.

Eżempju ta' differenzi għal 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 qabbad direttorju b'headers awżiljarji u diversi direttorji b'headers tal-kernel. Simbolu __KERNEL__ ifisser li l-headers UAPI (userspace API) huma definiti għall-kodiċi tal-kernel, peress li l-filtru huwa eżegwit fil-kernel.

Il-protezzjoni tal-munzell tista' tiġi diżattivata (-fno-stack-protector), minħabba li l-verifikatur tal-kodiċi eBPF għadu jiċċekkja għal ksur tal-munzell barra mill-konfini. Ta 'min jixgħel l-ottimizzazzjonijiet minnufih, minħabba li d-daqs tal-bytecode eBPF huwa limitat.

Nibdew b'filtru li jgħaddi l-pakketti kollha u ma jagħmel xejn:

#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 tiġbor xdp_filter.o. Fejn tipprovaha issa?

Stand tat-test

L-istand irid jinkludi żewġ interfaces: li fuqhom se jkun hemm filtru u minn fejn jintbagħtu l-pakketti. Dawn iridu jkunu tagħmir Linux sħiħ bl-IPs tagħhom stess sabiex jiċċekkjaw kif l-applikazzjonijiet regolari jaħdmu mal-filtru tagħna.

Apparati tat-tip veth (Ethernet virtwali) huma adattati għalina: dawn huma par interfaces ta 'netwerk virtwali "konnessi" direttament ma' xulxin. Tista 'toħloqhom bħal dan (f'din it-taqsima l-kmandi kollha ip jitwettqu minn root):

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

Hawnhekk xdp-remote и xdp-local — ismijiet tal-apparat. Fuq xdp-local (192.0.2.1/24) se jitwaħħal filtru, bil xdp-remote (192.0.2.2/24) se jintbagħat it-traffiku li jkun dieħel. Madankollu, hemm problema: l-interfaces huma fuq l-istess magna, u Linux mhux se jibgħat traffiku lil wieħed minnhom permezz ta 'l-ieħor. Tista 'ssolvi dan b'regoli delikati iptables, iżda se jkollhom jibdlu l-pakketti, li huwa inkonvenjenti għad-debugging. Huwa aħjar li tuża namespaces tan-netwerk (minn hawn 'il quddiem netns).

Spazju tal-isem tan-netwerk fih sett ta' interfaces, tabelli tar-routing, u regoli NetFilter li huma iżolati minn oġġetti simili f'netns oħra. Kull proċess jimxi fi namespace u għandu biss aċċess għall-oġġetti ta 'dak netns. B'mod awtomatiku, is-sistema għandha spazju tal-isem tan-netwerk wieħed għall-oġġetti kollha, sabiex tkun tista 'taħdem fil-Linux u ma tkunx taf dwar netns.

Ejja noħolqu spazju għall-isem ġdid xdp-test u ċċaqlaqha hemm xdp-remote.

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

Imbagħad il-proċess għaddej xdp-test, mhux se "tara" xdp-local (se jibqa' fin-netns awtomatikament) u meta jibgħat pakkett lil 192.0.2.1 jgħaddih xdp-remoteminħabba li hija l-unika interface fuq 192.0.2.0/24 aċċessibbli għal dan il-proċess. Dan jaħdem ukoll fid-direzzjoni opposta.

Meta tiċċaqlaq bejn netns, l-interface jinżel u jitlef l-indirizz tiegħu. Biex tikkonfigura l-interface f'netns, trid tmexxi ip ... f'dan l-ispazju tal-isem tal-kmand 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

Kif tistgħu taraw, dan mhuwiex differenti mill-issettjar xdp-local fl-ispazju tal-isem default:

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

Jekk tmexxi tcpdump -tnevi xdp-local, tista 'tara li pakketti mibgħuta minn xdp-test, huma kkunsinnati lil din l-interface:

ip netns exec xdp-test   ping 192.0.2.1

Huwa konvenjenti li tniedi qoxra ġewwa xdp-test. Ir-repożitorju għandu skript li awtomat ix-xogħol mal-istand; pereżempju, tista 'tikkonfigura l-istand bil-kmand sudo ./stand up u ħassarha sudo ./stand down.

Traċċar

Il-filtru huwa assoċjat mal-apparat bħal dan:

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

Ewlenin -force meħtieġa biex torbot programm ġdid jekk ieħor ikun diġà konness. "L-ebda aħbar hija aħbar tajba" mhix dwar dan il-kmand, il-konklużjoni hija voluminuża fi kwalunkwe każ. jindikaw verbose fakultattiv, iżda magħha jidher rapport dwar ix-xogħol tal-verifikatur tal-kodiċi b'lista tal-assemblaġġ:

Verifier analysis:

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

Unlink il-programm mill-interface:

ip link set dev xdp-local xdp off

Fl-iskrittura dawn huma kmandi sudo ./stand attach и sudo ./stand detach.

Billi twaħħal filtru, tista 'tagħmel ċert li ping tkompli taħdem, imma l-programm jaħdem? Ejja nżidu zkuk. Funzjoni bpf_trace_printk() simili għal printf(), iżda jappoġġja biss sa tliet argumenti minbarra l-mudell, u lista limitata ta 'speċifikaturi. Makro bpf_printk() tissimplifika s-sejħa.

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

L-output imur għall-kanal tat-traċċa tal-qalba, li jeħtieġ li jkun attivat:

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

Ara l-ħajt tal-messaġġ:

cat /sys/kernel/debug/tracing/trace_pipe

Dawn iż-żewġ kmandi jagħmlu sejħa sudo ./stand log.

Ping issa għandu jqajjem messaġġi bħal dan:

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

Jekk tħares mill-qrib lejn l-output tal-verifikatur, tinnota kalkoli strambi:

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

Il-fatt hu li l-programmi eBPF m'għandhomx sezzjoni tad-dejta, għalhekk l-uniku mod biex tiġi kkodifikata string tal-format huwa l-argumenti immedjati tal-kmandi VM:

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

Għal din ir-raġuni, l-output tad-debug bloats ħafna l-kodiċi li jirriżulta.

Tibgħat Pakketti XDP

Ejja nibdlu l-filtru: ħalliha tibgħat lura l-pakketti kollha li jkunu deħlin. Dan huwa żbaljat mil-lat tan-netwerk, peress li jkun meħtieġ li l-indirizzi fl-intestaturi jinbidel, iżda issa x-xogħol fil-prinċipju huwa importanti.

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

Tnedija tcpdump fuq xdp-remote. Għandu juri ICMP Echo Request identiku ħerġin u deħlin u jieqaf juri ICMP Echo Reply. Imma ma jurix. Jirriżulta li għax-xogħol XDP_TX fil-programm fuq xdp-local huwa meħtieġgħall-interface tal-par xdp-remote ġie assenjat ukoll programm, anke jekk kien vojt, u tqajjem.

Kif kont naf dan?

Intraċċa l-passaġġ ta 'pakkett fil-qalba Il-mekkaniżmu ta 'avvenimenti perf jippermetti, mill-mod, li tuża l-istess magna virtwali, jiġifieri, eBPF jintuża għal żarmar b'eBPF.

Trid tagħmel il-ġid mill-ħażen, għax m'hemm xejn aktar minnu.

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

X'inhu l-kodiċi 6?

$ errno 6
ENXIO 6 No such device or address

Funzjoni veth_xdp_flush_bq() jirċievi kodiċi ta 'żball minn veth_xdp_xmit(), fejn tfittxija minn ENXIO u sib il-kumment.

Ejja nirrestawraw il-filtru minimu (XDP_PASS) fil-fajl xdp_dummy.c, żidha mal-Makefile, għaqqadha xdp-remote:

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

Issa tcpdump juri x'inhu mistenni:

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

Jekk minflok jintwerew ARPs biss, trid tneħħi l-filtri (dan jagħmel sudo ./stand detach), itlaq ping, imbagħad issettja l-filtri u erġa' pprova. Il-problema hija li l-filtru XDP_TX validu kemm fuq ARP kif ukoll jekk il-munzell
spazji tal-isem xdp-test irnexxielu "jinsa" l-indirizz MAC 192.0.2.1, mhux se jkun kapaċi jsolvi din l-IP.

Dikjarazzjoni tal-problema

Ejja ngħaddu għall-kompitu ddikjarat: ikteb mekkaniżmu tal-cookies SYN fuq XDP.

L-għargħar SYN jibqa 'attakk DDoS popolari, li l-essenza tiegħu hija kif ġej. Meta tiġi stabbilita konnessjoni (TCP handshake), is-server jirċievi SYN, jalloka riżorsi għall-konnessjoni futura, jirrispondi b'pakkett SYNACK u jistenna ACK. L-attakkant sempliċement jibgħat eluf ta 'pakketti SYN kull sekonda minn indirizzi spoofed minn kull ospitanti f'botnet b'ħafna eluf. Is-server huwa sfurzat jalloka riżorsi immedjatament mal-wasla tal-pakkett, iżda jirrilaxxahom wara timeout kbir; bħala riżultat, il-memorja jew il-limiti huma eżawriti, konnessjonijiet ġodda mhumiex aċċettati, u s-servizz mhux disponibbli.

Jekk ma tallokax riżorsi bbażati fuq il-pakkett SYN, iżda tirrispondi biss b'pakkett SYNACK, kif allura jista' s-server jifhem li l-pakkett ACK li wasal aktar tard jirreferi għal pakkett SYN li ma ġiex salvat? Wara kollox, attakkant jista 'wkoll jiġġenera ACKs foloz. Il-punt tal-cookie SYN huwa li tikkodifikaha seqnum parametri ta 'konnessjoni bħala hash ta' indirizzi, portijiet u melħ li qed jinbidel. Jekk l-ACK irnexxielu jasal qabel ma nbidel il-melħ, tista 'terġa' tikkalkula l-hash u tqabbelha magħha acknum. Forġa acknum l-attakkant ma jistax, peress li l-melħ jinkludi s-sigriet, u mhux se jkollu ħin biex issolvih minħabba l-kanal limitat.

Il-cookie SYN ilha implimentata fil-kernel tal-Linux u tista 'saħansitra tiġi attivata awtomatikament jekk is-SYNs jaslu malajr wisq u b'mod massiv.

Programm edukattiv dwar it-TCP handshake

TCP jipprovdi trażmissjoni tad-dejta bħala fluss ta 'bytes, pereżempju, it-talbiet HTTP huma trażmessi fuq TCP. In-nixxiegħa hija trażmessa f'biċċiet f'pakketti. Il-pakketti TCP kollha għandhom bnadar loġiċi u numri ta’ sekwenza ta’ 32 bit:

  • Il-kombinazzjoni ta 'bnadar tiddetermina r-rwol ta' pakkett partikolari. Il-bandiera SYN tindika li dan huwa l-ewwel pakkett tal-mittent fuq il-konnessjoni. Il-bandiera ACK tfisser li l-mittent irċieva d-dejta tal-konnessjoni kollha sal-byte acknum. Pakkett jista' jkollu diversi bnadar u jissejjaħ bil-kombinazzjoni tagħhom, pereżempju, pakkett SYNACK.

  • Numru tas-sekwenza (seqnum) jispeċifika l-offset fil-fluss tad-dejta għall-ewwel byte li jiġi trażmess f'dan il-pakkett. Pereżempju, jekk fl-ewwel pakkett b'X bytes ta 'dejta dan in-numru kien N, fil-pakkett li jmiss b'dejta ġdida jkun N+X. Fil-bidu tal-konnessjoni, kull naħa tagħżel dan in-numru b'mod każwali.

  • Numru ta' rikonoxximent (acknum) - l-istess offset bħal seqnum, iżda ma jiddeterminax in-numru tal-byte li qed jiġi trażmess, iżda n-numru tal-ewwel byte mir-riċevitur, li min jibgħat ma rax.

Fil-bidu tal-konnessjoni, il-partijiet għandhom jaqblu seqnum и acknum. Il-klijent jibgħat pakkett SYN miegħu seqnum = X. Is-server jirrispondi b'pakkett SYNACK, fejn jirreġistra tiegħu seqnum = Y u jesponi acknum = X + 1. Il-klijent jirrispondi għal SYNACK b'pakkett ACK, fejn seqnum = X + 1, acknum = Y + 1. Wara dan, jibda t-trasferiment tad-data attwali.

Jekk il-peer ma jirrikonoxxix irċevuta tal-pakkett, TCP jerġa' jibgħatu wara timeout.

Għaliex il-cookies SYN mhux dejjem jintużaw?

L-ewwelnett, jekk tintilef SYNACK jew ACK, ikollok tistenna li terġa' tintbagħat - is-setup tal-konnessjoni jonqos. It-tieni nett, fil-pakkett SYN - u fih biss! — jiġu trażmessi numru ta' għażliet li jaffettwaw l-operat ulterjuri tal-konnessjoni. Mingħajr ma jiftakar il-pakketti SYN deħlin, is-server għalhekk jinjora dawn l-għażliet; il-klijent mhux se jibgħathom fil-pakketti li jmiss. TCP jista 'jaħdem f'dan il-każ, iżda għall-inqas fl-istadju inizjali l-kwalità tal-konnessjoni se tonqos.

Mill-perspettiva tal-pakketti, programm XDP għandu jagħmel dan li ġej:

  • tirrispondi għal SYN b'SYNACK b'cookie;
  • tirrispondi għal ACK b'RST (skonnettja);
  • armi l-pakketti li fadal.

Psewdokodiċi tal-algoritmu flimkien mal-parsing tal-pakketti:

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

Waħda (*) punti fejn għandek bżonn timmaniġġja l-istat tas-sistema huma mmarkati - fl-ewwel stadju tista 'tagħmel mingħajrhom billi sempliċiment timplimenta handshake TCP bil-ġenerazzjoni ta' cookie SYN bħala seqnum.

Fuq il-post (**), filwaqt li m'għandniex tabella, se naqbżu l-pakkett.

Implimentazzjoni tat-TCP handshake

Parsing tal-pakkett u verifika tal-kodiċi

Ikollna bżonn strutturi tal-header tan-netwerk: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) u TCP (uapi/linux/tcp.h). Ma stajtx nikkonnettja dan tal-aħħar minħabba żbalji relatati atomic64_t, Kelli nikkopja d-definizzjonijiet meħtieġa fil-kodiċi.

Il-funzjonijiet kollha li huma enfasizzati f'C għal-leġibbiltà għandhom ikunu inlined fil-punt ta 'sejħa, peress li l-verifikatur eBPF fil-kernel jipprojbixxi backtracking, jiġifieri, fil-fatt, loops u sejħiet ta' funzjoni.

#define INTERNAL static __attribute__((always_inline))

Makro LOG() tiddiżattiva l-istampar fil-build tar-rilaxx.

Il-programm huwa conveyor ta 'funzjonijiet. Kull wieħed jirċievi pakkett li fih l-intestatura tal-livell korrispondenti hija enfasizzata, pereżempju, process_ether() jistenna li jimtela ether. Ibbażat fuq ir-riżultati tal-analiżi tal-post, il-funzjoni tista 'tgħaddi l-pakkett għal livell ogħla. Ir-riżultat tal-funzjoni huwa l-azzjoni XDP. Għalissa, il-handlers SYN u ACK jgħaddu l-pakketti kollha.

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

Niġbed l-attenzjoni tiegħek għall-kontrolli mmarkati A u B. Jekk tikkummenta A, il-programm se jibni, iżda se jkun hemm żball ta 'verifika meta tagħbija:

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!

Spag ewlieni invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Hemm mogħdijiet ta 'eżekuzzjoni meta t-tlettax-il byte mill-bidu tal-buffer ikun barra l-pakkett. Huwa diffiċli li wieħed jifhem mil-lista liema linja qed nitkellmu, iżda hemm numru ta 'struzzjoni (12) u disassembler li juri l-linji tal-kodiċi tas-sors:

llvm-objdump -S xdp_filter.o | less

F'dan il-każ jindika l-linja

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

li jagħmilha ċara li l-problema hija ether. Dejjem ikun hekk.

Irrispondi għal SYN

L-għan f'dan l-istadju huwa li jiġġenera pakkett SYNACK korrett b'fiss seqnum, li se tiġi sostitwita fil-futur bil-cookie SYN. Il-bidliet kollha jseħħu fi process_tcp_syn() u ż-żoni tal-madwar.

Verifika tal-pakkett

B'mod stramb, hawnhekk hija l-linja l-aktar notevoli, jew aħjar, il-kummentarju għaliha:

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

Meta tikteb l-ewwel verżjoni tal-kodiċi, intuża l-kernel 5.1, li għall-verifikatur tiegħu kien hemm differenza bejn data_end и (const void*)ctx->data_end. Fiż-żmien tal-kitba, il-kernel 5.3.1 ma kellux din il-problema. Huwa possibbli li l-kompilatur kien qed jaċċessa varjabbli lokali b'mod differenti minn qasam. Morali tal-istorja: Is-simplifikazzjoni tal-kodiċi tista 'tgħin meta jkun hemm ħafna nesting.

Sussegwentement huma l-kontrolli tat-tul ta 'rutina għall-glorja tal-verifikatur; O MAX_CSUM_BYTES hawn taħt.

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

Tiżvolġi l-pakkett

Nimlew seqnum и acknum, issettja ACK (SYN diġà ssettjat):

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

Ibdel il-portijiet TCP, l-indirizz IP u l-indirizzi MAC. Il-librerija standard mhix aċċessibbli mill-programm XDP, għalhekk memcpy() — makro li jaħbi l-intrinsiċi Clang.

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

Rikalkolazzjoni ta' checksums

Iċ-checksums IPv4 u TCP jeħtieġu ż-żieda tal-kliem kollha ta '16-bit fl-headers, u d-daqs tal-headers huwa miktub fihom, jiġifieri mhux magħruf fil-ħin tal-kompilazzjoni. Din hija problema minħabba li l-verifikatur mhux se jaqbeż il-linja normali għall-varjabbli tal-konfini. Iżda d-daqs tal-headers huwa limitat: sa 64 bytes kull wieħed. Tista 'tagħmel loop b'numru fiss ta' iterazzjonijiet, li jista 'jispiċċa kmieni.

Ninnota li hemm RFC 1624 dwar kif tiġi kkalkulata mill-ġdid parzjalment iċ-checksum jekk jinbidel biss il-kliem fiss tal-pakketti. Madankollu, il-metodu mhuwiex universali, u l-implimentazzjoni tkun aktar diffiċli biex tinżamm.

Funzjoni ta' kalkolu tas-summa ta' kontroll:

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

Għalkemm size verifikata mill-kodiċi tas-sejħa, it-tieni kundizzjoni ta 'ħruġ hija meħtieġa sabiex il-verifikatur ikun jista' jipprova t-tlestija tal-linja.

Għal kliem ta' 32 bit, hija implimentata verżjoni aktar sempliċi:

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

Fil-fatt ikkalkola mill-ġdid iċ-checksums u tibgħat il-pakkett lura:

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;

Funzjoni carry() jagħmel checksum minn somma ta’ 32 bit ta’ kliem ta’ 16-il bit, skont RFC 791.

TCP handshake verifika

Il-filtru jistabbilixxi b'mod korrett konnessjoni ma ' netcat, nieqes l-ACK finali, li għalih Linux wieġbet b'pakkett RST, peress li l-munzell tan-netwerk ma rċevietx SYN - ġie kkonvertit għal SYNACK u mibgħut lura - u mill-aspett tal-OS, wasal pakkett li ma kienx relatat mal-ftuħ. konnessjonijiet.

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

Huwa importanti li tiċċekkja b'applikazzjonijiet kompluti u tosserva tcpdump fuq xdp-remote għaliex, pereżempju, hping3 ma jirrispondix għal checksums mhux korretti.

Mil-lat XDP, il-verifika nnifisha hija trivjali. L-algoritmu tal-kalkolu huwa primittiv u x'aktarx vulnerabbli għal attakkant sofistikat. Il-qalba tal-Linux, pereżempju, juża s-SipHash kriptografiku, iżda l-implimentazzjoni tiegħu għal XDP hija b'mod ċar lil hinn mill-ambitu ta 'dan l-artikolu.

Introdott għal TODOs ġodda relatati mal-komunikazzjoni esterna:

  • Il-programm XDP ma jistax jaħżen cookie_seed (il-parti sigrieta tal-melħ) f'varjabbli globali, għandek bżonn ħażna fil-qalba, li l-valur tagħha jiġi aġġornat perjodikament minn ġeneratur affidabbli.

  • Jekk il-cookie SYN taqbel mal-pakkett ACK, m'għandekx bżonn tipprintja messaġġ, imma tiftakar l-IP tal-klijent verifikat sabiex tkompli tgħaddi pakketti minnu.

Verifika leġittima tal-klijent:

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

Ir-reġistri juru li l-kontroll għadda (flags=0x2 - dan huwa SYN, flags=0x10 huwa 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

Filwaqt li m'hemm l-ebda lista ta 'IPs verifikati, mhux se jkun hemm protezzjoni mill-għargħar SYN innifsu, iżda hawnhekk hija r-reazzjoni għal għargħar ACK imniedi mill-kmand li ġej:

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

Dħul fil-log:

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

Konklużjoni

Xi drabi eBPF b'mod ġenerali u XDP b'mod partikolari huma ppreżentati aktar bħala għodda avvanzata ta 'amministratur milli bħala pjattaforma ta' żvilupp. Tabilħaqq, XDP hija għodda biex tinterferixxi mal-ipproċessar tal-pakketti mill-kernel, u mhux alternattiva għall-munzell tal-kernel, bħal DPDK u għażliet oħra ta 'bypass tal-kernel. Min-naħa l-oħra, XDP jippermettilek li timplimenta loġika pjuttost kumplessa, li, barra minn hekk, hija faċli biex taġġorna mingħajr interruzzjoni fl-ipproċessar tat-traffiku. Il-verifikatur ma joħloqx problemi kbar; personalment, ma nirrifjutax dan għal partijiet tal-kodiċi tal-ispazju tal-utent.

Fit-tieni parti, jekk is-suġġett huwa interessanti, aħna se tlesti t-tabella ta 'klijenti verifikati u skonnessjonijiet, nimplimentaw counters u niktbu utilità userspace biex timmaniġġja l-filtru.

Referenzi:

Sors: www.habr.com

Żid kumment