Táimid ag scríobh cosaint i gcoinne ionsaithe DDoS ar XDP. Cuid núicléach

Ceadaíonn teicneolaíocht eXpress Data Path (XDP) próiseáil tráchta randamach a dhéanamh ar chomhéadain Linux sula dtéann na paicéid isteach sa chruach líonra eithne. Cur i bhfeidhm XDP - cosaint i gcoinne ionsaithe DDoS (CloudFlare), scagairí casta, bailiú staitisticí (Netflix). Déanann an meaisín fíorúil eBPF cláir XDP a fhorghníomhú, agus mar sin tá srianta acu ar a gcód agus ar na feidhmeanna eithne atá ar fáil ag brath ar an gcineál scagaire.

Tá an t-alt beartaithe chun easnaimh na n-ábhar iomadúla ar XDP a líonadh. Ar an gcéad dul síos, cuireann siad cód réamhdhéanta ar fáil a sheachnaíonn gnéithe XDP láithreach: tá sé réidh le haghaidh fíoraithe nó tá sé ró-simplí chun fadhbanna a chruthú. Nuair a dhéanann tú iarracht ansin do chód a scríobh ón tús, níl aon smaoineamh agat cad a dhéanfaidh tú le gnáthearráidí. Ar an dara dul síos, níl bealaí chun XDP a thástáil go háitiúil gan VM agus crua-earraí clúdaithe, in ainneoin go bhfuil a gcuid gaistí féin acu. Tá an téacs dírithe ar ríomhchláraitheoirí atá eolach ar líonrú agus Linux a bhfuil suim acu i XDP agus eBPF.

Sa chuid seo, tuigfimid go mion conas a chuirtear an scagaire XDP le chéile agus conas é a thástáil, ansin scríobhfaimid leagan simplí den mheicníocht fianáin SYN atá ar eolas go maith ag an leibhéal próiseála paicéid. Ní chruthóimid “liosta bán” fós
cliaint fíoraithe, coinnigh cuntair agus bainistigh an scagaire - dóthain logs.

Scríobhfaimid i C - níl sé faiseanta, ach tá sé praiticiúil. Tá gach cód ar fáil ar GitHub tríd an nasc ag an deireadh agus tá sé roinnte ina gealltanais de réir na gcéimeanna a thuairiscítear san alt.

Séanadh. Thar thréimhse an ailt seo, forbróidh mé mionréiteach chun ionsaithe DDoS a chosc, toisc gur tasc réalaíoch é seo do XDP agus mo réimse saineolais. Mar sin féin, is é an príomhsprioc ná an teicneolaíocht a thuiscint; ní treoir é seo chun cosaint réidh a chruthú. Níl an cód teagaisc optamaithe agus fágtar roinnt nuances ar lár.

XDP Forbhreathnú gairid

Ní dhéanfaidh mé ach cur síos ar na príomhphointí ionas nach ndéanfar doiciméadú agus ailt atá ann cheana a mhacasamhlú.

Mar sin, tá an cód scagaire luchtaithe isteach san eithne. Cuirtear paicéid isteach chuig an scagaire. Mar thoradh air sin, ní mór don scagaire cinneadh a dhéanamh: cuir an paicéad isteach san eithne (XDP_PASS), paicéad titim (XDP_DROP) nó seol ar ais é (XDP_TX). Is féidir leis an scagaire an pacáiste a athrú, tá sé seo fíor go háirithe maidir le XDP_TX. Is féidir leat deireadh a chur leis an gclár freisin (XDP_ABORTED) agus athshocraigh an pacáiste, ach tá sé seo analógach assert(0) - le haghaidh dífhabhtaithe.

Déantar an meaisín fíorúil eBPF (Scagaire Paicéad Berkley leathnaithe) d'aon ghnó simplí ionas gur féidir leis an eithne a sheiceáil nach lúbann an cód agus nach ndéanann sé damáiste do chuimhne daoine eile. Srianta agus seiceálacha carnacha:

  • Tá cosc ​​ar lúbanna (ar gcúl).
  • Tá stack le haghaidh sonraí, ach níl aon fheidhmeanna ann (ní mór gach feidhm C a bheith inlíne).
  • Tá cosc ​​ar rochtain chuimhne lasmuigh den chruach agus maolán paicéad.
  • Tá méid an chóid teoranta, ach i ndáiríre níl sé seo an-suntasach.
  • Ní cheadaítear ach glaonna chuig feidhmeanna eithne speisialta (cúntóirí eBPF).

