Em parastina li dijî êrîşên DDoS li ser XDP dinivîsin. Beşa nukleerî

Teknolojiya Rêya Daneyên eXpress (XDP) dihêle ku pêvajoyek seyrûsefera rasthatî li ser navrûyên Linux-ê were kirin berî ku pakêt têkevin stika tora kernelê. Serîlêdana XDP - parastina li dijî êrîşên DDoS (CloudFlare), fîlterên tevlihev, berhevkirina statîstîkan (Netflix). Bernameyên XDP ji hêla makîneya virtual eBPF ve têne darve kirin, ji ber vê yekê hem li ser koda wan û hem jî fonksiyonên kernelê yên berdest li gorî celebê parzûnê sînorkirin hene.

Gotar armanc e ku kêmasiyên gelek materyalên li ser XDP tije bike. Pêşîn, ew kodek amade peyda dikin ku tavilê taybetmendiyên XDP derbas dike: ew ji bo verastkirinê amade ye an jî pir hêsan e ku bibe sedema pirsgirêkan. Gava ku hûn wê gavê hewl didin ku koda xwe ji nû ve binivîsin, hûn nizanin ku hûn bi xeletiyên tîpîk re çi bikin. Ya duyemîn, awayên ceribandina herêmî ya XDP bêyî VM û hardware nayên vegirtin, tevî vê yekê ku ew xeletiyên xwe hene. Nivîsar ji bo bernamenûsên ku bi torê û Linux-ê nas dikin ku bi XDP û eBPF-ê re eleqedar in, tê armanc kirin.

Di vê beşê de, em ê bi hûrgulî fam bikin ka Parzûna XDP çawa tê berhev kirin û meriv wê çawa ceribandine, dûv re em ê guhertoyek hêsan a mekanîzmaya cookies-a naskirî ya SYN di asta pêvajoya pakêtê de binivîsin. Em ê hîn "lîsteyek spî" neafirînin
xerîdarên verastkirî, jimarvanan bihêlin û parzûnê birêve bibin - têr têketin.

Em ê bi C binivîsin - ew ne moda ye, lê pratîk e. Hemî kod li ser GitHub-ê bi lînka paşîn ve tê peyda kirin û li gorî qonaxên ku di gotarê de hatine destnîşan kirin li peywiran têne dabeş kirin.

Disclaimer. Di heyama vê gotarê de, ez ê çareseriyek piçûk pêş bixim da ku êrişên DDoS biparêzim, ji ber ku ev ji bo XDP û qada pisporiya min karekî realîst e. Lêbelê, armanca sereke ew e ku meriv teknolojiyê fam bike ev ne rêbernameyek e ku parastina amade ye. Koda dersê ne xweşbîn e û hin nuwazeyan derdixe.

XDP Kurtî

Ez ê tenê xalên sereke destnîşan bikim da ku belge û gotarên heyî dubare nekim.

Ji ber vê yekê, koda parzûnê di kernelê de tê barkirin. Pakêtên hatinî derbasî parzûnê dibin. Wekî encamek, parzûn divê biryarek bide: pakêtê derbasî kernelê bike (XDP_PASS), pakêtê avêtin (XDP_DROP) an wê paşde bişînin (XDP_TX). Parzûn dikare pakêtê biguhezîne, ev bi taybetî ji bo rast e XDP_TX. Her weha hûn dikarin bernameyê betal bikin (XDP_ABORTED) û pakêtê ji nû ve saz bikin, lê ev analog e assert(0) - ji bo xeletkirinê.

Makîneya virtual eBPF (Parzûna Pakê ya Berkley ya dirêjkirî) bi qestî hêsan hatî çêkirin da ku kernel kontrol bike ka kod naqede û zirarê nade bîra kesên din. Qedexe û kontrolên kombûyî:

  • Loops (berepaş) qedexe ne.
  • Ji bo daneyan stûnek heye, lê fonksiyon tune (divê hemî fonksiyonên C-yê bêne xêz kirin).
  • Gihîştina bîranînê li derveyî stack û tampon pakêtê qedexe ye.
  • Mezinahiya kodê sînorkirî ye, lê di pratîkê de ev ne pir girîng e.
  • Tenê bangên fonksiyonên kernelê yên taybetî (arîkarên eBPF) têne destûr kirin.

