Rydym yn ysgrifennu amddiffyniad yn erbyn ymosodiadau DDoS ar XDP. Rhan niwclear

Mae technoleg eXpress Data Path (XDP) yn caniatáu prosesu traffig yn fympwyol ar ryngwynebau Linux cyn i becynnau fynd i mewn i'r pentwr rhwydwaith cnewyllyn. Cymhwyso XDP - amddiffyniad yn erbyn ymosodiadau DDoS (CloudFlare), hidlwyr cymhleth, casglu ystadegau (Netflix). Mae rhaglenni XDP yn cael eu gweithredu gan beiriant rhithwir eBPF, ac felly mae ganddynt gyfyngiadau ar eu cod a'r swyddogaethau cnewyllyn sydd ar gael, yn dibynnu ar y math o hidlydd.

Bwriad yr erthygl yw gwneud iawn am ddiffygion nifer o ddeunyddiau ar XDP. Yn gyntaf, maent yn darparu cod parod sy'n osgoi nodweddion XDP ar unwaith: wedi'i baratoi ar gyfer dilysu neu'n rhy syml i achosi problemau. Pan geisiwch ysgrifennu eich cod eich hun o'r dechrau yn ddiweddarach, nid oes unrhyw ddealltwriaeth o beth i'w wneud â gwallau nodweddiadol. Yn ail, nid yw'n ymdrin â ffyrdd o brofi XDP yn lleol heb VM a chaledwedd, er gwaethaf y ffaith bod ganddynt eu peryglon eu hunain. Mae'r testun wedi'i fwriadu ar gyfer rhaglenwyr sy'n gyfarwydd â rhwydweithiau a Linux sydd â diddordeb mewn XDP ac eBPF.

Yn y rhan hon, byddwn yn deall yn fanwl sut mae'r hidlydd XDP yn cael ei ymgynnull a sut i'w brofi, yna byddwn yn ysgrifennu fersiwn syml o'r mecanwaith cwcis SYN adnabyddus ar lefel prosesu pecynnau. Hyd nes i ni ffurfio "rhestr wen"
cleientiaid wedi'u dilysu, cadw cownteri a rheoli'r hidlydd - digon o foncyffion.

Byddwn yn ysgrifennu yn C - nid yw hyn yn ffasiynol, ond yn ymarferol. Mae'r holl god ar gael ar GitHub yn y ddolen ar y diwedd ac mae wedi'i rannu'n ymrwymiadau yn ôl y camau a ddisgrifir yn yr erthygl.

Ymwadiad. Yn ystod yr erthygl, bydd datrysiad bach ar gyfer gwrthyrru ymosodiadau DDoS yn cael ei ddatblygu, oherwydd mae hon yn dasg realistig i XDP a fy ardal i. Fodd bynnag, y prif nod yw deall y dechnoleg, nid yw hwn yn ganllaw i greu amddiffyniad parod. Nid yw'r cod tiwtorial wedi'i optimeiddio ac mae'n hepgor rhai arlliwiau.

Trosolwg Cryno o'r XDP

Nodaf y pwyntiau allweddol yn unig er mwyn peidio â dyblygu'r ddogfennaeth a'r erthyglau presennol.

Felly, mae'r cod hidlo yn cael ei lwytho i mewn i'r cnewyllyn. Mae'r hidlydd yn cael ei basio pecynnau sy'n dod i mewn. O ganlyniad, rhaid i'r hidlydd wneud penderfyniad: i drosglwyddo'r pecyn i'r cnewyllyn (XDP_PASS), pecyn gollwng (XDP_DROP) neu ei anfon yn ôl (XDP_TX). Gall yr hidlydd newid y pecyn, mae hyn yn arbennig o wir am XDP_TX. Gallwch hefyd chwalu'r rhaglen (XDP_ABORTED) a gollwng y pecyn, ond mae hwn yn gyfatebol assert(0) - ar gyfer dadfygio.