Breathnaíonn an dearadh agus an suiteáil scagaire mar seo:

  1. Cód foinse (m.sh kernel.c) atá tiomsaithe i réad (kernel.o) le haghaidh ailtireacht meaisín fíorúil eBPF. Ó Dheireadh Fómhair 2019, tacaíonn Clang le tiomsú chuig eBPF agus gealltar é in GCC 10.1.
  2. Má tá glaonna ar struchtúir eithne sa chód oibiachta seo (mar shampla, táblaí agus cuntair), cuirtear nialais in ionad a n-aitheantais, rud a chiallaíonn nach féidir an cód sin a fhorghníomhú. Sula luchtú isteach an eithne, ní mór duit a chur in ionad na nialais le IDs na rudaí ar leith a cruthaíodh trí ghlaonna eithne (nasc an cód). Is féidir leat é seo a dhéanamh le fóntais sheachtracha, nó is féidir leat clár a scríobh a nascfaidh agus a luchtóidh scagaire ar leith.
  3. Fíoraíonn an eithne an clár luchtaithe. Seiceáiltear easpa timthriallta agus teip ar theorainneacha paicéad agus cruachta. Mura féidir leis an bhfíoraitheoir a chruthú go bhfuil an cód ceart, diúltaítear don chlár - ní mór duit a bheith in ann é a shásamh.
  4. Tar éis fíorú rathúil, tiomsaíonn an eithne cód réad ailtireachta eBPF isteach i gcód meaisín d'ailtireacht an chórais (díreach in am).
  5. Ceanglaíonn an clár leis an gcomhéadan agus tosaíonn sé ag próiseáil paicéid.

Ós rud é go ritheann XDP san eithne, déantar dífhabhtú ag baint úsáide as rian-logaí agus, go deimhin, paicéid a scagtar nó a ghineann an clár. Cinntíonn eBPF, áfach, go bhfuil an cód íoslódála slán don chóras, ionas gur féidir leat triail a bhaint as XDP go díreach ar do Linux áitiúil.

An Timpeallacht a Ullmhú

Tionól

Ní féidir le Clang cód oibiachta a tháirgeadh go díreach don ailtireacht eBPF, mar sin tá dhá chéim sa phróiseas:

  1. Tiomsaigh cód C go bytecode LLVM (clang -emit-llvm).
  2. Tiontaigh bytecode go cód oibiachta eBPF (llc -march=bpf -filetype=obj).

