Við skrifum vörn gegn DDoS árásum á XDP. Kjarnorkuhluti

eXpress Data Path (XDP) tækni gerir kleift að framkvæma handahófskennda umferðarvinnslu á Linux viðmótum áður en pakkarnir fara inn í kjarnanetsstaflann. Notkun XDP - vörn gegn DDoS árásum (CloudFlare), flóknar síur, tölfræðisöfnun (Netflix). XDP forrit eru keyrð af eBPF sýndarvélinni, þannig að þau hafa takmarkanir á bæði kóðanum og tiltækum kjarnaaðgerðum eftir síugerðinni.

Greininni er ætlað að fylla út galla fjölmargra efna á XDP. Í fyrsta lagi veita þeir tilbúinn kóða sem fer strax framhjá eiginleikum XDP: hann er tilbúinn til sannprófunar eða er of einfaldur til að valda vandamálum. Þegar þú reynir síðan að skrifa kóðann þinn frá grunni hefurðu ekki hugmynd um hvað þú átt að gera við dæmigerðar villur. Í öðru lagi er ekki fjallað um leiðir til að prófa XDP á ​​staðnum án VM og vélbúnaðar, þrátt fyrir að þeir hafi sínar eigin gildrur. Textinn er ætlaður forriturum sem þekkja netkerfi og Linux sem hafa áhuga á XDP og eBPF.

Í þessum hluta munum við skilja í smáatriðum hvernig XDP sían er sett saman og hvernig á að prófa hana, síðan munum við skrifa einfalda útgáfu af hinu vel þekkta SYN kökukerfi á pakkavinnslustigi. Við munum ekki búa til „hvítan lista“ ennþá
staðfesta viðskiptavini, halda teljara og hafa umsjón með síunni - nóg af skrám.

Við munum skrifa í C - það er ekki í tísku, en það er hagnýtt. Allur kóði er fáanlegur á GitHub í gegnum hlekkinn í lokin og er skipt í skuldbindingar í samræmi við stigin sem lýst er í greininni.

Fyrirvari. Meðan á þessari grein stendur mun ég þróa smálausn til að verjast DDoS árásum, því þetta er raunhæft verkefni fyrir XDP og mitt sérfræðisvið. Hins vegar er meginmarkmiðið að skilja tæknina; þetta er ekki leiðarvísir til að búa til tilbúna vörn. Kennsluskóðinn er ekki fínstilltur og sleppir nokkrum blæbrigðum.

XDP stutt yfirlit

Ég mun aðeins útlista lykilatriðin til að afrita ekki skjöl og núverandi greinar.

Svo er síukóðinn hlaðinn inn í kjarnann. Komandi pakkar eru sendar til síunnar. Þar af leiðandi verður sían að taka ákvörðun: senda pakkann inn í kjarnann (XDP_PASS), sleppa pakka (XDP_DROP) eða sendu það til baka (XDP_TX). Sían getur breytt umbúðum, þetta á sérstaklega við um XDP_TX. Þú getur líka hætt forritinu (XDP_ABORTED) og endurstilltu pakkann, en þetta er hliðstætt assert(0) - fyrir villuleit.

eBPF (extended Berkley Packet Filter) sýndarvélin er vísvitandi einföld þannig að kjarninn geti athugað að kóðinn fari ekki í lykkju og skemmi ekki minni annarra. Uppsafnaðar takmarkanir og athuganir:

  • Lykkjur (aftur á bak) eru bannaðar.
  • Það er stafli fyrir gögn, en engar aðgerðir (allar C aðgerðir verða að vera innbyggðar).
  • Minnisaðgangur utan staflans og pakkabiðminni er bannaður.
  • Kóðastærðin er takmörkuð, en í reynd er þetta ekki mjög mikilvægt.
  • Aðeins símtöl í sérstakar kjarnaaðgerðir (eBPF-hjálpar) eru leyfðar.

