ProHoster > Օրագիր > Վարչակազմը > Մենք գրում ենք պաշտպանություն DDoS հարձակումներից XDP-ի վրա: Միջուկային մաս
Մենք գրում ենք պաշտպանություն DDoS հարձակումներից XDP-ի վրա: Միջուկային մաս
eXpress Data Path (XDP) տեխնոլոգիան թույլ է տալիս կամայական մշակել տրաֆիկը Linux ինտերֆեյսներում, նախքան փաթեթները միջուկի ցանցի կույտ մտնելը: XDP-ի կիրառում - պաշտպանություն DDoS հարձակումներից (CloudFlare), բարդ զտիչներ, վիճակագրության հավաքագրում (Netflix): XDP ծրագրերն իրականացվում են eBPF վիրտուալ մեքենայի կողմից և, հետևաբար, սահմանափակումներ ունեն ինչպես իրենց կոդի, այնպես էլ միջուկի հասանելի գործառույթների վրա՝ կախված ֆիլտրի տեսակից:
Հոդվածը նախատեսված է լրացնել XDP-ի բազմաթիվ նյութերի թերությունները: Նախ, նրանք տրամադրում են պատրաստի կոդ, որն անմիջապես շրջանցում է XDP-ի առանձնահատկությունները՝ պատրաստված ստուգման համար կամ չափազանց պարզ՝ խնդիրներ առաջացնելու համար: Երբ ավելի ուշ փորձում եք զրոյից գրել ձեր սեփական կոդը, հասկանալի չէ, թե ինչ անել բնորոշ սխալների հետ: Երկրորդ, այն չի ներառում XDP-ն առանց VM-ի և սարքաշարի տեղական փորձարկման ուղիները, չնայած այն հանգամանքին, որ նրանք ունեն իրենց սեփական թակարդները: Տեքստը նախատեսված է ցանցերին և Linux-ին ծանոթ ծրագրավորողների համար, ովքեր հետաքրքրված են XDP-ով և eBPF-ով:
Այս մասում մենք մանրամասն կհասկանանք, թե ինչպես է հավաքվում XDP ֆիլտրը և ինչպես է այն փորձարկվում, այնուհետև փաթեթների մշակման մակարդակում կգրենք հայտնի SYN թխուկների մեխանիզմի պարզ տարբերակը։ Մինչև մենք կկազմենք «սպիտակ ցուցակ».
ստուգված հաճախորդներ, պահեք հաշվիչներ և կառավարեք զտիչը՝ բավականաչափ տեղեկամատյաններ:
Մենք կգրենք C - սա ոչ թե մոդայիկ է, այլ գործնական: Ամբողջ ծածկագիրը հասանելի է GitHub-ում վերջում տեղադրված հղման վրա և բաժանված է պարտավորությունների՝ համաձայն հոդվածում նկարագրված քայլերի:
Disclaimer. Հոդվածի ընթացքում կմշակվի DDoS հարձակումները հետ մղելու մինի լուծում, քանի որ սա իրատեսական խնդիր է XDP-ի և իմ տարածքի համար։ Այնուամենայնիվ, հիմնական նպատակը տեխնոլոգիան հասկանալն է, սա պատրաստի պաշտպանություն ստեղծելու ուղեցույց չէ: Ուսուցման կոդը օպտիմիզացված չէ և բաց է թողնում որոշ նրբերանգներ:
XDP-ի համառոտ ակնարկ
Նշեմ միայն առանցքային կետերը, որպեսզի չկրկնվեն փաստաթղթերն ու առկա հոդվածները։
Այսպիսով, ֆիլտրի կոդը բեռնվում է միջուկում: Զտիչը փոխանցվում է մուտքային փաթեթներ: Արդյունքում ֆիլտրը պետք է որոշում կայացնի՝ փաթեթը փոխանցել միջուկին (XDP_PASS), գցել փաթեթը (XDP_DROP) կամ հետ ուղարկեք (XDP_TX) Զտիչը կարող է փոխել փաթեթը, սա հատկապես ճիշտ է XDP_TX. Կարող եք նաև խափանել ծրագիրը (XDP_ABORTED) և գցեք փաթեթը, բայց սա նման է assert(0) - վրիպազերծման համար:
eBPF (ընդլայնված Berkley Packet Filter) վիրտուալ մեքենան միտումնավոր պարզեցված է, որպեսզի միջուկը կարողանա ստուգել, որ կոդը չի պտտվում և չի վնասում այլ մարդկանց հիշողությունը: Կուտակային սահմանափակումներ և ստուգումներ.
Օղակները (հետ ցատկել) արգելված են:
Տվյալների համար կա կույտ, բայց ֆունկցիաներ չկան (C-ի բոլոր գործառույթները պետք է ներգծված լինեն):
Հիշողության մուտքն արգելված է փաթեթից և փաթեթների բուֆերից դուրս:
Կոդի չափը սահմանափակ է, բայց գործնականում դա այնքան էլ նշանակալի չէ։
Թույլատրվում են միայն միջուկի հատուկ գործառույթներ (eBPF օգնականներ):
Զտիչ մշակելը և տեղադրելը հետևյալն է.
աղբյուրի կոդը (օրինակ. kernel.c) կազմում է օբյեկտ (kernel.o) eBPF վիրտուալ մեքենայի ճարտարապետության համար: 2019 թվականի հոկտեմբերի դրությամբ eBPF-ին կազմելը աջակցվում է Clang-ի կողմից և խոստացված է GCC 10.1-ում:
Եթե այս օբյեկտի կոդում կան զանգեր դեպի միջուկային կառույցներ (օրինակ՝ աղյուսակներ և հաշվիչներ), ապա դրանց ID-ների փոխարեն կան զրոներ, այսինքն՝ նման կոդը հնարավոր չէ կատարել։ Նախքան միջուկը բեռնելը, այս զրոները պետք է փոխարինվեն միջուկի կանչերի միջոցով ստեղծված հատուկ օբյեկտների ID-ներով (կապեք կոդը): Դուք կարող եք դա անել արտաքին կոմունալ ծրագրերի միջոցով, կամ կարող եք գրել ծրագիր, որը կապելու և բեռնելու է կոնկրետ ֆիլտր:
Միջուկը ստուգում է բեռնվող ծրագիրը: Այն ստուգում է ցիկլերի բացակայությունը և փաթեթի և կույտի սահմաններից դուրս չգալը: Եթե ստուգիչը չի կարող ապացուցել, որ կոդը ճիշտ է, ծրագիրը մերժվում է, պետք է կարողանա գոհացնել նրան:
Հաջող ստուգումից հետո միջուկը հավաքում է eBPF ճարտարապետության օբյեկտի կոդը համակարգի ճարտարապետության մեքենայի կոդի մեջ (ճիշտ ժամանակին):
Ծրագիրը կցվում է ինտերֆեյսին և սկսում է մշակել փաթեթները:
Քանի որ XDP-ն աշխատում է միջուկում, վրիպազերծումը հիմնված է հետագծերի տեղեկամատյանների և, փաստորեն, փաթեթների վրա, որոնք ծրագիրը զտում կամ ստեղծում է: Այնուամենայնիվ, eBPF-ն ապահով է պահում ներբեռնված կոդը համակարգի համար, այնպես որ դուք կարող եք փորձարկել XDP-ն անմիջապես ձեր տեղական Linux-ում:
Շրջակա միջավայրի նախապատրաստում
Ասամբլեան
Clang-ը չի կարող ուղղակիորեն թողարկել օբյեկտի կոդը eBPF ճարտարապետության համար, ուստի գործընթացը բաղկացած է երկու քայլից.
Զտիչ գրելիս օգտակար կլինեն մի քանի ֆայլ՝ օժանդակ գործառույթներով և մակրոներով միջուկի թեստերից. Կարևոր է, որ դրանք համապատասխանեն միջուկի տարբերակին (KVER) Ներբեռնեք դրանք helpers/:
KDIR պարունակում է միջուկի վերնագրերի ուղին, ARCH - համակարգի ճարտարապետություն. Ճանապարհները և գործիքները կարող են մի փոքր տարբերվել բաշխումների միջև:
Տարբերության օրինակ Debian 10-ի համար (միջուկ 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 ներառել գրացուցակ՝ օժանդակ վերնագրերով և մի քանի գրացուցակներ՝ միջուկի վերնագրերով: Խորհրդանիշ __KERNEL__ նշանակում է, որ UAPI (userspace API) վերնագրերը սահմանված են միջուկի կոդի համար, քանի որ ֆիլտրը կատարվում է միջուկում:
Կույտի պաշտպանությունը կարող է անջատվել (-fno-stack-protector) քանի որ eBPF կոդերի ստուգիչը, այնուամենայնիվ, ստուգում է կույտի սահմաններից դուրս չլինելը: Դուք պետք է անմիջապես միացնեք օպտիմալացումները, քանի որ eBPF բայթկոդի չափը սահմանափակ է:
Սկսենք զտիչից, որն անցնում է բոլոր փաթեթները և ոչինչ չի անում.
Թիմ make հավաքում է xdp_filter.o. Որտեղ կարող եք փորձարկել այն հիմա:
Փորձարկման տակդիր
Ստենդը պետք է ներառի երկու ինտերֆեյս՝ որի վրա կլինի զտիչ և որտեղից փաթեթներ կուղարկվեն։ Սրանք պետք է լինեն լիարժեք Linux սարքեր՝ իրենց սեփական IP-ներով, որպեսզի ստուգենք, թե ինչպես են սովորական հավելվածներն աշխատում մեր ֆիլտրի հետ:
Մեզ համար հարմար են veth-ի (վիրտուալ Ethernet) նման սարքերը. դրանք վիրտուալ ցանցային ինտերֆեյսերի զույգ են, որոնք «կապված» են ուղղակիորեն միմյանց հետ: Դուք կարող եք դրանք ստեղծել այսպես (այս բաժնում բոլոր հրամանները ip -ից կատարվեց root):
ip link add xdp-remote type veth peer name xdp-local
Այստեղ xdp-remote и xdp-local - սարքերի անունները. Վրա xdp-local (192.0.2.1/24) կկցվի զտիչ՝ հետ xdp-remote (192.0.2.2/24) մուտքային տրաֆիկը կուղարկվի: Այնուամենայնիվ, խնդիր կա. ինտերֆեյսները գտնվում են նույն մեքենայի վրա, և Linux-ը մյուսի միջոցով չի ուղարկի տրաֆիկ դրանցից մեկին: Դուք կարող եք լուծել այն բարդ կանոններով iptables, բայց նրանք ստիպված կլինեն փոխել փաթեթները, ինչը անհարմար է վրիպազերծման ժամանակ։ Ավելի լավ է օգտագործել ցանցի անվանատարածքները (ցանցային անվանատարածքներ, հետագա ցանցեր):
Ցանցի անվանատարածքը պարունակում է մի շարք միջերեսներ, երթուղային աղյուսակներ և NetFilter կանոններ, որոնք մեկուսացված են այլ ցանցերի նմանատիպ օբյեկտներից: Յուրաքանչյուր գործընթաց աշխատում է ինչ-որ անվանատարածքում, և միայն այս ցանցերի օբյեկտները հասանելի են նրան: Լռելյայնորեն, համակարգը ունի մեկ ցանցի անվանատարածք բոլոր օբյեկտների համար, այնպես որ կարող եք աշխատել Linux-ով և չիմանալ ցանցերի մասին:
Եկեք ստեղծենք նոր անվանատարածք xdp-test և տեղափոխվել այնտեղ xdp-remote.
ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test
Այնուհետև գործընթացը սկսվում է xdp-test, չեմ «տեսնի» xdp-local (այն լռելյայն կմնա ցանցերում) և 192.0.2.1-ին փաթեթ ուղարկելիս այն կանցնի xdp-remote, քանի որ դա միակ ինտերֆեյսն է 192.0.2.0/24-ով, որը հասանելի է այս գործընթացին: Սա նույնպես աշխատում է հակառակ ուղղությամբ:
Ցանցերի միջև շարժվելիս ինտերֆեյսը իջնում է և կորցնում հասցեն: Ցանցերում ինտերֆեյս ստեղծելու համար պետք է գործարկել ip ... այս հրամանի անվան տարածքում 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
Ինչպես տեսնում եք, սա ոչնչով չի տարբերվում կարգավորումից xdp-local լռելյայն անվանատարածքում.
ip address add 192.0.2.1/24 dev xdp-local
ip link set xdp-local up
Եթե վազել tcpdump -tnevi xdp-local, դուք կարող եք տեսնել, որ փաթեթները ուղարկվել են xdp-test, առաքվում են այս ինտերֆեյսին.
ip netns exec xdp-test ping 192.0.2.1
Հարմար է ներս մտցնել կեղև xdp-test. Պահեստն ունի սկրիպտ, որն ավտոմատացնում է ստենդի հետ աշխատանքը, օրինակ՝ կարող եք ստենդը կարգավորել հրամանով. sudo ./stand up և հեռացնել այն sudo ./stand down.
հետագծում
Զտիչը սարքին կցվում է հետևյալ կերպ.
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
Բանալին -force անհրաժեշտ է կապել նոր ծրագիր, եթե մեկ այլ ծրագիր արդեն կապված է: «Ոչ մի նորություն լավ նորություն չէ» այս հրամանի մասին չէ, այնուամենայնիվ, ելքը ծավալուն է։ նշել verbose կամընտիր, բայց դրա հետ մեկտեղ հայտնվում է հաշվետվություն կոդի ստուգիչի աշխատանքի մասին ասեմբլերի ցուցակով.
Verifier analysis:
0: (b7) r0 = 2
1: (95) exit
Անջատեք ծրագիրը ինտերֆեյսից.
ip link set dev xdp-local xdp off
Սցենարում սրանք հրամաններն են sudo ./stand attach и sudo ./stand detach.
Ֆիլտրը կապելով՝ կարող եք համոզվել, որ ping շարունակում է աշխատել, բայց արդյո՞ք ծրագիրը գործում է: Ավելացնենք լոգոները։ Գործառույթ bpf_trace_printk() նման է printf(), բայց աջակցում է միայն մինչև երեք արգումենտ, բացի օրինաչափությունից, և սպեցիֆիկատորների սահմանափակ ցանկ: Մակրո bpf_printk() պարզեցնում է զանգը.
Այս պատճառով վրիպազերծման ելքը մեծապես փչացնում է ստացված կոդը:
XDP փաթեթների ուղարկում
Եկեք փոխենք զտիչը. թող այն հետ ուղարկի բոլոր մուտքային փաթեթները: Սա սխալ է ցանցի տեսանկյունից, քանի որ անհրաժեշտ կլիներ փոխել հասցեները վերնագրերում, բայց այժմ աշխատանքը սկզբունքորեն կարևոր է:
Մենք մեկնարկում ենք tcpdump մասին xdp-remote. Այն պետք է ցույց տա նույնական ելքային և մուտքային ICMP Echo Request-ը և դադարեցնի ցուցադրել ICMP Echo Reply-ը: Բայց դա ցույց չի տալիս: Ստացվում է, որ աշխատում է XDP_TX համար ծրագրում xdp-localպետք էինտերֆեյսը զուգավորելու համար xdp-remote նշանակվել է նաև ծրագիր, թեկուզ դատարկ, և բարձրացվել է։
որտեղի՞ց իմացա։
Փաթեթի ուղու հետագծում միջուկում perf իրադարձությունների մեխանիզմը թույլ է տալիս, ի դեպ, օգտագործել նույն վիրտուալ մեքենան, այսինքն, eBPF-ն օգտագործվում է eBPF-ով ապամոնտաժելու համար:
Դուք պետք է չարից բարիք ստեղծեք, քանի որ դրանից ուրիշ բան չկա:
Եթե փոխարենը ցուցադրվում է միայն ARP, դուք պետք է հեռացնեք զտիչները (սա ստիպում է sudo ./stand detach), թող ping, ապա տեղադրեք զտիչներ և նորից փորձեք: Խնդիրն այն է, որ ֆիլտրը XDP_TX ազդում է նաև ARP-ին, և եթե stack
անունների տարածքներ xdp-test հաջողվել է «մոռանալ» MAC հասցեն 192.0.2.1, նա չի կարողանա լուծել այս IP-ն։
Խնդրի ձևակերպում
Անցնենք նշված առաջադրանքին՝ գրել SYN թխուկների մեխանիզմ XDP-ում:
Մինչ այժմ SYN ջրհեղեղը մնում է հանրաճանաչ DDoS գրոհ, որի էությունը հետևյալն է. Երբ կապ է հաստատվում (TCP ձեռքսեղմում), սերվերը ստանում է SYN, հատկացնում է ռեսուրսներ ապագա կապի համար, պատասխանում է SYNACK փաթեթով և սպասում ACK-ին: Հարձակվողը պարզապես ուղարկում է SYN փաթեթներ կեղծ հասցեներից՝ հազարավոր վայրկյանում յուրաքանչյուր հոսթից բազմահազար բոտնետում: Սերվերը ստիպված է լինում ռեսուրսներ բաշխել անմիջապես փաթեթը ժամանելուն պես, սակայն այն թողարկում է երկար ժամանակի ավարտից հետո, արդյունքում հիշողությունը կամ սահմանափակումները սպառվում են, նոր կապեր չեն ընդունվում, ծառայությունն անհասանելի է:
Եթե դուք ռեսուրսներ չեք հատկացնում SYN փաթեթի վրա, այլ պատասխանում եք միայն SYNACK փաթեթով, ապա ինչպե՞ս կարող է սերվերը հասկանալ, որ ավելի ուշ եկած ACK փաթեթը պատկանում է SYN փաթեթին, որը չի պահպանվել: Ի վերջո, հարձակվողը կարող է նաև կեղծ ACK-ներ ստեղծել: SYN թխուկի էությունը կոդավորումն է seqnum կապի պարամետրերը որպես հասցեների, նավահանգիստների և փոփոխվող աղի հաշ: Եթե ACK-ին հաջողվել է հասնել մինչև աղի փոփոխությունը, կարող եք նորից հաշվել հեշը և համեմատել դրա հետ acknum. կեղծ acknum հարձակվողը չի կարող, քանի որ աղն իր մեջ ներառում է գաղտնիքը և չի հասցնի տեսակավորել այն սահմանափակ ալիքի պատճառով:
SYN թխուկները երկար ժամանակ ներդրվել են Linux միջուկում և կարող են նույնիսկ ավտոմատ կերպով միացվել, եթե SYN-ները շատ արագ և մեծ քանակությամբ հայտնվեն:
Կրթական ծրագիր TCP ձեռքսեղմման վերաբերյալ
TCP-ն ապահովում է տվյալների փոխանցումը որպես բայթերի հոսք, օրինակ՝ HTTP հարցումները փոխանցվում են TCP-ով: Հոսքը փոխանցվում է մաս առ մաս փաթեթներով: Բոլոր TCP փաթեթներն ունեն տրամաբանական դրոշներ և 32-բիթանոց հաջորդականության համարներ.
Դրոշների համադրությունը սահմանում է որոշակի փաթեթի դերը: SYN դրոշը նշանակում է, որ սա ուղարկողի առաջին փաթեթն է կապի վրա: ACK դրոշը նշանակում է, որ ուղարկողը ստացել է կապի բոլոր տվյալները մինչև մեկ բայթ: acknum. Փաթեթը կարող է ունենալ մի քանի դրոշներ և անվանվել դրանց համակցության հիման վրա, օրինակ՝ SYNACK փաթեթ:
Հերթականության համարը (seqnum) նշում է տվյալների հոսքի օֆսեթը այս փաթեթում ուղարկված առաջին բայթի համար: Օրինակ, եթե X բայթ տվյալներով առաջին փաթեթում այս թիվը N էր, ապա նոր տվյալներով հաջորդ փաթեթում այն կլինի N+X: Զանգի սկզբում յուրաքանչյուր կողմ պատահականորեն ընտրում է այս համարը:
Հաստատման համարը (acknum) - նույնն է, ինչ seqnum-ը, բայց դա որոշում է ոչ թե փոխանցված բայթի համարը, այլ ստացողի առաջին բայթի թիվը, որն ուղարկողը չի տեսել:
Կապի սկզբում կողմերը պետք է պայմանավորվեն seqnum и acknum. Հաճախորդն իր հետ ուղարկում է SYN փաթեթ seqnum = X. Սերվերը պատասխանում է SYNACK փաթեթով, որտեղ գրում է իր սեփականը seqnum = Y և բացահայտում է acknum = X + 1. Հաճախորդը պատասխանում է SYNACK-ին ACK փաթեթով, որտեղ seqnum = X + 1, acknum = Y + 1. Դրանից հետո սկսվում է իրական տվյալների փոխանցումը:
Եթե զրուցակիցը չի հաստատում փաթեթի ստացումը, TCP-ն այն կրկին ուղարկում է ժամանակի վերջնաժամկետով:
Ինչու՞ SYN թխուկները միշտ չէ, որ օգտագործվում են:
Նախ, եթե SYNACK-ը կամ ACK-ը կորել է, դուք պետք է սպասեք նորից ուղարկելու. կապի հաստատումը դանդաղում է: Երկրորդ, SYN փաթեթում և միայն դրանում: - փոխանցվում են մի շարք տարբերակներ, որոնք ազդում են կապի հետագա շահագործման վրա: Չհիշելով մուտքային SYN փաթեթները, սերվերն այդպիսով անտեսում է այս ընտրանքները, հաջորդ փաթեթներում հաճախորդն այլևս դրանք չի ուղարկի: TCP-ն այս դեպքում կարող է աշխատել, բայց գոնե սկզբնական փուլում կապի որակը կնվազի։
Փաթեթների առումով XDP ծրագիրը պետք է կատարի հետևյալը.
պատասխանել SYN-ին SYNACK-ով քուքի միջոցով;
պատասխանել ACK RST-ով (կապը կոտրել);
թողնել այլ փաթեթներ:
Ալգորիթմի կեղծ կոդը փաթեթների վերլուծության հետ մեկտեղ.
Если это не Ethernet,
пропустить пакет.
Если это не IPv4,
пропустить пакет.
Если адрес в таблице проверенных, (*)
уменьшить счетчик оставшихся проверок,
пропустить пакет.
Если это не TCP,
сбросить пакет. (**)
Если это SYN,
ответить SYN-ACK с cookie.
Если это ACK,
если в acknum лежит не cookie,
сбросить пакет.
Занести в таблицу адрес с N оставшихся проверок. (*)
Ответить RST. (**)
В остальных случаях сбросить пакет.
Մեկ (*) այն կետերը, որտեղ դուք պետք է կառավարեք համակարգի վիճակը, նշված են. առաջին փուլում դուք կարող եք անել առանց դրանց՝ պարզապես իրականացնելով TCP ձեռքսեղմում՝ ստեղծելով SYN թխուկ՝ որպես հաջորդականություն:
Կայքի մեջ (**), քանի դեռ սեղան չունենք, մենք բաց կթողնենք փաթեթը։
TCP ձեռքսեղմման իրականացում
Փաթեթի վերլուծություն և կոդի ստուգում
Մեզ անհրաժեշտ են ցանցի վերնագրի կառուցվածքներ՝ Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) և TCP (uapi/linux/tcp.h). Ես չկարողացա միացնել վերջինը կապված սխալների պատճառով atomic64_t, ես պետք է պատճենեի անհրաժեշտ սահմանումները կոդի մեջ։
Բոլոր գործառույթները, որոնք տարբերվում են C-ում ընթեռնելիության համար, պետք է ներկառուցված լինեն զանգի վայրում, քանի որ eBPF ստուգիչը միջուկում արգելում է ետ թռիչքները, այսինքն՝ իրականում հանգույցները և ֆունկցիաների կանչերը:
Մակրո LOG() անջատում է տպագրությունը թողարկման նախագծում:
Ծրագիրը գործառույթների խողովակաշար է: Յուրաքանչյուրը ստանում է փաթեթ, որում ընդգծված է համապատասխան մակարդակի վերնագիր, օրինակ. process_ether() սպասում է լցվել ether. Դաշտային վերլուծության արդյունքների հիման վրա ֆունկցիան կարող է փաթեթը տեղափոխել ավելի բարձր մակարդակ։ Ֆունկցիայի արդյունքը XDP գործողություն է: Մինչդեռ SYN և ACK մշակողները թույլ են տալիս բոլոր փաթեթները անցնել:
Բանալի տող invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0)կան կատարման ուղիներ, երբ բուֆերի սկզբից տասներեքերորդ բայթը գտնվում է փաթեթից դուրս: Դժվար է նշել ցուցակից, թե որ տողի մասին է խոսքը, բայց կա հրահանգի համար (12) և ապամոնտաժող, որը ցույց է տալիս սկզբնական կոդի տողերը.
ինչից պարզ է դառնում, որ խնդիրն այն է ether. Միշտ այդպես կլիներ։
Պատասխանել SYN-ին
Այս փուլում նպատակն է ստեղծել ճիշտ SYNACK փաթեթ՝ ֆիքսվածով seqnum, որը հետագայում կփոխարինվի SYN թխուկով: Բոլոր փոփոխությունները տեղի են ունենում process_tcp_syn() և շրջակայքը։
Փաթեթի ստուգում
Տարօրինակ կերպով, ահա ամենաուշագրավ տողը, ավելի ճիշտ՝ մեկնաբանություն դրան.
Կոդի առաջին տարբերակը գրելիս օգտագործվել է 5.1 միջուկը, որի ստուգիչի համար տարբերություն կար. data_end и (const void*)ctx->data_end. Գրելու պահին 5.3.1 միջուկն այս խնդիրը չուներ։ Հավանաբար, կոմպիլյատորն այլ կերպ էր մուտք գործում տեղական փոփոխական, քան դաշտը: Բարոյական - մեծ բույնի վրա կոդի պարզեցումը կարող է օգնել:
Ստուգիչի փառքի համար երկարությունների հետագա սովորական ստուգումներ. Օ MAX_CSUM_BYTES ներքևում
Փոխանակեք TCP նավահանգիստները, IP և MAC հասցեները: Ստանդարտ գրադարանը հասանելի չէ XDP ծրագրից, ուստի memcpy() — մակրո, որը թաքցնում է Clang ինտրինսիկան:
IPv4-ի և TCP-ի ստուգման գումարները պահանջում են վերնագրերում ավելացնել բոլոր 16-բիթանոց բառերը, և վերնագրերի չափը գրված է դրանցում, այսինքն՝ կազմման պահին անհայտ է: Սա խնդիր է, քանի որ ստուգիչը չի բաց թողնի սովորական օղակը մինչև սահմանային փոփոխականը: Բայց վերնագրերի չափը սահմանափակ է՝ յուրաքանչյուրը մինչև 64 բայթ: Դուք կարող եք օղակ կազմել ֆիքսված թվով կրկնումներով, որը կարող է վաղ ավարտվել:
Նշում եմ, որ կա RFC 1624 այն մասին, թե ինչպես կարելի է մասնակիորեն վերահաշվարկել ստուգիչ գումարը, եթե փոխվեն միայն փաթեթների ֆիքսված բառերը: Այնուամենայնիվ, մեթոդը համընդհանուր չէ, և իրականացումն ավելի դժվար կլինի պահպանել:
Ստուգիչ գումարի հաշվարկման գործառույթ.
#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;
}
Չնայած նրան size Ստուգվում է կանչող կոդով, երկրորդ ելքի պայմանն անհրաժեշտ է, որպեսզի ստուգիչը կարողանա ապացուցել օղակի ավարտը:
32-բիթանոց բառերի համար իրականացվում է ավելի պարզ տարբերակ.
INTERNAL u32
sum16_32(u32 v) {
return (v >> 16) + (v & 0xffff);
}
Փաստացիորեն վերահաշվարկելով չեկային գումարները և ուղարկելով փաթեթը.
Ֆունկցիա carry() 32-բիթանոց բառերի 16-բիթանոց գումարից կազմում է ստուգիչ գումար՝ համաձայն RFC 791-ի:
TCP ձեռքսեղմման ստուգում
Զտիչը ճիշտ է կապ հաստատում հետ netcat, բաց թողնելով վերջնական ACK-ը, որին Linux-ը պատասխանեց RST փաթեթով, քանի որ ցանցի կույտը SYN չստացավ, այն փոխարկվեց SYNACK-ի և հետ ուղարկվեց, և ՕՀ-ի տեսանկյունից ստացվեց փաթեթ, որը չէր կապված բաց միացումների հետ.
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer
Կարևոր է ստուգել լիարժեք դիմումներով և դիտարկել tcpdump մասին xdp-remote քանի որ, օրինակ, hping3 չի արձագանքում սխալ ստուգումներին:
SYN թխուկ
XDP-ի տեսանկյունից չեկն ինքնին չնչին է։ Հաշվարկի ալգորիթմը պարզունակ է և, հավանաբար, խոցելի է բարդ հարձակվողի համար: Linux միջուկը, օրինակ, օգտագործում է ծածկագրային SipHash, սակայն XDP-ի համար դրա իրականացումը ակնհայտորեն դուրս է այս հոդվածի շրջանակներից:
Հայտնվել է արտաքին փոխազդեցության հետ կապված նոր TODO-ների համար.
XDP ծրագիրը չի կարող պահել cookie_seed (աղի գաղտնի մասը) գլոբալ փոփոխականում ձեզ անհրաժեշտ է միջուկի պահեստ, որի արժեքը պարբերաբար կթարմացվի հուսալի գեներատորից:
Եթե ACK փաթեթում SYN թխուկը համընկնում է, ապա ձեզ հարկավոր չէ հաղորդագրություն տպել, այլ հիշել ստուգված հաճախորդի IP-ն՝ դրանից փաթեթները հետագայում բաց թողնելու համար:
Երբեմն eBPF-ն ընդհանրապես և XDP-ն մասնավորապես ներկայացվում են որպես ադմինիստրատորի առաջադեմ գործիք, քան զարգացման հարթակ: Իրոք, XDP-ն միջուկի փաթեթների մշակմանը խանգարելու գործիք է, և ոչ միջուկի կույտին այլընտրանք, ինչպես DPDK-ն և միջուկի շրջանցման այլ տարբերակներ: Մյուս կողմից, XDP-ն թույլ է տալիս իրականացնել բավականին բարդ տրամաբանություն, որը, ավելին, հեշտ է թարմացնել առանց երթևեկության մշակման դադարի։ Ստուգիչը մեծ խնդիրներ չի ստեղծում, անձամբ ես այդպիսիք չէի հրաժարվի օգտվողների տարածքի կոդի մասերի համար:
Երկրորդ մասում, եթե թեման հետաքրքիր է, մենք կլրացնենք ստուգված հաճախորդների աղյուսակը և կխզենք կապերը, կիրականացնենք հաշվիչներ և կգրենք օգտվողների տարածքի օգտակար ծրագիր՝ ֆիլտրը կառավարելու համար։