Виртуальная машина eBPF (extended Berkley Packet Filter) специально сделана простой, дабы ядро могло проверить, что код не зацикливается и не повреждает чужую память. Совокупные ограничения и проверки:

  • Gwaherddir dolenni (neidiau yn ôl).
  • Mae pentwr ar gyfer data, ond dim swyddogaethau (rhaid i holl swyddogaethau C fod wedi'u mewnlinio).
  • Gwaherddir mynediad i'r cof y tu allan i'r pentwr a byffer pecyn.
  • Mae maint y cod yn gyfyngedig, ond yn ymarferol nid yw hyn yn arwyddocaol iawn.
  • Dim ond swyddogaethau cnewyllyn arbennig (cynorthwywyr eBPF) a ganiateir.

Mae datblygu a gosod hidlydd yn edrych fel hyn:

  1. cod ffynhonnell (ee. kernel.c) yn llunio i wrthwynebu (kernel.o) ar gyfer pensaernïaeth peiriannau rhithwir eBPF. O fis Hydref 2019 ymlaen, mae llunio i eBPF yn cael ei gefnogi gan Clang a'i addo yn GCC 10.1.
  2. Os oes galwadau i strwythurau cnewyllyn yn y cod gwrthrych hwn (er enghraifft, i dablau a chownteri), yn lle eu IDau mae sero, hynny yw, ni ellir gweithredu cod o'r fath. Cyn eu llwytho i mewn i'r cnewyllyn, rhaid disodli'r seroau hyn â rhifau adnabod gwrthrychau penodol a grëwyd trwy alwadau cnewyllyn (cyswllt y cod). Gallwch wneud hyn gyda chyfleustodau allanol, neu gallwch ysgrifennu rhaglen a fydd yn cysylltu a llwytho hidlydd penodol.
  3. Mae'r cnewyllyn yn gwirio'r rhaglen sy'n cael ei llwytho. Mae'n gwirio am absenoldeb cylchoedd a pheidio â gadael y pecyn a ffiniau stac. Os na all y dilysydd brofi bod y cod yn gywir, caiff y rhaglen ei gwrthod - rhaid i un allu ei blesio.
  4. Ar ôl dilysu llwyddiannus, mae'r cnewyllyn yn llunio cod gwrthrych pensaernïaeth eBPF yn god peiriant pensaernïaeth system (mewn union bryd).
  5. Mae'r rhaglen ynghlwm wrth y rhyngwyneb ac yn dechrau prosesu pecynnau.

Gan fod XDP yn rhedeg yn y cnewyllyn, mae dadfygio yn cael ei wneud gan logiau olrhain ac, mewn gwirionedd, gan becynnau y mae'r rhaglen yn eu hidlo neu'n eu cynhyrchu. Fodd bynnag, mae eBPF yn cadw'r cod wedi'i lawrlwytho yn ddiogel ar gyfer y system, felly gallwch chi arbrofi gyda XDP yn union ar eich Linux lleol.

Paratoi'r Amgylchedd

Cynulliad

Ni all Clang gyhoeddi cod gwrthrych yn uniongyrchol ar gyfer pensaernïaeth eBPF, felly mae'r broses yn cynnwys dau gam:

  1. Llunio cod C i god byte LLVM (clang -emit-llvm).
  2. Trosi beitcode i god gwrthrych eBPF (llc -march=bpf -filetype=obj).

Wrth ysgrifennu hidlydd, bydd cwpl o ffeiliau gyda swyddogaethau ategol a macros yn dod yn ddefnyddiol o brofion cnewyllyn. Mae'n bwysig eu bod yn cyfateb i'r fersiwn cnewyllyn (KVER). Lawrlwythwch nhw i 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 ar gyfer Arch Linux (cnewyllyn 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 yn cynnwys y llwybr i benawdau'r cnewyllyn, ARCH - pensaernïaeth system. Gall llwybrau ac offer amrywio ychydig rhwng dosbarthiadau.

Enghraifft o wahaniaeth ar gyfer Debian 10 (cnewyllyn 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 cynnwys cyfeiriadur gyda phenawdau ategol a sawl cyfeiriadur gyda phenawdau cnewyllyn. Symbol __KERNEL__ означает, что заголовки UAPI (userspace API) определяются для кода ядра, так как фильтр выполняется в ядре.

Gellir analluogi amddiffyniad stac (-fno-stack-protector) oherwydd bod dilysydd cod yr eBPF yn gwirio ffiniau nad ydynt allan o stac beth bynnag. Dylech alluogi optimeiddiadau ar unwaith, oherwydd mae maint y cod byte eBPF yn gyfyngedig.

Gadewch i ni ddechrau gyda hidlydd sy'n pasio pob pecyn ac yn gwneud dim byd:

#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îm make yn casglu xdp_filter.o. Ble gallwch chi ei brofi nawr?

stondin prawf

Dylai'r stondin gynnwys dau ryngwyneb: y bydd hidlydd arno ac y bydd pecynnau'n cael eu hanfon ohono. Rhaid i'r rhain fod yn ddyfeisiau Linux llawn gyda'u IPs eu hunain er mwyn gwirio sut mae cymwysiadau rheolaidd yn gweithio gyda'n hidlydd.

Mae dyfeisiau fel veth (Veth Ethernet) yn addas i ni: maen nhw'n bâr o ryngwynebau rhwydwaith rhithwir “wedi'u cysylltu” yn uniongyrchol â'i gilydd. Gallwch chi eu creu fel hyn (yn yr adran hon, pob gorchymyn ip perfformio o root):

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

Yma xdp-remote и xdp-local - enwau dyfeisiau. Ar xdp-local (192.0.2.1/24) bydd hidlydd yn cael ei atodi, gyda xdp-remote (192.0.2.2/24) bydd traffig sy'n dod i mewn yn cael ei anfon. Fodd bynnag, mae yna broblem: mae'r rhyngwynebau ar yr un peiriant, ac ni fydd Linux yn anfon traffig i un ohonynt trwy'r llall. Gallwch chi ei ddatrys gyda rheolau anodd iptables, ond bydd yn rhaid iddynt newid pecynnau, sy'n anghyfleus wrth ddadfygio. Mae'n well defnyddio gofodau enwau rhwydwaith (gofodau enwau rhwydwaith, rhwydi pellach).

Mae gofod enwau'r rhwydwaith yn cynnwys set o ryngwynebau, tablau llwybro, a rheolau NetFilter sydd wedi'u hynysu oddi wrth wrthrychau tebyg mewn rhwydi eraill. Mae pob proses yn rhedeg mewn rhyw ofod enw, a dim ond gwrthrychau'r rhwydi hwn sydd ar gael iddi. Yn ddiofyn, mae gan y system un gofod enw rhwydwaith ar gyfer pob gwrthrych, felly gallwch chi weithio ar Linux a pheidio â gwybod am netns.

Gadewch i ni greu gofod enw newydd xdp-test a symud yno xdp-remote.

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

Yna y broses yn rhedeg i mewn xdp-test, ni fydd yn "gweld" xdp-local (bydd yn aros mewn netns yn ddiofyn) ac wrth anfon pecyn i 192.0.2.1 yn ei basio drwodd xdp-remote, oherwydd dyna'r unig ryngwyneb yn 192.0.2.0/24 sydd ar gael i'r broses hon. Mae hyn hefyd yn gweithio i'r gwrthwyneb.

Wrth symud rhwng netns, mae'r rhyngwyneb yn mynd i lawr ac yn colli'r cyfeiriad. I sefydlu rhyngwyneb mewn netns, mae angen i chi redeg ip ... yn y gofod enw gorchymyn hwn 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

Fel y gallwch weld, nid yw hyn yn wahanol i'r gosodiad xdp-local yn y gofod enw diofyn:

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

Os rhedeg tcpdump -tnevi xdp-local, gallwch weld bod pecynnau a anfonwyd o xdp-test, yn cael eu danfon i'r rhyngwyneb hwn:

ip netns exec xdp-test   ping 192.0.2.1

Mae'n gyfleus rhedeg cragen i mewn xdp-test. Mae gan y storfa sgript sy'n awtomeiddio gwaith gyda'r stondin, er enghraifft, gallwch chi osod y stondin gyda'r gorchymyn sudo ./stand up a gwared ef sudo ./stand down.

olrhain

Mae'r hidlydd ynghlwm wrth y ddyfais fel hyn:

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

Allwedd -force angen cysylltu rhaglen newydd os oes un arall eisoes yn gysylltiedig. Nid yw "Dim newyddion yn newyddion da" yn ymwneud â'r gorchymyn hwn, mae'r allbwn yn swmpus beth bynnag. nodi verbose dewisol, ond gydag ef mae adroddiad am waith y dilysydd cod gyda'r rhestr cydosodwr yn ymddangos:

Verifier analysis:

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

Datgysylltwch y rhaglen o'r rhyngwyneb:

ip link set dev xdp-local xdp off

Yn y sgript, dyma'r gorchmynion sudo ./stand attach и sudo ./stand detach.

Trwy rwymo'r hidlydd, gallwch chi wneud yn siŵr hynny ping yn parhau i weithio, ond a yw'r rhaglen yn gweithio? Gadewch i ni ychwanegu logos. Swyddogaeth bpf_trace_printk() yn debyg i printf(), ond dim ond yn cefnogi hyd at dair dadl ar wahân i'r patrwm, a rhestr gyfyngedig o fanylebwyr. Macro bpf_printk() yn symleiddio'r alwad.

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

Mae'r allbwn yn mynd i'r sianel olrhain cnewyllyn, y mae angen ei alluogi:

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

Gweld llif neges:

cat /sys/kernel/debug/tracing/trace_pipe

Mae'r ddau dîm hyn yn gwneud galwad sudo ./stand log.

Dylai Ping nawr gynhyrchu negeseuon fel hyn ynddo:

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

Os edrychwch yn ofalus ar allbwn y dilysydd, gallwch sylwi ar gyfrifiadau rhyfedd:

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

Y ffaith yw nad oes gan raglenni eBPF adran ddata, felly yr unig ffordd i amgodio'r llinyn fformat yw dadleuon uniongyrchol y gorchmynion VM:

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

Am y rheswm hwn, mae allbwn dadfygio yn chwyddo'r cod canlyniadol yn fawr.

Anfon Pecynnau XDP

Gadewch i ni newid yr hidlydd: gadewch iddo anfon yr holl becynnau sy'n dod i mewn yn ôl. Mae hyn yn anghywir o safbwynt rhwydwaith, gan y byddai angen newid y cyfeiriadau yn y penawdau, ond nawr mae'r gwaith mewn egwyddor yn bwysig.

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

Lansio tcpdump ar xdp-remote. Dylai ddangos Cais Echo ICMP sy'n mynd allan ac yn dod i mewn a stopio dangos ICMP Echo Reply. Ond nid yw'n dangos. Yn troi allan i weithio XDP_TX yn y rhaglen ar gyfer xdp-local angenrheidioli baru rhyngwyneb xdp-remote neilltuwyd rhaglen hefyd, hyd yn oed os oedd yn wag, a chodwyd hi.

Sut oeddwn i'n gwybod?

Olrhain llwybr pecyn yn y cnewyllyn mae'r mecanwaith digwyddiadau perf yn caniatáu, gyda llaw, ddefnyddio'r un peiriant rhithwir, hynny yw, defnyddir eBPF ar gyfer dadosod ag eBPF.

Rhaid i ti wneud daioni allan o ddrwg, oherwydd nid oes dim arall i'w wneud ohono.

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

Beth yw cod 6?

$ errno 6
ENXIO 6 No such device or address

Swyddogaeth veth_xdp_flush_bq() yn cael cod gwall o veth_xdp_xmit(), lle chwilio gan ENXIO a dod o hyd i sylw.

Adfer yr hidlydd lleiaf (XDP_PASS) mewn ffeil xdp_dummy.c, ei ychwanegu at y Makefile, rhwymo i xdp-remote:

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

Nawr tcpdump yn dangos yr hyn a ddisgwylir:

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

Os mai dim ond ARP a ddangosir yn lle hynny, mae angen i chi gael gwared ar yr hidlwyr (mae hyn yn gwneud sudo ./stand detach), gadewch ping, yna gosodwch hidlwyr a cheisiwch eto. Y broblem yw bod yr hidlydd XDP_TX действует и на ARP, и если стек
gofodau enwau xdp-test llwyddo i "anghofio" y cyfeiriad MAC 192.0.2.1, ni fydd yn gallu datrys yr IP hwn.

Datganiad o'r broblem

Gadewch i ni symud ymlaen at y dasg a nodir: ysgrifennu mecanwaith cwci SYN ar XDP.

Hyd yn hyn, mae llifogydd SYN yn parhau i fod yn ymosodiad DDoS poblogaidd, a'i hanfod fel a ganlyn. Pan sefydlir cysylltiad (ysgwyd llaw TCP), mae'r gweinydd yn derbyn SYN, yn dyrannu adnoddau ar gyfer cysylltiad yn y dyfodol, yn ymateb gyda phecyn SYNACK, ac yn aros am ACK. Mae'r ymosodwr yn syml yn anfon pecynnau SYN o gyfeiriadau ffug yn y swm o filoedd yr eiliad o bob gwesteiwr mewn botnet aml-fil. Mae'r gweinydd yn cael ei orfodi i ddyrannu adnoddau yn syth ar ôl i'r pecyn gyrraedd, ond yn ei ryddhau ar ôl cyfnod hir, o ganlyniad, mae cof neu derfynau wedi dod i ben, ni dderbynnir cysylltiadau newydd, nid yw'r gwasanaeth ar gael.

Os nad ydych yn dyrannu adnoddau ar y pecyn SYN, ond dim ond yn ymateb gyda phecyn SYNACK, yna sut gall y gweinydd ddeall bod y pecyn ACK a ddaeth yn ddiweddarach yn perthyn i'r pecyn SYN na chafodd ei gadw? Wedi'r cyfan, gall ymosodwr hefyd gynhyrchu ACKs ffug. Hanfod cwci SYN yw amgodio i mewn seqnum paramedrau cysylltiad fel hash o gyfeiriadau, porthladdoedd a newid halen. Os llwyddodd yr ACK i gyrraedd cyn y newid halen, gallwch gyfrifo'r hash eto a'i gymharu â acknum. ffug acknum ni all yr ymosodwr, gan fod yr halen yn cynnwys y gyfrinach, ac ni fydd ganddo amser i ddidoli trwyddo oherwydd y sianel gyfyngedig.

Mae cwcis SYN wedi cael eu gweithredu yn y cnewyllyn Linux ers amser maith a gellir eu galluogi hyd yn oed yn awtomatig os yw SYNs yn cyrraedd yn rhy gyflym ac mewn swmp.

Rhaglen addysgol ar ysgwyd llaw TCP

Mae TCP yn darparu trosglwyddiad data fel llif o beit, er enghraifft, trosglwyddir ceisiadau HTTP dros TCP. Mae'r ffrwd yn cael ei throsglwyddo fesul darn mewn pecynnau. Mae gan bob pecyn TCP fflagiau rhesymegol a rhifau dilyniant 32-did:

  • Mae'r cyfuniad o fflagiau yn diffinio rôl pecyn penodol. Mae baner SYN yn golygu mai hwn yw pecyn cyntaf yr anfonwr ar y cysylltiad. Mae baner ACK yn golygu bod yr anfonwr wedi derbyn yr holl ddata cysylltiad hyd at beit. acknum. Gall fod gan becyn sawl baner ac fe'i enwir ar ôl eu cyfuniad, er enghraifft, pecyn SYNACK.

  • Mae rhif dilyniant (sequnum) yn pennu'r gwrthbwyso yn y ffrwd data ar gyfer y beit cyntaf a anfonir yn y pecyn hwn. Er enghraifft, os mai N oedd y rhif hwn yn y pecyn cyntaf gyda X beit o ddata, yn y pecyn nesaf gyda data newydd bydd yn N+X. Ar ddechrau'r alwad, mae pob ochr yn dewis y rhif hwn ar hap.

  • Rhif cydnabod (acnum) - yr un gwrthbwyso â seqnum, ond nid yw'n pennu nifer y beit a drosglwyddir, ond nifer y beit cyntaf gan y derbynnydd, na welodd yr anfonwr.

Ar ddechrau'r cysylltiad, rhaid i'r partïon gytuno seqnum и acknum. Mae'r cleient yn anfon pecyn SYN gyda'i seqnum = X. Mae'r gweinydd yn ymateb gyda phecyn SYNACK, lle mae'n ysgrifennu ei becyn ei hun seqnum = Y ac yn amlygu acknum = X + 1. Клиент на SYNACK отвечает ACK-пакетом, где seqnum = X + 1, acknum = Y + 1. Ar ôl hynny, mae'r trosglwyddiad data gwirioneddol yn dechrau.

Os na fydd y cydgysylltydd yn cydnabod derbyn y pecyn, mae TCP yn ei ail-anfon erbyn terfyn amser.

Pam na ddefnyddir cwcis SYN bob amser?

Yn gyntaf, os collir SYNACK neu ACK, bydd yn rhaid i chi aros am ail-anfon - mae'r sefydliad cysylltu yn arafu. Yn ail, yn y pecyn SYN - a dim ond ynddo! - trosglwyddir nifer o opsiynau sy'n effeithio ar weithrediad pellach y cysylltiad. Heb gofio pecynnau SYN sy'n dod i mewn, mae'r gweinydd felly yn anwybyddu'r opsiynau hyn, yn y pecynnau canlynol ni fydd y cleient yn eu hanfon mwyach. Gall TCP weithio yn yr achos hwn, ond o leiaf yn y cam cychwynnol, bydd ansawdd y cysylltiad yn gostwng.

O ran pecynnau, dylai rhaglen XDP wneud y canlynol:

  • ymateb i SYN gyda SYNACK gyda cwci;
  • ateb ACK gyda RST (torri'r cysylltiad);
  • gollwng pecynnau eraill.

Ffuggod yr algorithm ynghyd â dosrannu pecynnau:

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

Un (*) mae'r pwyntiau lle mae angen i chi reoli cyflwr y system wedi'u nodi - yn y cam cyntaf, gallwch wneud hebddynt trwy weithredu ysgwyd llaw TCP gyda chynhyrchu cwci SYN fel seqnum.

Ar safle (**), er nad oes gennym fwrdd, byddwn yn hepgor y pecyn.

Gweithredu ysgwyd llaw TCP

Dosrannu pecyn a dilysu cod

Mae angen strwythurau pennawd rhwydwaith arnom: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) a TCP (uapi/linux/tcp.h). Yr un olaf ni allwn gysylltu oherwydd gwallau yn ymwneud â atomic64_t, roedd yn rhaid i mi gopïo'r diffiniadau angenrheidiol i'r cod.

Rhaid i'r holl swyddogaethau sy'n cael eu gwahaniaethu yn C ar gyfer darllenadwyedd gael eu halinio yn y safle galwadau, gan fod y dilysydd eBPF yn y cnewyllyn yn gwahardd neidiau cefn, hynny yw, mewn gwirionedd, dolenni a galwadau ffwythiant.

#define INTERNAL static __attribute__((always_inline))

Macro LOG() yn analluogi argraffu mewn adeilad rhyddhau.

Mae'r rhaglen yn biblinell o swyddogaethau. Mae pob un yn derbyn pecyn lle mae pennawd o'r lefel gyfatebol yn cael ei amlygu, er enghraifft, process_ether() yn aros i gael ei lenwi ether. Yn seiliedig ar ganlyniadau dadansoddiad maes, gall y swyddogaeth drosglwyddo'r pecyn i lefel uwch. Canlyniad y swyddogaeth yw gweithred XDP. Tra bod y trinwyr SYN ac ACK yn gadael pob pecyn drwodd.

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

Rwy'n talu sylw i'r sieciau sydd wedi'u marcio A a B. Os gwnewch sylwadau ar A, bydd y rhaglen yn adeiladu, ond bydd gwall dilysu wrth lwytho:

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!

Ключевая строка invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): mae yna lwybrau gweithredu pan fydd y trydydd beit ar ddeg o ddechrau'r byffer y tu allan i'r pecyn. Mae'n anodd dweud o'r rhestr pa linell rydyn ni'n siarad amdani, ond mae yna rif cyfarwyddyd (12) a dadosodwr sy'n dangos llinellau'r cod ffynhonnell:

llvm-objdump -S xdp_filter.o | less

Yn yr achos hwn, mae'n pwyntio at y llinell

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

sy'n ei gwneud yn glir bod y broblem ether. Byddai felly bob amser.

Ymateb i SYN

Y nod ar hyn o bryd yw cynhyrchu pecyn SYNACK cywir gyda phecyn sefydlog seqnum, a fydd yn cael ei ddisodli gan gwci SYN yn y dyfodol. Mae pob newid yn digwydd yn process_tcp_syn() a'r amgylchoedd.

Gwirio'r pecyn

Yn rhyfedd ddigon, dyma’r llinell fwyaf rhyfeddol, neu yn hytrach, sylw iddi:

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

Wrth ysgrifennu'r fersiwn gyntaf o'r cod, defnyddiwyd y cnewyllyn 5.1, ac ar gyfer y dilysydd roedd gwahaniaeth rhwng data_end и (const void*)ctx->data_end. Ar adeg ysgrifennu, nid oedd gan y cnewyllyn 5.3.1 y broblem hon. Efallai bod y casglwr yn cyrchu newidyn lleol yn wahanol i faes. Moesol - ar nythu mawr, gall symleiddio'r cod helpu.

Gwiriadau rheolaidd pellach o hydoedd er gogoniant y gwiriwr; O MAX_CSUM_BYTES isod.

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

Lledaeniad pecyn

Rydyn ni'n llenwi seqnum и acknum, gosod ACK (SYN eisoes wedi'i osod):

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

Cyfnewid porthladdoedd TCP, cyfeiriadau IP a MAC. Nid yw'r llyfrgell safonol ar gael o'r rhaglen XDP, felly memcpy() — macro sy'n cuddio'r cynhenid ​​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);