Að hanna og setja upp síu lítur svona út:

  1. Frumkóði (td kernel.c) er sett saman í hlut (kernel.o) fyrir eBPF sýndarvélararkitektúrinn. Frá og með október 2019 er samantekt á eBPF studd af Clang og lofað í GCC 10.1.
  2. Ef þessi hlutakóði inniheldur köll í kjarnabyggingar (til dæmis töflur og teljara), er auðkenni þeirra skipt út fyrir núll, sem þýðir að ekki er hægt að keyra slíkan kóða. Áður en þú hleður inn í kjarnann þarftu að skipta út þessum núllum fyrir auðkenni tiltekinna hluta sem eru búnir til með kjarnaköllum (tengja kóðann). Þú getur gert þetta með ytri tólum, eða þú getur skrifað forrit sem mun tengja og hlaða ákveðna síu.
  3. Kjarninn staðfestir hlaðið forrit. Athugað er hvort hringrásir séu ekki til staðar og ekki er farið yfir mörk pakka og stafla. Ef sannprófandinn getur ekki sannað að kóðinn sé réttur er forritinu hafnað - þú þarft að geta þóknast honum.
  4. Eftir árangursríka sannprófun safnar kjarninn eBPF arkitektúrhlutakóðanum saman í vélkóða fyrir kerfisarkitektúrinn (just-in-time).
  5. Forritið tengist viðmótinu og byrjar að vinna pakka.

Þar sem XDP keyrir í kjarnanum fer kembiforrit fram með því að nota rekja annála og í raun pakka sem forritið síar eða býr til. Hins vegar tryggir eBPF að kóðinn sem hlaðið er niður sé öruggur fyrir kerfið, svo þú getur gert tilraunir með XDP beint á þínu staðbundnu Linux.

Undirbúningur umhverfisins

Þing

Clang getur ekki beint framleitt hlutakóða fyrir eBPF arkitektúrinn, þannig að ferlið samanstendur af tveimur skrefum:

  1. Settu saman C kóða í LLVM bækikóða (clang -emit-llvm).
  2. Umbreyta bætikóða í eBPF hlutakóða (llc -march=bpf -filetype=obj).