Sêwirandin û sazkirina parzûnek weha xuya dike:

  1. Koda çavkaniyê (mînak kernel.c) di objeyê de tê berhevkirin (kernel.o) ji bo mîmariya makîneya virtual eBPF. Ji Cotmeha 2019-an ve, berhevkirina eBPF ji hêla Clang ve tê piştgirî kirin û di GCC 10.1-ê de soz tê dayîn.
  2. Ger ev koda objeyê bang li strukturên kernelê bike (mînak, tablo û jimarvan), nasnameyên wan bi sifiran têne guheztin, ev tê vê wateyê ku kodek wusa nikare were darve kirin. Berî barkirina nav kernelê, hûn hewce ne ku van sifiran bi nasnameyên tiştên taybetî yên ku bi bangên kernelê ve hatine afirandin biguhezînin (kodê girêdin). Hûn dikarin vê yekê bi karûbarên derveyî re bikin, an jî hûn dikarin bernameyek binivîsin ku dê parzûnek taybetî girêbide û bar bike.
  3. Kernel bernameya barkirî piştrast dike. Nebûna dewran û nebûna derbasbûna sînorên pakêt û stêkê tê kontrol kirin. Ger verastker nikaribe îsbat bike ku kod rast e, bername tê red kirin - hûn hewce ne ku hûn wî xweş bikin.
  4. Piştî verastkirina serketî, kernel ji bo mîmariya pergalê (tenê-dem-demê) koda objektê ya mîmariya eBPF di koda makîneyê de berhev dike.
  5. Bername bi navbeynê ve girêdide û dest bi pêvajokirina pakêtan dike.

Ji ber ku XDP di kernelê de dimeşe, debugkirin bi karanîna têketinên şopê û, bi rastî, pakêtên ku bername fîlter dike an diafirîne, tê meşandin. Lêbelê, eBPF piştrast dike ku koda dakêşandî ji bo pergalê ewle ye, ji ber vê yekê hûn dikarin XDP-ê rasterast li ser Linux-a xweya herêmî biceribînin.

Amadekirina Jîngehê

Meclîsa

Clang nikare rasterast ji bo mîmariya eBPF koda objektê hilberîne, ji ber vê yekê pêvajo ji du gavan pêk tê:

  1. Koda C-yê bi bytekodê LLVM berhev bikin (clang -emit-llvm).
  2. Bytecode veguherînin koda objeya eBPF (llc -march=bpf -filetype=obj).

Dema ku parzûnek dinivîsin, çend pelên bi fonksiyonên alîkar û makro dê bikêr bin ji testên kernel. Girîng e ku ew guhertoya kernelê li hev bikin (KVER). Daxistina wan 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 ji bo 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 riya sernavên kernelê vedihewîne, ARCH - mîmariya pergalê. Dibe ku rê û amûr di navbera dabeşan de hinekî cûda bibin.

Mînak cûdahiyên ji bo 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 pelrêçekek bi sernivîsên alîkar û çend pelrêçan bi sernavên kernel ve girêdin. Nîşan __KERNEL__ tê vê wateyê ku sernavên UAPI (API cîhê bikarhêner) ji bo koda kernelê têne diyar kirin, ji ber ku parzûn di kernelê de tê darve kirin.

Parastina stackê dikare bête asteng kirin (-fno-stack-protector), ji ber ku verastkerê kodê eBPF hîn jî binpêkirinên li derveyî sînoran kontrol dike. Hêja ye ku tavilê xweşbîniyan bizivirînin, ji ber ku mezinahiya bytekodê eBPF sînordar e.

Ka em bi parzûnek ku hemî pakêtan derbas dike û tiştek nake dest pê bikin:

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

tîma make berhev dike xdp_filter.o. Niha li ku biceribîne?

Stand testê

Pêdivî ye ku stend du navbeynkar hebin: li ser kîjan parzûnek heye û ji kîjan pakêt dê bêne şandin. Pêdivî ye ku ev cîhazên Linux-ê yên bêkêmasî yên bi IP-yên xwe bin da ku kontrol bikin ka serîlêdanên birêkûpêk bi fîltera me re çawa dixebitin.