Ailgyfrifo siec

Mae gwiriadau IPv4 a TCP yn gofyn am ychwanegu'r holl eiriau 16-bit yn y penawdau, ac mae maint y penawdau wedi'i ysgrifennu ynddynt, hynny yw, nid yw'n hysbys ar adeg eu llunio. Mae hyn yn broblem oherwydd ni fydd y dilysydd yn hepgor y ddolen arferol tan y newidyn terfyn. Ond mae maint y penawdau yn gyfyngedig: hyd at 64 beit yr un. Gallwch wneud dolen gyda nifer sefydlog o iteriadau, a all ddod i ben yn gynnar.

Sylwaf fod yna RFC 1624 sut i ailgyfrifo'r siec yn rhannol os mai dim ond geiriau sefydlog y pecynnau sy'n cael eu newid. Fodd bynnag, nid yw'r dull yn gyffredinol, a byddai'n anoddach cynnal y gweithrediad.

Swyddogaeth cyfrifo siec:

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

Er size wedi'i wirio gan y cod galw, mae angen yr ail gyflwr ymadael fel y gall y dilysydd brofi diwedd y ddolen.

Для 32-битных слов реализована более простая версия:

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

Mewn gwirionedd ailgyfrifo'r sieciau ac anfon y pecyn yn ôl:

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;

