eXpress Data Path (XDP) tækni gerir kleift að vinna úr handahófskenndri umferð á viðmótum Linux Áður en pakkar komast inn í kjarna netkerfisins. XDP forrit innihalda DDoS vörn (CloudFlare), flóknar síur og tölfræðisöfnun (Netflix). XDP forrit eru keyrð af eBPF sýndarvélinni og því hafa þau takmarkanir bæði á kóða þeirra og tiltækum kjarnavirkni eftir því hvaða síugerð er notuð.
Þessi grein miðar að því að fjalla um galla fjölmargra efna um XDP. Í fyrsta lagi bjóða þau upp á tilbúinn kóða sem fer strax fram hjá eiginleikum XDP: hann er annað hvort tilbúinn til staðfestingar eða of einfaldur til að valda vandamálum. Þegar reynt er að skrifa sinn eigin kóða frá grunni er erfitt að skilja hvernig á að takast á við dæmigerðar villur. Í öðru lagi fjallar hún ekki um aðferðir til að prófa XDP staðbundið án sýndarvéla eða vélbúnaðar, þrátt fyrir að þessar aðferðir hafi sína eigin galla. Þessi texti er ætlaður forriturum sem þekkja net 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:
- 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. - 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.
- 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.
- Eftir árangursríka sannprófun safnar kjarninn eBPF arkitektúrhlutakóðanum saman í vélkóða fyrir kerfisarkitektúrinn (just-in-time).
- Forritið tengist viðmótinu og byrjar að vinna pakka.
Þar sem XDP keyrir í kjarnanum er kembiforritun framkvæmd með því að nota rakningarskrár og pakka sem forritið síar eða býr til. Hins vegar tryggir eBPF öryggi hlaðins kóða fyrir kerfið, þannig að þú getur prófað XDP beint á þinni eigin vél. Linux.
Undirbúningur umhverfisins
Þing
Clang getur ekki beint framleitt hlutakóða fyrir eBPF arkitektúrinn, þannig að ferlið samanstendur af tveimur skrefum:
- Settu saman C kóða í LLVM bækikóða (
clang -emit-llvm). - 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 . Þ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 BASEMakefile fyrir Arch Linux (kjarni 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 ./*.oKDIR inniheldur slóðina að kjarnahausunum, ARCH - kerfisarkitektúr. Slóðir og verkfæri geta verið lítillega breytileg milli dreifinga.
Dæmi um mismun fyrir Debian 10 (kjarni 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
Uppsetningin verður að innihalda tvö viðmót: eitt fyrir síuna og eitt þaðan sem pakkar verða sendir. Þetta verða að vera fullkomlega virk tæki. Linux með IP-tölum þínum 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-localHé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) mun áframsenda umferð sem kemur inn. Hins vegar er vandamál: viðmótin eru á sömu vélinni, og Linux mun ekki senda umferð til annars þeirra í gegnum hinn. Þetta er hægt að leysa með snjöllum 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).
Netnafnrými inniheldur safn af viðmótum, leiðartöflum og NetFilter-reglum, einangrað frá svipuðum hlutum í öðrum netnöfnum. Hvert ferli starfar í tilteknu nafnrými og hefur aðeins aðgang að hlutum innan þess netnafnrýmis. Sjálfgefið er að kerfið hafi eitt netnafnrými fyrir alla hluti, þannig að þú getur unnið í Linux og veit ekki með 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 upEins 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 upEf þú 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 verboseLykill -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) exitAftengdu 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 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_printkSkoða skilaboðaþráð:
cat /sys/kernel/debug/tracing/trace_pipeBáð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: 00000000ac510377Ef þú 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 í 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?
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 addressVirka 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.oNú 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 64Ef 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-vafrakökur hafa verið innleiddar í kjarnanum í langan tíma. Linux og getur jafnvel kveikt sjálfkrafa á því ef SYN-tæki koma of hratt og í miklu magni.
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 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, sleppa síðustu ACK, sem Linux svaraði með RST-pakka vegna þess að netpakkinn tók ekki á móti SYN - því var breytt í SYNACK og sent til baka - og frá sjónarhóli stýrikerfisins barst pakki sem tengdist ekki opnum tengingum.
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peerMikilvæ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.
SYN kex
Frá sjónarhóli XDP er staðfestingin sjálf einföld. Reikniritið er frumstætt og líklega viðkvæmt fyrir háþróuðum árásaraðilum. Linuxnotar til dæmis dulritunarkóðann SipHash, en útfærsla þess fyrir XDP er greinilega utan efnissviðs greinarinnar.
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 peerLogarnir 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.1Skrá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