Amûrên celebê veth (Etherneta virtual) ji bo me guncan in: ev cotek navrûyên torê yên virtual "girêdayî" rasterast bi hev re ne. Hûn dikarin wan bi vî rengî biafirînin (di vê beşê de hemî ferman ip ji têne kirin root):

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

Ev e xdp-remote и xdp-local - navên cîhazên. Li xdp-local (192.0.2.1/24) Parzûnek wê were girêdan, bi xdp-remote (192.0.2.2/24) seyrûsefera hatinê dê were şandin. Lêbelê, pirsgirêkek heye: navber li ser heman makîneyê ne, û Linux dê seyrûseferê ji yek ji wan re bi ya din re neşîne. Hûn dikarin vê bi qaîdeyên dijwar çareser bikin iptables, lê ew ê neçar bin ku pakêtan biguhezînin, ku ji bo xeletkirinê nerehet e. Çêtir e ku meriv cîhên navên torê (li vir netns) bikar bîne.

Cihê navekî torê komek navber, tabloyên rêvekirinê, û qaîdeyên NetFilter-ê yên ku ji tiştên mîna yên di torên din de têne veqetandin vedihewîne. Her pêvajo di nav cîhek navekî de dimeşe û tenê gihîştina tiştên wê torê heye. Ji hêla xwerû, pergalê ji bo hemî tiştan navek yek torê heye, ji ber vê yekê hûn dikarin li Linux-ê bixebitin û li ser netn-an nizanin.

Werin em navekî nû ava bikin xdp-test û li wir bar bikin xdp-remote.

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

Piştre pêvajo dimeşe xdp-test, dê "nebîne" xdp-local (ew ê ji hêla xwerû di toranan de bimîne) û dema ku pakêtek bişîne 192.0.2.1 ew ê jê re derbas bibe. xdp-remoteji ber ku ew yekane navbeynkariya li ser 192.0.2.0/24 e ku ji vê pêvajoyê re tê gihîştin. Ev jî berovajî vê yekê dixebite.

Dema ku di navbera toran de digere, navber dadikeve û navnîşana xwe winda dike. Ji bo veavakirina navbeynê di netns de, hûn hewce ne ku hûn bixebitin ip ... di nav navê vê fermanê de 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

Wekî ku hûn dibînin, ev ji mîhengê ne cûda ye xdp-local di nav cîhê xwerû de:

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

Ger hûn birevin tcpdump -tnevi xdp-local, hûn dikarin bibînin ku pakêt ji hatine şandin xdp-test, ji vê navberê re têne şandin:

ip netns exec xdp-test   ping 192.0.2.1

Destpêkirina şêlê tê de rehet e xdp-test. Di depoyê de skrîptek heye ku karê bi standê re otomatîk dike, ji bo nimûne, hûn dikarin bi fermanê rawestgehê mîheng bikin sudo ./stand up û jêbirin sudo ./stand down.

Şopandin

Parzûn bi vê amûrê re têkildar e:

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

Ключ -force hewce ye ku bernameyek nû ve girêbide heke bernameyek din jixwe ve girêdayî ye. "Tu nûçe ne mizgîniyek baş e" ne li ser vê fermanê ye, encam di her rewşê de mezin e. nîşandan verbose vebijarkî, lê digel wê raporek li ser xebata verastkerê kodê bi navnîşek meclîsê xuya dike:

Verifier analysis:

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

Bernameyê ji navrûyê veqetîne:

ip link set dev xdp-local xdp off

Di skrîptê de ev ferman in sudo ./stand attach и sudo ./stand detach.

Bi pêvekirina parzûnek, hûn dikarin wê piştrast bikin ping berdewam dike, lê bername dixebite? Ka em têketin lê zêde bikin. Karkirin bpf_trace_printk() mîna printf(), lê tenê sê argumanên din ji xeynî nimûne, û navnîşek sînorkirî ya diyarkeran piştgirî dike. Macro bpf_printk() bangê hêsan dike.

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

Derketin diçe kanala şopandina kernelê, ku pêdivî ye ku were çalak kirin:

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