Swyddogaeth carry() yn gwneud siec allan o swm 32-did o eiriau 16-did, yn ôl RFC 791.

Gwiriad ysgwyd llaw TCP

Mae'r hidlydd yn sefydlu cysylltiad â netcat, gan sgipio'r ACK terfynol, yr ymatebodd Linux iddo gyda phecyn RST, gan nad oedd y pentwr rhwydwaith yn derbyn SYN - cafodd ei drawsnewid i SYNACK a'i anfon yn ôl - ac o safbwynt yr OS, cyrhaeddodd pecyn nad oedd yn yn ymwneud â chysylltiadau agored.

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

Mae'n bwysig gwirio gyda cheisiadau llawn ac arsylwi tcpdump ar xdp-remote oherwydd, er enghraifft, hping3 ddim yn ymateb i wiriadau anghywir.

O safbwynt XDP, mae'r siec ei hun yn ddibwys. Mae'r algorithm cyfrifo yn gyntefig ac yn debygol o fod yn agored i ymosodwr soffistigedig. Mae'r cnewyllyn Linux, er enghraifft, yn defnyddio'r cryptograffig SipHash, ond mae ei weithrediad ar gyfer XDP yn amlwg y tu hwnt i gwmpas yr erthygl hon.

Ymddangos ar gyfer TODOs newydd yn ymwneud â rhyngweithio allanol:

  • Ni all rhaglen XDP storio cookie_seed (rhan gyfrinachol yr halen) mewn newidyn byd-eang, mae angen storfa gnewyllyn arnoch y bydd ei werth yn cael ei ddiweddaru o bryd i'w gilydd o gynhyrchydd dibynadwy.

  • Os yw'r cwci SYN yn y pecyn ACK yn cyfateb, nid oes angen i chi argraffu neges, ond cofiwch IP y cleient sydd wedi'i ddilysu er mwyn hepgor pecynnau ymhellach ohono.