Agus scagaire á scríobh, beidh cúpla comhad le feidhmeanna cúnta agus macraí úsáideach ó thástálacha eithne. Tá sé tábhachtach go meaitseálann siad leis an leagan eithne (KVER). Íosluchtaigh iad a 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 le haghaidh Arch Linux (eithne 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 ina bhfuil an cosán go dtí na ceanntásca eithne, ARCH — ailtireacht chórais. Féadfaidh cosáin agus uirlisí a bheith beagán éagsúil idir dáiltí.

Sampla de dhifríochtaí le haghaidh Debian 10 (eithne 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 ceangal eolaire le ceanntásca cúnta agus roinnt eolairí le ceanntásca eithne. Siombail __KERNEL__ Ciallaíonn sé sin go sainmhínítear ceanntásca UAPI (userspace API) don chód eithne, ós rud é go ndéantar an scagaire a fhorghníomhú san eithne.

Is féidir cosaint stoic a dhíchumasú (-fno-stack-protector), toisc go seiceálann fíoraitheoir an chóid eBPF go fóill le haghaidh sáruithe ar chairn lasmuigh de theorainneacha. Is fiú leas iomlán a bhaint as ar an bpointe boise, toisc go bhfuil teorainn le méid bytecode eBPF.

Cuirimis tús le scagaire a théann thar gach paicéad agus nach ndéanann faic:

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

Foireann make bailíonn xdp_filter.o. Cá háit le triail a bhaint as anois?

Seastán tástála

Caithfidh dhá chomhéadan a bheith sa seastán: ar a mbeidh scagaire agus óna seolfar paicéid. Ní mór gur gléasanna Linux lán-chuimsitheach iad seo a bhfuil a IPanna féin acu chun a sheiceáil conas a oibríonn feidhmchláir rialta lenár scagaire.

Tá gléasanna den chineál veth (Ethernet fíorúil) oiriúnach dúinn: is péire comhéadain líonra fíorúla iad seo “ceangailte” go díreach lena chéile. Is féidir leat iad a chruthú mar seo (sa chuid seo gach ordú ip a dhéantar ó root):

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

Anseo xdp-remote и xdp-local — ainmneacha gléasanna. Ar xdp-local (192.0.2.1/24) cuirfear scagaire i gceangal, le xdp-remote (192.0.2.2/24) seolfar trácht ag teacht isteach. Mar sin féin, tá fadhb ann: tá na comhéadain ar an meaisín céanna, agus ní chuirfidh Linux trácht ar cheann acu tríd an gceann eile. Is féidir leat é seo a réiteach le rialacha tricky iptables, ach beidh orthu pacáistí a athrú, rud atá deacair le haghaidh dífhabhtaithe. Is fearr spásanna ainmneacha líonra (netns anseo feasta) a úsáid.

Tá sraith comhéadain, táblaí ródaithe, agus rialacha NetFilter atá scoite ó réada cosúla i netns eile in ainmspás líonra. Ritheann gach próiseas in ainmspás agus níl rochtain aige ach ar oibiachtaí na líonta sin. De réir réamhshocraithe, tá ainmspás líonra amháin ag an gcóras do gach réad, ionas gur féidir leat oibriú i Linux agus gan a bheith ar an eolas faoi netns.

Cruthaímid ainmspás nua xdp-test agus bogadh ann é xdp-remote.

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

Ansin an próiseas ag rith i xdp-test, ní "fheicfear" xdp-local (fanfaidh sé i netns de réir réamhshocraithe) agus nuair a sheolfaidh sé paicéad chuig 192.0.2.1 cuirfidh sé tríd é xdp-remotetoisc gurb é an t-aon chomhéadan ar 192.0.2.0/24 atá inrochtana don phróiseas seo. Oibríonn sé seo sa treo eile freisin.

Agus é ag bogadh idir netns, téann an comhéadan síos agus cailleann sé a sheoladh. Chun an comhéadan i netns a chumrú, ní mór duit a rith ip ... san ainmspás ordaithe seo 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

Mar a fheiceann tú, níl sé seo difriúil ón suíomh xdp-local san ainmspás réamhshocraithe:

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

Má ritheann tú tcpdump -tnevi xdp-local, is féidir leat a fheiceáil go bhfuil paicéid seolta ó xdp-test, a sheachadadh chuig an gcomhéadan seo:

ip netns exec xdp-test   ping 192.0.2.1

Tá sé áisiúil sliogán a sheoladh isteach xdp-test. Tá script ag an stór a oibreoidh leis an seastán go huathoibríoch; mar shampla, is féidir leat an seastán a chumrú leis an ordú sudo ./stand up agus scrios é sudo ./stand down.

Rianú

Tá baint ag an scagaire leis an ngléas mar seo:

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

Eochair -force ag teastáil chun clár nua a nascadh má tá ceann eile nasctha cheana féin. Ní bhaineann “Ní dea-scéal ar bith” an t-ordú seo, tá an chonclúid toirtiúil ar aon nós. cuir in iúl verbose roghnach, ach in éineacht leis tá tuairisc le feiceáil ar obair an fhíoraitheora cóid le liostú cóimeála:

Verifier analysis:

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

Dícheangail an clár ón gcomhéadan:

ip link set dev xdp-local xdp off

Sa script is orduithe iad seo sudo ./stand attach и sudo ./stand detach.

Trí scagaire a cheangal, is féidir leat a chinntiú go bhfuil ping leanann sé ar aghaidh, ach an oibríonn an clár? Cuirimis logaí. Feidhm bpf_trace_printk() cosúil le printf(), ach ní thacaíonn ach suas le trí argóint seachas an patrún, agus liosta teoranta de shonraitheoirí. Macra bpf_printk() shimplíonn sé an glao.

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

Téann an t-aschur go dtí an cainéal rian eithne, nach mór a chumasú:

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

Féach ar snáithe na teachtaireachta:

cat /sys/kernel/debug/tracing/trace_pipe

Déanann an dá ordú seo glaoch sudo ./stand log.

Ba cheart do Ping teachtaireachtaí mar seo a spreagadh anois:

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

Má fhéachann tú go géar ar aschur an fhíoraitheora, tabharfaidh tú faoi deara ríomhaireachtaí aisteacha:

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

Is é fírinne an scéil nach bhfuil alt sonraí ag cláir eBPF, mar sin is é an t-aon bhealach chun teaghrán formáide a ionchódú ná argóintí láithreacha orduithe VM:

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

Ar an gcúis seo, cuireann an t-aschur dífhabhtaithe go mór leis an gcód mar thoradh air.

Paicéid XDP á ​​seoladh

Athraímis an scagaire: lig dó gach paicéad isteach a sheoladh ar ais. Tá sé seo mícheart ó thaobh an líonra de, ós rud é go mbeadh sé riachtanach na seoltaí sna ceanntásca a athrú, ach anois tá an obair i bprionsabal tábhachtach.

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

Seoladh tcpdump ar xdp-remote. Ba chóir go dtaispeánfadh sé Iarratas Macalla ICMP atá ag dul as agus ag teacht isteach agus stop a thaispeáint ICMP Echo Reply. Ach ní léiríonn sé. Tharlaíonn sé go raibh le haghaidh oibre XDP_TX sa chlár ar xdp-local is gáchuig an gcomhéadan péire xdp-remote sannadh clár freisin, fiú má bhí sé folamh, agus ardaíodh é.

Conas a bhí a fhios agam seo?

Rian cosán an phacáiste san eithne Ceadaíonn an mheicníocht imeachtaí perf, dála an scéil, úsáid a bhaint as an meaisín fíorúil céanna, is é sin, úsáidtear eBPF le haghaidh disassemblies le eBPF.

Caithfidh tú maith a dhéanamh as an olc, mar níl aon rud eile le déanamh as.

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

Cad é cód 6?

$ errno 6
ENXIO 6 No such device or address

Feidhm veth_xdp_flush_bq() Faigheann cód earráide ó veth_xdp_xmit(), áit a chuardach le ENXIO agus aimsigh an trácht.

Déanaimis an scagaire íosta a aischur (XDP_PASS) i gcomhad xdp_dummy.c, cuir leis an Makefile é, ceangal leis xdp-remote:

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

Anois tcpdump léiríonn a bhfuiltear ag súil leis:

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

Mura dtaispeántar ach ARPanna ina ionad sin, ní mór duit na scagairí a bhaint (déanann sé seo sudo ./stand detach), lig dul ping, ansin socraigh na scagairí agus bain triail eile as. Is í an fhadhb atá ann go bhfuil an scagaire XDP_TX bailí ar ARP agus má tá an chairn
spásanna ainmneacha xdp-test bhainistiú chun “dearmad” a dhéanamh ar an seoladh MAC 192.0.2.1, ní bheidh sé in ann an IP seo a réiteach.

An fhadhb a fhoirmiú

Bogfaimid ar aghaidh chuig an tasc luaite: scríobh meicníocht fianáin SYN ar XDP.

Tá tuile SYN fós ina ionsaí DDoS a bhfuil an-tóir air, agus is é seo a leanas a bhunbhrí. Nuair a bhunaítear nasc (croitheadh ​​láimhe TCP), faigheann an freastalaí SYN, leithdháileann sé acmhainní don nasc amach anseo, freagraíonn sé le paicéad SYNACK agus fanann sé ar ACK. Ní dhéanann an t-ionsaitheoir ach na mílte paicéid SYN a sheoladh in aghaidh an tsoicind ó sheoltaí bréagacha ó gach óstach i botnet il-mhíle-láidir. Cuirtear iallach ar an bhfreastalaí acmhainní a leithdháileadh láithreach nuair a thagann an paicéad isteach, ach scaoileann sé iad tar éis am istigh mór; mar thoradh air sin, tá cuimhne nó teorainneacha ídithe, ní ghlactar le naisc nua, agus níl an tseirbhís ar fáil.

Mura leithdháileann tú acmhainní bunaithe ar an bpaicéad SYN, ach mura bhfreagraíonn tú ach le paicéad SYNACK, conas mar sin is féidir leis an bhfreastalaí a thuiscint go dtagraíonn an paicéad ACK a tháinig níos déanaí do phaicéad SYN nár sábháladh? Tar éis an tsaoil, is féidir le ionsaitheoir ACKanna falsa a ghiniúint freisin. Is é an pointe atá ag an bhfianán SYN ná é a ionchódú i seqnum paraiméadair nasc mar hash de seoltaí, calafoirt agus salann atá ag athrú. Má d’éirigh leis an ACK teacht sular athraíodh an salann, is féidir leat an hash a ríomh arís agus é a chur i gcomparáid leis acknum. Forge acknum ní féidir leis an ionsaitheoir, ós rud é go bhfuil an rún san áireamh sa salann, agus ní bheidh am aige é a réiteach mar gheall ar chainéal teoranta.

Tá an fianán SYN curtha i bhfeidhm le fada san eithne Linux agus is féidir é a chumasú go huathoibríoch fiú má thagann SYNanna isteach ró-thapa agus en masse.

Clár oideachais ar chroitheadh ​​láimhe TCP

Soláthraíonn TCP tarchur sonraí mar shruth beart, mar shampla, tarchuirtear iarratais HTTP thar TCP. Tarchuirtear an sruth i bpíosaí i bpacáistí. Tá bratacha loighciúla agus uimhreacha seichimh 32-giotán ag gach paicéad TCP:

  • Cinneann an meascán de bhratacha ról pacáiste áirithe. Léiríonn an bhratach SYN gurb é seo an chéad phaicéad a bhí ag an seoltóir ar an gceangal. Ciallaíonn an bhratach ACK go bhfuil na sonraí nasc go léir faighte ag an seoltóir suas go dtí an beart acknum. Is féidir le roinnt bratacha a bheith ar phaicéad agus tugtar paicéad SYNACK air de réir a dteaglaim, mar shampla.

  • Sonraíonn uimhir seicheamh (seicnum) an fritháireamh sa sruth sonraí don chéad bheart a tharchuirtear sa phaicéad seo. Mar shampla, más rud é gurb é N an uimhir seo sa chéad phaicéad le X beart sonraí, is é N+X a bheidh sa chéad phaicéad eile le sonraí nua. Ag tús an naisc, roghnaíonn gach taobh an uimhir seo go randamach.

  • Uimhir admhála (acknum) - an fhritháireamh céanna leis an seqnum, ach ní chinneann sé líon an bheart atá á tharchur, ach uimhir an chéad bheart ón bhfaighteoir, rud nach bhfaca an seoltóir.

Ag tús an cheangail, ní mór do na páirtithe aontú seqnum и acknum. Seolann an cliant paicéad SYN lena seqnum = X. Freagraíonn an freastalaí le paicéad SYNACK, áit a dtaifeadann sé a chuid seqnum = Y agus nochtann acknum = X + 1. Freagraíonn an cliant do SYNACK le paicéad ACK, áit seqnum = X + 1, acknum = Y + 1. Tar éis seo, tosaíonn an t-aistriú sonraí iarbhír.

Mura n-admhaíonn an piaraí go bhfuarthas an paicéad, seolfaidh TCP ar ais é tar éis teorainn ama.

Cén fáth nach n-úsáidtear fianáin SYN i gcónaí?

Ar an gcéad dul síos, má chailltear SYNACK nó ACK, beidh ort fanacht go seolfar arís é - mhoilleoidh socrú an cheangail. Ar an dara dul síos, sa phacáiste SYN - agus gan ach ann! — tarchuirtear roinnt roghanna a dhéanann difear d’oibriú breise an naisc. Gan cuimhneamh ar phaicéid SYN atá ag teacht isteach, déanann an freastalaí neamhaird ar na roghanna seo; ní sheolfaidh an cliant sna chéad paicéid eile iad. Is féidir le TCP a bheith ag obair sa chás seo, ach ar a laghad ag an gcéim tosaigh laghdóidh cáilíocht an cheangail.

Ó thaobh na bpacáistí de, ní mór do chlár XDP na nithe seo a leanas a dhéanamh:

  • freagra a thabhairt ar SYN le SYNACK le fianán;
  • freagra a thabhairt ar ACK le RST (dícheangal);
  • caith amach na paicéid atá fágtha.

Pseudocode an algartam mar aon le parsáil pacáiste:

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

A haon (*) marcáiltear pointí inar gá duit staid an chórais a bhainistiú - ag an gcéad chéim is féidir leat a dhéanamh gan iad trí chroitheadh ​​láimhe TCP a chur i bhfeidhm le fianán SYN a ghiniúint mar seqnum.

Ar an spota (**), cé nach bhfuil tábla againn, déanfaimid an paicéad a scipeáil.

Croitheadh ​​láimhe TCP a chur i bhfeidhm

An pacáiste a pharsáil agus an cód a fhíorú

Beidh struchtúir ceanntásca líonra de dhíth orainn: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) agus TCP (uapi/linux/tcp.h). Ní raibh mé in ann an dara ceann a nascadh mar gheall ar earráidí a bhain le atomic64_t, bhí orm na sainmhínithe riachtanacha a chóipeáil isteach sa chód.

Ní mór na feidhmeanna go léir atá aibhsithe in C maidir le hinléiteacht a líneáil ag an bpointe glaonna, ós rud é go gcuireann an fíoraitheoir eBPF san eithne cosc ​​ar aisrianú, is é sin, i ndáiríre, lúba agus glaonna feidhme.

#define INTERNAL static __attribute__((always_inline))

Macra LOG() díchumasaítear priontáil sa tógáil scaoileadh.

Tá an clár ina iompróir feidhmeanna. Faigheann gach ceann acu paicéad ina leagtar béim ar an gceanntásc leibhéil comhfhreagrach, mar shampla, process_ether() ag súil go mbeidh sé líonta ether. Bunaithe ar thorthaí anailíse allamuigh, is féidir leis an bhfeidhm an paicéad a chur ar aghaidh go leibhéal níos airde. Is é toradh na feidhme an gníomh XDP. Go dtí seo, pasann na láimhseálaithe SYN agus ACK na paicéid go léir.

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

Dírím d’aird ar na seiceálacha marcáilte A agus B. Má dhéanann tú trácht ar A, tógfaidh an clár, ach beidh earráid fíoraithe ann agus tú ag luchtú:

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!

Teaghrán eochair invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Tá cosáin fhorghníomhaithe ann nuair a bhíonn an tríú beart déag ó thús an mhaoláin lasmuigh den phaicéad. Is deacair a thuiscint ón liostú cén líne a bhfuilimid ag caint faoi, ach tá treoiruimhir (12) agus dí-chomhshóiteálaí a thaispeánann línte an bhunchód:

llvm-objdump -S xdp_filter.o | less

Sa chás seo díríonn sé go dtí an líne

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

a dhéanann sé soiléir go bhfuil an fhadhb ether. Bheadh ​​​​sé i gcónaí mar seo.

Freagair SYN

Is é an sprioc ag an gcéim seo ná paicéad ceart SYNACK a ghiniúint le seasta seqnum, a chuirfear in ionad fianán SYN sa todhchaí. Tarlaíonn gach athrú i process_tcp_syn() agus na ceantair máguaird.

Fíorú pacáiste

Is aisteach go leor, seo an líne is suntasaí, nó in áit, an tráchtaireacht air:

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

Agus an chéad leagan den chód á scríobh, baineadh úsáid as an eithne 5.1, a raibh difríocht idir data_end и (const void*)ctx->data_end. Ag am scríofa, ní raibh an fhadhb seo ag eithne 5.3.1. Seans go raibh rochtain ag an tiomsaitheoir ar athróg áitiúil ar bhealach difriúil ó réimse. Morálta an scéil: nuair a bhíonn an neadú mór, is féidir an cód a shimpliú.

Ansin tá gnáthsheiceálacha faid le haghaidh ghlóir an fhíoraitheora; O MAX_CSUM_BYTES thíos.

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

An pacáiste a scaoileadh

Líonann muid isteach seqnum и acknum, socraigh ACK (tá SYN socraithe cheana féin):

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

Babhtáil calafoirt TCP, seoladh IP agus seoltaí MAC. Níl an leabharlann caighdeánach inrochtana ón gclár XDP, mar sin memcpy() - macra a cheiltíonn intreacha 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);