Mijara peyamê bibînin:

cat /sys/kernel/debug/tracing/trace_pipe

Van her du fermanan bang dikin sudo ./stand log.

Ping divê nuha peyamên bi vî rengî bide destpêkirin:

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

Ger hûn ji nêz ve li hilberîna verastker binêrin, hûn ê hesabên ecêb bibînin:

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

Rastî ev e ku bernameyên eBPF xwedan beşek daneyê nînin, ji ber vê yekê riya yekane ji bo şîfrekirina rêzika formatê argumanên tavilê yên fermanên VM-ê ye:

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

Ji ber vê yekê, derketina debug pir koda encam dişewitîne.

Pakêtên XDP dişînin

Ka em fîlterê biguherînin: bila ew hemî pakêtên hatî paşve bişîne. Ev ji hêla torê ve nerast e, ji ber ku pêdivî ye ku navnîşan di sernavan de biguhezînin, lê naha xebata di prensîbê de girîng e.

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

Destpêkirin tcpdump li ser xdp-remote. Pêdivî ye ku ew Daxwaza Echo ICMP-ê ya derketin û tê de wekhev nîşan bide û nîşandana ICMP Echo Reply rawestîne. Lê xuya nake. Ji bo xebatê derdikeve holê XDP_TX di bernameyê de li ser xdp-local pêwîst eji bo pêwendiya cotê xdp-remote bernameyek jî hat destnîşankirin, her çiqas vala be jî û ew rabû.

Min ev çawa dizanî?

Rêya pakêtek di kernelê de bişopînin Mekanîzmaya bûyerên perf dihêle, bi awayê, heman makîneya virtual bikar bîne, ango, eBPF ji bo veqetandina bi eBPF ve tê bikar anîn.

Divê hûn ji xerabiyê qenciyê bikin, ji ber ku tiştek din tune ku meriv jê çêbike.

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

Koda 6 çi ye?

$ errno 6
ENXIO 6 No such device or address

function veth_xdp_flush_bq() koda çewtiyê ji distîne veth_xdp_xmit(), li ku derê lêgerîn ENXIO û şîroveyê bibînin.

Ka em parzûna hindiktirîn vegerînin (XDP_PASS) di pelê de xdp_dummy.c, wê li Makefile zêde bikin, pê ve girêdin xdp-remote:

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

Now tcpdump nîşan dide ku çi tê hêvîkirin:

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

Ger li şûna wê tenê ARP têne xuyang kirin, hûn hewce ne ku fîlteran rakin (ev dike sudo ./stand detach), berdin ping, paşê fîlteran saz bikin û dîsa biceribînin. Pirsgirêk ev e ku parzûn XDP_TX hem li ser ARP û hem jî ger stack derbasdar e
cîhên navan xdp-test karî ku navnîşana MAC 192.0.2.1 "jibîr" bike, ew ê nikaribe vê IP-yê çareser bike.

Formulkirina pirsgirêkê

Ka em biçin ser peywira diyarkirî: li ser XDP mekanîzmayek cookies SYN binivîsin.

lehiya SYN êrîşek DDoS ya populer dimîne, ku cewhera wê wiha ye. Dema ku têkiliyek tê saz kirin (destê TCP), server SYNek distîne, çavkaniyan ji bo pêwendiya pêşerojê veqetîne, bi pakêtek SYNACK bersiv dide û li benda ACK-ê dimîne. Êrîşkar bi tenê bi hezaran pakêtên SYN di çirkeyê de ji navnîşanên xapînok ji her mêvandar di botnetek pir-hezar-hêz de dişîne. Pêşkêşkar neçar e ku tavilê çavkaniyan bi hatina pakêtê veqetîne, lê piştî demek mezin wan berdide, wekî encamek, bîranîn an sînor qut dibin, girêdanên nû nayên qebûl kirin, û karûbar nayê peyda kirin.