Dilysiad gan gleient cyfreithlon:

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

Roedd y logiau'n cofnodi taith y siec (flags=0x2 — это SYN, flags=0x10 yn 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

Cyn belled nad oes rhestr o IPs wedi'u dilysu, ni fydd unrhyw amddiffyniad rhag llifogydd SYN ei hun, ond dyma'r ymateb i'r llifogydd ACK a lansiwyd gan y gorchymyn hwn:

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

Cofnodion log:

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

Casgliad

Weithiau mae eBPF yn gyffredinol a XDP yn arbennig yn cael eu cyflwyno fel mwy o arf gweinyddwr uwch na llwyfan datblygu. Yn wir, mae XDP yn offeryn ar gyfer ymyrryd â phrosesu pecynnau cnewyllyn, ac nid yn ddewis arall i'r pentwr cnewyllyn, fel DPDK ac opsiynau ffordd osgoi cnewyllyn eraill. Ar y llaw arall, mae XDP yn caniatáu ichi weithredu rhesymeg eithaf cymhleth, sydd, ar ben hynny, yn hawdd ei diweddaru heb oedi wrth brosesu traffig. Nid yw'r dilysydd yn creu problemau mawr, yn bersonol ni fyddwn yn gwrthod o'r fath ar gyfer rhannau o'r cod userspace.

Yn yr ail ran, os yw'r pwnc yn ddiddorol, byddwn yn cwblhau'r tabl o gleientiaid wedi'u dilysu ac yn torri cysylltiadau, yn gweithredu cownteri ac yn ysgrifennu cyfleustodau gofod defnyddiwr i reoli'r hidlydd.

Cyfeiriadau:

Ffynhonnell: hab.com

Ychwanegu sylw