Seiceanna a athríomh

Éilíonn seiceálacha IPv4 agus TCP go gcuirfear na focail 16-giotán ar fad sna ceanntásca, agus scríobhtar méid na gceanntásca isteach iontu, is é sin, anaithnid ag am tiomsaithe. Is fadhb é seo toisc nach rachaidh an fíoraitheoir thar an ngnáthlúb go dtí an athróg teorann. Ach tá méid na gceanntásca teoranta: suas le 64 beart an ceann. Is féidir leat lúb a dhéanamh le líon seasta atriallta, ar féidir leo críochnú go luath.

Tugaim faoi deara go bhfuil RFC 1624 faoi ​​conas an tseic a athríomh go páirteach mura n-athraítear ach focail sheasta na bpacáistí. Mar sin féin, níl an modh uilíoch, agus bheadh ​​an cur i bhfeidhm níos deacra a choimeád ar bun.

Feidhm ríomh seiceála:

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

Cé go size fíoraithe ag an gcód glaonna, is gá an dara coinníoll scoir ionas gur féidir leis an bhfíoraitheoir críochnú an lúb a chruthú.

I gcás focail 32-giotán, cuirtear leagan níos simplí i bhfeidhm:

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

Na seiceálacha a athríomh i ndáiríre agus an paicéad a sheoladh ar ais:

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;