Þegar þú skrifar síu munu nokkrar skrár með aukaaðgerðum og fjölvi vera gagnlegar úr kjarnaprófum. Það er mikilvægt að þeir passi við kjarnaútgáfuna (KVER). Sækja þá til 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 fyrir Arch Linux (kjarna 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 inniheldur slóðina að kjarnahausunum, ARCH - kerfisarkitektúr. Slóðir og verkfæri geta verið lítillega breytileg milli dreifinga.

Dæmi um mun fyrir Debian 10 (kjarna 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 tengdu möppu með aukahausum og nokkrum möppum með kjarnahausum. Tákn __KERNEL__ þýðir að UAPI (userspace API) hausar eru skilgreindir fyrir kjarnakóðann, þar sem sían er keyrð í kjarnanum.

Hægt er að slökkva á staflavörn (-fno-stack-protector), vegna þess að eBPF kóða sannprófandi athugar enn hvort staflar séu brotnir utan marka. Það er þess virði að kveikja á hagræðingu strax, vegna þess að stærð eBPF bætikóðans er takmörkuð.

Byrjum á síu sem fer í gegnum alla pakka og gerir ekkert:

#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 safnar xdp_filter.o. Hvar á að prófa það núna?

Prófstandur

Standurinn verður að innihalda tvö tengi: sem sía verður á og sem pakkar verða sendir frá. Þetta verða að vera fullgild Linux tæki með eigin IP til að athuga hvernig venjuleg forrit virka með síunni okkar.

Tæki af veth (sýndar Ethernet) gerðinni henta okkur: þetta eru par af sýndarnetsviðmótum sem eru „tengd“ beint við hvert annað. Þú getur búið þær til svona (í þessum hluta allar skipanir ip eru framkvæmdar frá root):

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

Hér xdp-remote и xdp-local — heiti tækis. Á xdp-local (192.0.2.1/24) verður sía fest, með xdp-remote (192.0.2.2/24) verður send inn umferð. Hins vegar er vandamál: viðmótin eru á sömu vélinni og Linux mun ekki senda umferð til annars þeirra í gegnum hina. Þú getur leyst þetta með erfiðum reglum iptables, en þeir verða að breyta um pakka, sem er óþægilegt fyrir villuleit. Það er betra að nota netnafnarými (hér eftir netns).

Netnafnarými inniheldur sett af viðmótum, leiðartöflum og NetFilter reglum sem eru einangruð frá svipuðum hlutum í öðrum netnum. Hvert ferli keyrir í nafnrými og hefur aðeins aðgang að hlutum þess netns. Sjálfgefið er að kerfið hefur eitt netnafnrými fyrir alla hluti, þannig að þú getur unnið í Linux og ekki vitað um netns.

Búum til nýtt nafnrými xdp-test og flytja það þangað xdp-remote.

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

Þá fer ferlið í gang xdp-test, mun ekki "sjá" xdp-local (það verður sjálfgefið áfram í netns) og þegar pakki er sent til 192.0.2.1 mun það fara í gegnum það xdp-remotevegna þess að það er eina viðmótið á 192.0.2.0/24 sem er aðgengilegt fyrir þetta ferli. Þetta virkar líka í gagnstæða átt.

Þegar farið er á milli netna fer viðmótið niður og missir heimilisfangið sitt. Til að stilla viðmótið í netns þarftu að keyra ip ... í þessu skipananafnarými 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

Eins og þú sérð er þetta ekkert frábrugðið stillingunni xdp-local í sjálfgefnu nafnrými:

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

Ef þú hleypur tcpdump -tnevi xdp-local, þú getur séð að pakkar sendir frá xdp-test, eru sendar í þetta viðmót:

ip netns exec xdp-test   ping 192.0.2.1

Það er þægilegt að setja skel í xdp-test. Geymslan er með skriftu sem gerir sjálfvirkan vinnu með standinum; til dæmis geturðu stillt standinn með skipuninni sudo ./stand up og eyða því sudo ./stand down.

Rekja

Sían er tengd tækinu svona:

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

Lykill -force þarf til að tengja nýtt forrit ef annað er þegar tengt. „Engar fréttir eru góðar fréttir“ snýst ekki um þessa skipun, niðurstaðan er í öllum tilvikum fyrirferðarmikil. gefa til kynna verbose valfrjálst, en með henni birtist skýrsla um störf kóða sannprófanda með samsetningarskrá:

Verifier analysis:

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

Aftengdu forritið frá viðmótinu:

ip link set dev xdp-local xdp off

Í handritinu eru þetta skipanir sudo ./stand attach и sudo ./stand detach.

Með því að festa síu er hægt að ganga úr skugga um það ping heldur áfram að keyra, en virkar forritið? Við skulum bæta við logs. Virka bpf_trace_printk() svipað printf(), en styður aðeins allt að þrjú rök önnur en mynstrið og takmarkaðan lista yfir forskriftir. Fjölvi bpf_printk() einfaldar símtalið.

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

Úttakið fer í kjarnarekjarásina, sem þarf að virkja:

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

Skoða skilaboðaþráð:

cat /sys/kernel/debug/tracing/trace_pipe

Báðar þessar skipanir hringja sudo ./stand log.

Ping ætti nú að kalla fram skilaboð eins og þessi:

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

Ef þú skoðar úttak sannprófandans vel muntu taka eftir undarlegum útreikningum:

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

Staðreyndin er sú að eBPF forrit eru ekki með gagnahluta, þannig að eina leiðin til að umrita sniðstreng eru strax rök VM skipana:

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

Af þessum sökum blæs kembiúttakið mjög út kóðann sem myndast.

Sendir XDP pakka

Við skulum skipta um síu: leyfum henni að senda alla pakka sem berast til baka. Þetta er rangt frá sjónarhóli netkerfisins, þar sem nauðsynlegt væri að breyta vistföngum í hausunum, en nú er vinnan í grundvallaratriðum mikilvæg.

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

Sjósetja tcpdump á xdp-remote. Það ætti að sýna eins sendandi og komandi ICMP Echo Request og hætta að sýna ICMP Echo Reply. En það sýnir sig ekki. Það kemur í ljós að fyrir vinnu XDP_TX í dagskrá á xdp-local nauðsynlegtí parviðmótið xdp-remote var líka úthlutað prógrammi, jafnvel þótt það væri tómt, og hann var alinn upp.

Hvernig vissi ég þetta?

Rekja slóð pakka í kjarnanum Perf events vélbúnaðurinn leyfir, við the vegur, að nota sömu sýndarvélina, það er eBPF er notað til að taka í sundur með eBPF.

Þú verður að gera gott úr illu, því það er ekkert annað til að gera það úr.

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

Hvað er kóði 6?

$ errno 6
ENXIO 6 No such device or address

Virka veth_xdp_flush_bq() fær villukóða frá veth_xdp_xmit(), þar sem leitað er eftir ENXIO og finndu athugasemdina.

Endurheimtum lágmarkssíuna (XDP_PASS) í skránni xdp_dummy.c, bættu því við Makefile, bindðu það við xdp-remote:

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

tcpdump sýnir hvað er gert ráð fyrir:

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

Ef aðeins ARP eru sýnd í staðinn þarftu að fjarlægja síurnar (þetta gerir það sudo ./stand detach), slepptu ping, stilltu síðan síur og reyndu aftur. Vandamálið er að sían XDP_TX gildir bæði á ARP og ef stafla
nafnarými xdp-test tókst að "gleyma" MAC vistfanginu 192.0.2.1, það mun ekki geta leyst þessa IP.

Samsetning vandans

Við skulum halda áfram að tilgreindu verkefni: skrifa SYN vafrakökukerfi á XDP.

SYN flóð er enn vinsæl DDoS árás, kjarni hennar er sem hér segir. Þegar tenging er komið á (TCP handshake) fær þjónninn SYN, úthlutar auðlindum fyrir framtíðartenginguna, svarar með SYNACK pakka og bíður eftir ACK. Árásarmaðurinn sendir einfaldlega þúsundir SYN pakka á sekúndu frá fölsuðum heimilisföngum frá hverjum hýsingaraðila í mörg þúsund sterkt botnet. Miðlarinn neyðist til að úthluta tilföngum strax við komu pakkans, en losar þau eftir langan tíma, þar af leiðandi eru minni eða takmörk uppurin, nýjar tengingar eru ekki samþykktar og þjónustan er ekki tiltæk.

Ef þú úthlutar ekki auðlindum byggt á SYN pakkanum, heldur svarar aðeins með SYNACK pakka, hvernig getur þá þjónninn skilið að ACK pakkinn sem kom seinna vísar til SYN pakka sem var ekki vistaður? Þegar öllu er á botninn hvolft getur árásarmaður líka búið til falsa ACK. Tilgangurinn með SYN kökunni er að kóða hana inn seqnum tengibreytur sem kjötkássa af vistföngum, höfnum og breytilegum salti. Ef ACK náði að berast áður en saltinu var breytt, geturðu reiknað kjötkássa aftur og borið saman við acknum. Forge acknum árásarmaðurinn getur það ekki, þar sem saltið inniheldur leyndarmálið, og mun ekki hafa tíma til að flokka það vegna takmarkaðrar rásar.

SYN kexið hefur lengi verið innleitt í Linux kjarnanum og er jafnvel hægt að virkja það sjálfkrafa ef SYN berast of fljótt og í fjöldann.

Fræðsluforrit um TCP handabandi

TCP veitir gagnaflutning sem bætistraum, til dæmis eru HTTP beiðnir sendar yfir TCP. Straumurinn er sendur í bútum í pökkum. Allir TCP pakkar hafa rökrétt fána og 32 bita raðnúmer:

  • Samsetning fána ákvarðar hlutverk tiltekins pakka. SYN fáninn gefur til kynna að þetta sé fyrsti pakki sendandans á tengingunni. ACK fáninn þýðir að sendandi hefur fengið öll tengigögn upp að bæti acknum. Pakki getur haft nokkra fána og er kallaður með samsetningu þeirra, til dæmis SYNACK pakki.

  • Raðnúmer (seqnum) tilgreinir offset í gagnastraumnum fyrir fyrsta bæti sem er sent í þessum pakka. Til dæmis, ef í fyrsta pakkanum með X bæti af gögnum var þessi tala N, í næsta pakka með nýjum gögnum verður það N+X. Í upphafi tengingarinnar velur hvor aðili þessa tölu af handahófi.

  • Staðfestingarnúmer (acknum) - sama offset og seqnum, en það ákvarðar ekki númer bætisins sem verið er að senda, heldur númer fyrsta bætisins frá viðtakanda, sem sendandi sá ekki.

Í upphafi tengingar verða aðilar að vera sammála seqnum и acknum. Viðskiptavinurinn sendir SYN pakka með sínum seqnum = X. Miðlarinn svarar með SYNACK pakka, þar sem hann skráir það seqnum = Y og afhjúpar acknum = X + 1. Viðskiptavinurinn svarar SYNACK með ACK pakka, þar sem seqnum = X + 1, acknum = Y + 1. Eftir þetta hefst hinn raunverulegi gagnaflutningur.

Ef jafninginn staðfestir ekki móttöku pakkans sendir TCP hann aftur eftir tímamörk.

Af hverju eru SYN vafrakökur ekki alltaf notaðar?

Í fyrsta lagi, ef SYNACK eða ACK glatast, verður þú að bíða eftir að það verði sent aftur - uppsetning tengingarinnar mun hægjast. Í öðru lagi í SYN pakkanum - og aðeins í honum! — nokkrir valkostir eru sendir sem hafa áhrif á frekari rekstur tengingarinnar. Án þess að muna komandi SYN pakka, hunsar þjónninn þessa valkosti; viðskiptavinurinn mun ekki senda þá í næstu pakka. TCP getur virkað í þessu tilfelli, en að minnsta kosti á upphafsstigi munu gæði tengingarinnar minnka.

Frá sjónarhóli pakka verður XDP forrit að gera eftirfarandi:

  • svara SYN með SYNACK með kex;
  • svara ACK með RST (aftengjast);
  • fargaðu pökkunum sem eftir eru.

Gervikóði reikniritsins ásamt pakkaþáttun:

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

Einn (*) punktar þar sem þú þarft að stjórna stöðu kerfisins eru merktir - á fyrsta stigi geturðu verið án þeirra með því einfaldlega að innleiða TCP handaband með myndun SYN vafraköku sem röð.

Á staðnum (**), á meðan við höfum ekki borð, munum við sleppa pakkanum.

Innleiðing TCP handabandi

Að flokka pakkann og staðfesta kóðann

Við munum þurfa nethausbyggingu: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) og TCP (uapi/linux/tcp.h). Ég gat ekki tengt hið síðarnefnda vegna villna tengdum atomic64_t, Ég þurfti að afrita nauðsynlegar skilgreiningar inn í kóðann.

Allar aðgerðir sem eru auðkenndar í C ​​fyrir læsileika verða að vera innbyggðar við símtalið, þar sem eBPF sannprófunarmaðurinn í kjarnanum bannar afturköllun, það er í raun lykkjur og fallköll.

#define INTERNAL static __attribute__((always_inline))

Fjölvi LOG() slekkur á prentun í útgáfunni.

Forritið er færiband aðgerða. Hver fær pakka þar sem samsvarandi stigshaus er auðkenndur, til dæmis, process_ether() gerir ráð fyrir að það verði fyllt ether. Byggt á niðurstöðum sviðsgreiningar getur aðgerðin komið pakkanum á hærra stig. Niðurstaða aðgerðarinnar er XDP aðgerðin. Í bili standast SYN og ACK meðhöndlarnir alla pakka.

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

Ég vek athygli þína á ávísunum merktum A og B. Ef þú gerir athugasemdir við A mun forritið byggja upp, en það verður staðfestingarvilla við hleðslu:

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!

Lyklastrengur invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Það eru keyrsluslóðir þegar þrettánda bæti frá upphafi biðminni er utan pakkans. Það er erfitt að skilja af skráningunni hvaða línu við erum að tala um, en það er leiðbeininganúmer (12) og sundurtakari sem sýnir línurnar af frumkóða:

llvm-objdump -S xdp_filter.o | less

Í þessu tilviki bendir það á línuna

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

sem gerir það ljóst að vandamálið er ether. Þetta væri alltaf svona.

Svaraðu SYN

Markmiðið á þessu stigi er að búa til réttan SYNACK pakka með fasta seqnum, sem í framtíðinni verður skipt út fyrir SYN kökuna. Allar breytingar eiga sér stað í process_tcp_syn() og nærliggjandi svæði.

Staðfesting pakka

Merkilegt nokk, hér er merkilegasta línan, eða réttara sagt, athugasemdin við hana:

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

Þegar fyrstu útgáfan af kóðanum var skrifuð var 5.1 kjarninn notaður, til að sannreyna það var munur á milli data_end и (const void*)ctx->data_end. Þegar þetta er skrifað átti kjarni 5.3.1 ekki við þetta vandamál. Það er mögulegt að þýðandinn hafi fengið aðgang að staðbundinni breytu öðruvísi en reit. Siðferði sögunnar: Einföldun kóðans getur hjálpað þegar mikið er um hreiður.

Næst eru venjubundnar lengdarprófanir til dýrðar sannprófandans; O MAX_CSUM_BYTES hér að neðan.

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

Að brjóta upp pakkann

Við fyllum út seqnum и acknum, stilltu ACK (SYN er þegar stillt):

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

Skiptu um TCP tengi, IP tölu og MAC vistföng. Staðlað bókasafn er ekki aðgengilegt frá XDP forritinu, svo memcpy() — fjölvi sem felur innri eiginleika 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);