Ger hûn çavkaniyan li gorî pakêta SYN venaqetînin, lê tenê bi pakêtek SYNACK bersiv bidin, wê hingê server çawa dikare fêm bike ku pakêta ACK ya ku paşê gihîştiye pakêtek SYN-ya ku nehatiye tomar kirin vedibêje? Beriya her tiştî, êrîşkar dikare ACK-yên sexte jî çêbike. Armanca cookie SYN ew e ku wê tê de kod bike seqnum Parametreyên pêwendiyê wekî hash navnîşan, port û guheztina xwê. Ger ACK karî bigihîje berî ku xwê were guheztin, hûn dikarin haş ji nû ve hesab bikin û bi acknum. Forge acknum êrîşkar nikane, ji ber ku xwê sirê dihewîne, û ji ber kanalek tixûbdar dê wext tune ku wê bi rê ve bibe.

Cookie SYN demek dirêj di kernel Linux-ê de hatî bicîh kirin û heke SYN pir zû û bi girseyî bigihîjin dikare bixweber jî were çalak kirin.

Bernameya perwerdehiyê li ser TCP handshake

TCP veguheztina daneyê wekî çemek byte peyda dike, mînakî, daxwazên HTTP li ser TCP têne şandin. Herik di pakêtan de perçe perçe tê veguheztin. Hemî pakêtên TCP alayên mentiqî û hejmarên rêza 32-bit hene:

  • Kombûna alayan rola pakêtek taybetî diyar dike. Ala SYN destnîşan dike ku ev pakêta yekem a şanderê li ser girêdanê ye. Ala ACK tê vê wateyê ku şander hemî daneyên pêwendiyê heya byte wergirtiye acknum. Paketek dikare çend alayan hebin û bi berhevdana wan jê re tê gotin, mînakî pakêtek SYNACK.

  • Jimareya rêzê (seqnum) ji bo bîta yekem a ku di vê pakêtê de tê şandin, guheztina di herika daneyê de diyar dike. Mînakî, heke di pakêta yekem a bi daneya X byte de ev hejmar N bûya, di pakêta din a bi daneya nû de ew ê N+X be. Di destpêka girêdanê de, her alî vê hejmarê bi korfelaqî hildibijêre.

  • Hejmara pejirandinê (acknum) - heman guheztina seqnum-ê ye, lê ew hejmara baytê ku tê veguheztin diyar nake, lê hejmara baytê yekem ji wergir, ku şander nedîtiye, diyar dike.

Di destpêka girêdanê de divê alî li hev bikin seqnum и acknum. Xerîdar bi xwe re pakêtek SYN dişîne seqnum = X. Pêşkêşkar bi pakêtek SYNACK bersiv dide, li ku derê wê tomar dike seqnum = Y û eşkere dike acknum = X + 1. Xerîdar bi pakêtek ACK bersivê dide SYNACK, li ku derê seqnum = X + 1, acknum = Y + 1. Piştî vê yekê, veguhestina daneyên rastîn dest pê dike.

Heke peer wergirtina pakêtê qebûl neke, TCP wê piştî demekê ji nû ve dişîne.

Çima çerezên SYN her gav nayên bikar anîn?

Pêşîn, heke SYNACK an ACK winda bibe, hûn ê li bendê bin ku ew dîsa were şandin - sazkirina girêdanê dê hêdî bibe. Ya duyemîn, di pakêta SYN de - û tenê di wê de! - hejmarek vebijark têne veguheztin ku bandorê li ser xebitandina pêvekê dikin. Bêyî bîranîna pakêtên SYN-ê, server bi vî rengî van vebijarkan paşguh dike dê wan di pakêtên din de neşîne. TCP dikare di vê rewşê de bixebite, lê bi kêmanî di qonaxa destpêkê de dê kalîteya pêwendiyê kêm bibe.

Ji perspektîfa pakêtan, bernameyek XDP divê jêrîn bike:

  • SYN bi SYNACK re bi cookie bersiv bide;
  • bi RST re bersivê bidin ACK (veqetandin);
  • pakêtên mayî biavêjin.

Pseudokoda algorîtmayê ligel parkirina pakêtê:

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

Yek (*) xalên ku hûn hewce ne ku hûn rewşa pergalê birêve bibin têne nîşankirin - di qonaxa yekem de hûn dikarin bêyî wan bikin bi tenê pêkanîna destanek TCP-ê bi hilberîna cookie SYN-ê re wekî seqnum.