Feidhm carry() déanann sé seiceálacha as suim 32-giotán de fhocail 16-giotán, de réir RFC 791.

Fíorú croith láimhe TCP

Bunaíonn an scagaire nasc i gceart le netcat, in easnamh ar an ACK deiridh, ar fhreagair Linux le paicéad RST, ós rud é nach bhfuair an stack líonra SYN - rinneadh é a thiontú go SYNACK agus a sheoladh ar ais - agus ó thaobh an OS, tháinig paicéad nach raibh baint aige le hoscailt. ceangail.

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

Tá sé tábhachtach seiceáil le feidhmchláir lán-chuimsitheach agus breathnú tcpdump ar xdp-remote mar, mar shampla, hping3 ní fhreagraíonn sé do sheiceálacha míchearta.

Ó thaobh XDP de, tá an fíorú féin fánach. Tá an t-algartam ríofa primitive agus is dócha go mbeidh sé i mbaol ionsaitheoir sofaisticiúla. Úsáideann an eithne Linux, mar shampla, an SipHash cripteagrafach, ach tá a chur i bhfeidhm le haghaidh XDP go soiléir thar raon feidhme an ailt seo.

Tugadh isteach do TODOanna nua a bhaineann le cumarsáid sheachtrach:

  • Ní féidir an clár XDP a stóráil cookie_seed (an chuid rúnda den salann) in athróg dhomhanda, is gá duit a stóráil san eithne, agus déanfar a luach a nuashonrú go tréimhsiúil ó ghineadóir iontaofa.

  • Má mheaitseálann an fianán SYN sa phaicéad ACK, ní gá duit teachtaireacht a phriontáil, ach cuimhnigh ar IP an chliaint fíoraithe chun leanúint ar aghaidh ag cur paicéid uaidh.