Endurútreikningur tékkupphæða

IPv4 og TCP eftirlitssummur krefjast þess að öllum 16 bita orðum sé bætt við í hausunum og stærð hausanna er skrifuð inn í þær, það er óþekkt á þýðingu. Þetta er vandamál vegna þess að sannprófandinn mun ekki sleppa venjulegri lykkju að mörkabreytunni. En stærð hausanna er takmörkuð: allt að 64 bæti hver. Þú getur búið til lykkju með föstum fjölda endurtekningar, sem getur endað snemma.

Ég tek það fram að það er RFC 1624 um hvernig eigi að endurreikna eftirlitssumman að hluta ef aðeins er breytt föstum orðum pakkana. Aðferðin er hins vegar ekki algild og erfiðara að viðhalda framkvæmdinni.

Athugunarsummu útreikningsaðgerð:

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

Samt size staðfest með köllunarkóðanum er annað útgönguskilyrðið nauðsynlegt svo sannprófandinn geti sannað að lykkjunni sé lokið.

Fyrir 32 bita orð er einfaldari útgáfa útfærð:

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

Reyndar að endurreikna eftirlitstölurnar og senda pakkann til baka:

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;

Virka carry() gerir athugunarsummu úr 32 bita summu 16 bita orða, samkvæmt RFC 791.