Li cihê bûyerê (**), dema ku tabloyek me tune be, em ê pakêtê berdin.

Pêkanîna TCP handshake

Parvekirina pakêtê û verastkirina kodê

Em ê hewceyê strukturên sernavê torê bikin: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) û TCP (uapi/linux/tcp.h). Ji ber xeletiyên têkildar min nekarî ya paşîn girêbide atomic64_t, Diviyabû min pênaseyên pêwîst di kodê de kopî bikim.

Hemî fonksiyonên ku di C-yê de ji bo xwendinê têne ronî kirin divê di cîhê bangê de bêne xêz kirin, ji ber ku verastkerê eBPF di kernelê de paşvekêşanê qedexe dike, ango, bi rastî, lûp û bangên fonksiyonê.

#define INTERNAL static __attribute__((always_inline))

Macro LOG() çapkirinê di avakirina berdanê de asteng dike.

Bername veguhestina fonksiyonan e. Her yek pakêtek werdigire ku tê de sernavê asta têkildar tê ronî kirin, mînakî, process_ether() hêvî dike ku were dagirtin ether. Li ser bingeha encamên analîza zeviyê, fonksiyon dikare pakêtê derbasî astek bilindtir bike. Encama fonksiyonê çalakiya XDP ye. Heya nuha, rêvebirên SYN û ACK hemî pakêtan derbas dikin.

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

Ez bala we dikişînim ser kontrolên bi A û B hatine nîşankirin. Ger hûn A şîrove bikin, bername dê çêbibe, lê dema barkirinê dê xeletiyek verastkirinê hebe:

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!

Têla kilît invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Dema sêzdeh bît ji destpêka tamponê li derveyî pakêtê be rêyên îcrakirinê hene. Zehmet e ku meriv ji navnîşê fêm bike ka em qala kîjan rêzê dikin, lê hejmarek rêwerzek (12) û veqetandinek heye ku rêzikên koda çavkaniyê nîşan dide:

llvm-objdump -S xdp_filter.o | less

Di vê rewşê de ew li ser rêzê nîşan dide

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

ku eşkere dike ku pirsgirêk e ether. Dê her tim wiha be.

Bersiva SYN

Di vê qonaxê de armanc ew e ku pakêtek SYNACK-ya rast bi sabît biafirîne seqnum, ku dê di pêşerojê de ji hêla cookie SYN ve were guhertin. Hemû guhertin di nav de pêk tê process_tcp_syn() û deverên derdorê.

Verastkirina pakêtê

Pir ecêb e, li vir rêza herî berbiçav, an bêtir, şîroveya wê ye:

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

Dema nivîsandina guhertoya yekem a kodê, kernel 5.1 hate bikar anîn, ji bo verastkerê ku di navbera wê de ferqek hebû. data_end и (const void*)ctx->data_end. Di dema nivîsandinê de, kernel 5.3.1 ev pirsgirêk nebû. Mimkun e ku berhevkar ji qadekê cuda xwe bigihîne guhêrbarek herêmî. Morala çîrokê: dema ku hêlîn mezin e, hêsankirina kodê dikare bibe alîkar.

Piştre ji bo rûmeta verastker kontrolên dirêjahiya rûtîn hene; O MAX_CSUM_BYTES jêrîn.

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

Vekirina pakêtê

Lenet ji te seqnum и acknum, ACK saz bike (SYN jixwe hatiye danîn):

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

Portên TCP, navnîşana IP û navnîşanên MAC biguherînin. Pirtûkxaneya standard ji bernameya XDP nayê gihîştin, ji ber vê yekê memcpy() - makroyek ku xwerûya Clang vedişêre.

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

Ji nû ve hesabkirina kontrolê

Ji kontrolkirina IPv4 û TCP-ê re pêdivî ye ku hemî peyvên 16-bit di sernivîsan de werin zêdekirin, û mezinahiya sernivîsan di nav wan de têne nivîsandin, ango di dema berhevkirinê de nenas in. Ev pirsgirêkek e ji ber ku verastker dê lûleya normal berbi guhêrbara sînor venegere. Lê mezinahiya sernivîsan sînorkirî ye: her yek heya 64 byte. Hûn dikarin bi hejmarek dubareyên sabît xêzek çêbikin, ku dikare zû biqede.