Fíorú dlisteanach cliant:

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

Léiríonn na logaí gur ritheadh ​​an seic (flags=0x2 - seo SYN, flags=0x10 tá 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

Cé nach bhfuil aon liosta IPanna fíoraithe ann, ní bheidh aon chosaint ann ó thuilte SYN féin, ach anseo tá an t-imoibriú ar thuilte ACK a sheol an t-ordú seo a leanas:

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

Iontrálacha logála:

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

Conclúid

Uaireanta cuirtear i láthair eBPF i gcoitinne agus XDP go háirithe mar arduirlis riarthóra ná mar ardán forbartha. Go deimhin, is uirlis é XDP chun cur isteach ar phróiseáil paicéid ag an eithne, agus ní rogha eile é seachas an chruach eithne, cosúil le DPDK agus roghanna seachbhóthar eithne eile. Ar an láimh eile, ceadaíonn XDP duit loighic casta go leor a chur i bhfeidhm, rud atá, ina theannta sin, éasca le nuashonrú gan cur isteach ar phróiseáil tráchta. Ní chruthaíonn an fíoraitheoir fadhbanna móra; go pearsanta, ní dhiúltóidh mé é seo do chodanna de chód spáis úsáideora.

Sa dara cuid, má tá an t-ábhar suimiúil, déanfaimid an tábla de chliaint fíoraithe agus dícheangail a chomhlánú, cuntair a chur i bhfeidhm agus fóntais spás úsáideora a scríobh chun an scagaire a bhainistiú.

Naisc:

Foinse: will.com

Add a comment