TCP handabandi staðfesting

Sían kemur rétt á tengingu við netcat, vantar endanlega ACK, sem Linux svaraði með RST pakka, þar sem netstaflan fékk ekki SYN - honum var breytt í SYNACK og sendur til baka - og frá OS sjónarhóli kom pakki sem var ekki tengt opnu tengingar.

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

Mikilvægt er að athuga með fullgildar umsóknir og fylgjast með tcpdump á xdp-remote vegna þess að td. hping3 svarar ekki röngum eftirlitstölum.

Frá XDP sjónarhóli er sannprófunin sjálf léttvæg. Reikniritið fyrir útreikning er frumstætt og líklega viðkvæmt fyrir háþróuðum árásarmanni. Linux kjarninn, til dæmis, notar dulmálið SipHash, en útfærsla hans fyrir XDP er greinilega utan gildissviðs þessarar greinar.

Kynnt fyrir nýjum verkefnum sem tengjast ytri samskiptum:

  • XDP forrit getur ekki geymt cookie_seed (leynihluti saltsins) í alþjóðlegri breytu þarftu geymslu í kjarnanum, verðmæti þess verður reglulega uppfært frá áreiðanlegum rafalli.

  • Ef SYN kexið passar í ACK pakkanum þarftu ekki að prenta skilaboð, heldur muna IP staðfesta biðlarans til að halda áfram að senda pakka frá honum.