Têbînî ku heye RFC 1624 li ser ka meriv çawa qismî jimareya kontrolê ji nû ve hesab dike heke tenê peyvên sabît ên pakêtan werin guheztin. Lêbelê, rêbaz ne gerdûnî ye, û pêkanîna wê dê dijwartir be.

Fonksiyona hesabkirina jimareya kontrolê:

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

Herçi size ji hêla koda bangê ve hatî verast kirin, şerta derketina duyemîn pêdivî ye ku verastker karibe temambûna lûkê îspat bike.

Ji bo peyvên 32-bit, guhertoyek hêsantir tête bicîh kirin:

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

Bi rastî ji nû ve hesabkirina kontrolê û şandina pakêtê paşde:

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;

function carry() Li gorî RFC 32, ji berhevokek 16-bitî ya peyvên 791-bit kontrolek çêdike.

Verastkirina destên TCP

Parzûn bi rêkûpêk pêwendiyê saz dike netcat, ACK-ya dawîn winda kir, ku Linux bi pakêtek RST bersiv da, ji ber ku stûna torê SYN negirt - ew veguherî SYNACK û paşve hat şandin - û ji hêla OS-ê ve pakêtek hat ku ne girêdayî vekirî ye. girêdan.

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

Girîng e ku meriv bi serîlêdanên bêkêmasî ve were kontrol kirin û çavdêrî bike tcpdump li ser xdp-remote ji ber ku, bo nimûne, hping3 bersivê nade kontrolên çewt.

Ji nêrînek XDP-ê, verastkirin bi xwe piçûk e. Algorîtmaya hesabkirinê primitive e û îhtîmal e ku ji êrîşkarek sofîstîke re xeternak e. Mînakî, kernel Linux-ê SipHash-a krîptografî bikar tîne, lê pêkanîna wê ji bo XDP eşkere ji çarçoweya vê gotarê der e.

Ji bo TODO-yên nû yên bi pêwendiya derve ve girêdayî têne destnîşan kirin:

  • Bernameya XDP nikare hilîne cookie_seed (beşê veşartî ya xwê) di guhêrbarek gerdûnî de, hûn hewce ne ku di kernelê de hilanîn, nirxa wê bi periyodîk ji jeneratorek pêbawer were nûve kirin.

  • Ger cookie SYN di pakêta ACK-ê de li hev bike, hûn ne hewce ne ku peyamek çap bikin, lê IP-ya muwekîlê pejirandî bi bîr bînin da ku hûn pakêtan ji wê bidomînin.

Verastkirina xerîdar a rewa:

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

Têketin nîşan didin ku kontrol derbas bûye (flags=0x2 - ev SYN e, flags=0x10 ACK ye):

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

Digel ku navnîşek IP-yên pejirandî tune, dê ji lehiya SYN bixwe parastin tune be, lê li vir berteka li ser lehiyek ACK-ê ye ku bi fermana jêrîn hatî destpêkirin:

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

Têketinên têketinê:

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

encamê

Carinan eBPF bi gelemperî û XDP bi taybetî ji wekî platformek pêşkeftinê bêtir wekî amûrek rêveberê pêşkeftî têne pêşkêş kirin. Bi rastî, XDP amûrek e ji bo mudaxelekirina pêvajoya paketan ji hêla kernelê ve, û ne alternatîfek stûna kernelê ye, mîna DPDK û vebijarkên din ên dorpêçkirina kernelê. Ji hêla din ve, XDP dihêle hûn mantiqek pir tevlihev bicîh bikin, ku, di heman demê de, nûvekirina hêsan e ku di pêvajoya seyrûseferê de bê navber were nûve kirin. Verastker bi xwe pirsgirêkên mezin çênake, ez ê vê yekê ji bo beşên koda cîhê bikarhêner red nekim.

Di beşa duyemîn de, heke mijar balkêş be, em ê tabloya xerîdarên pejirandî û veqetandî temam bikin, jimarvanan bicîh bikin û ji bo birêvebirina parzûnê amûrek cîhê bikarhêner binivîsin.

References:

Source: www.habr.com

Add a comment