Lögmæt staðfesting viðskiptavina:

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

Logarnir sýna að athugunin stóðst (flags=0x2 - þetta er SYN, flags=0x10 er 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

Þó að það sé enginn listi yfir staðfestar IP-tölur, þá verður engin vörn gegn SYN-flóðinu sjálfu, en hér eru viðbrögðin við ACK-flóði sem var hleypt af stokkunum með eftirfarandi skipun:

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

Skráningarfærslur:

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

Ályktun

Stundum er eBPF almennt og XDP sérstaklega kynnt meira sem háþróað stjórnendatæki en sem þróunarvettvangur. Reyndar, XDP er tæki til að trufla vinnslu pakka af kjarnanum, og ekki valkostur við kjarnastaflann, eins og DPDK og aðrir kjarnaframhjáhaldsvalkostir. Á hinn bóginn gerir XDP þér kleift að innleiða nokkuð flókna rökfræði, sem að auki er auðvelt að uppfæra án truflana í umferðarvinnslu. Sannprófandinn skapar ekki stór vandamál; persónulega myndi ég ekki neita þessu fyrir hluta af notendarýmiskóða.

Í seinni hlutanum, ef efnið er áhugavert, munum við klára töfluna yfir staðfesta viðskiptavini og aftengingar, innleiða teljara og skrifa notendarými til að stjórna síunni.

Tilvísanir:

Heimild: www.habr.com

Bæta við athugasemd