BPF mazajiem, pirmā daļa: pagarināts BPF

Sākumā bija tehnoloÄ£ija, un to sauca par BPF. Mēs paskatÄ«jāmies uz viņu iepriekŔējā, Å Ä«s sērijas Vecās DerÄ«bas raksts. 2013. gadā ar Alekseja Starovoitova un Daniela Borkmana pÅ«lēm tika izstrādāta tās uzlabota versija, kas optimizēta modernām 64 bitu iekārtām un iekļauta Linux kodolā. Å o jauno tehnoloÄ£iju Ä«si sauca par iekŔējo BPF, pēc tam pārdēvēja par Extended BPF, un tagad, pēc vairākiem gadiem, visi to vienkārÅ”i sauc par BPF.

Aptuveni runājot, BPF ļauj palaist patvaļīgu lietotāja nodroÅ”inātu kodu Linux kodola telpā, un jaunā arhitektÅ«ra izrādÄ«jās tik veiksmÄ«ga, ka mums bÅ«s nepiecieÅ”ams vēl ducis rakstu, lai aprakstÄ«tu visas tās lietojumprogrammas. (VienÄ«gais, ko izstrādātājiem neveicās labi, kā redzams tālāk sniegtajā veiktspējas kodā, bija pienācÄ«ga logotipa izveide.)

Å ajā rakstā ir aprakstÄ«ta BPF virtuālās maŔīnas struktÅ«ra, kodola saskarnes darbam ar BPF, izstrādes rÄ«ki, kā arÄ« Ä«ss, ļoti Ä«ss pārskats par esoÅ”ajām iespējām, t.i. viss, kas mums nākotnē bÅ«s nepiecieÅ”ams, lai dziļāk izpētÄ«tu BPF praktisko pielietojumu.
BPF mazajiem, pirmā daļa: pagarināts BPF

Raksta kopsavilkums

Ievads BPF arhitektūrā. Pirmkārt, mēs aplūkosim BPF arhitektūru no putna lidojuma un izklāstīsim galvenās sastāvdaļas.

BPF virtuālās maŔīnas reÄ£istri un komandu sistēma. Mums jau ir priekÅ”stats par arhitektÅ«ru kopumā, mēs aprakstÄ«sim BPF virtuālās maŔīnas struktÅ«ru.

BPF objektu dzÄ«ves cikls, bpffs failu sistēma. Å ajā sadaļā sÄ«kāk aplÅ«kosim BPF objektu dzÄ«ves ciklu ā€“ programmas un kartes.

Objektu pārvaldÄ«ba, izmantojot bpf sistēmas zvanu. Ar zināmu izpratni par sistēmu, kas jau ir ieviesta, mēs beidzot aplÅ«kosim, kā izveidot un manipulēt ar objektiem no lietotāja telpas, izmantojot Ä«paÅ”u sistēmas izsaukumu. bpf(2).

ŠŸŠøшŠµŠ¼ ŠæрŠ¾Š³Ń€Š°Š¼Š¼Ń‹ BPF с ŠæŠ¾Š¼Š¾Ń‰ŃŒŃŽ libbpf. Protams, jÅ«s varat rakstÄ«t programmas, izmantojot sistēmas zvanu. Bet tas ir grÅ«ti. Reālistiskākam scenārijam kodolprogrammētāji izstrādāja bibliotēku libbpf. Mēs izveidosim pamata BPF lietojumprogrammas skeletu, ko izmantosim turpmākajos piemēros.

Kodola palÄ«gi. Å eit mēs uzzināsim, kā BPF programmas var piekļūt kodola palÄ«gfunkcijām - rÄ«kam, kas kopā ar kartēm bÅ«tiski paplaÅ”ina jaunā BPF iespējas salÄ«dzinājumā ar klasisko.

Piekļuve kartēm no BPF programmām. Šajā brīdī mēs zinām pietiekami daudz, lai precīzi saprastu, kā mēs varam izveidot programmas, kas izmanto kartes. Un pat ātri ieskatīsimies lieliskajā un varenajā verificētājā.

Izstrādes rīki. Palīdzības sadaļa par to, kā salikt nepiecieŔamos utilītus un kodolu eksperimentiem.

Secinājums. Raksta beigās tie, kas izlasÄ«ja tik tālu, turpmākajos rakstos atradÄ«s motivējoÅ”us vārdus un Ä«su aprakstu par to, kas notiks. UzskaitÄ«sim arÄ« vairākas saites paÅ”mācÄ«bai tiem, kam nav ne vēlÄ“Å”anās, ne iespēju gaidÄ«t turpinājumu.

Ievads BPF arhitektūrā

Pirms sākam apsvērt BPF arhitektÅ«ru, mēs pēdējo reizi (ak) atsauksimies uz klasiskais BPF, kas tika izstrādāta kā atbilde uz RISC maŔīnu parādÄ«Å”anos un atrisināja efektÄ«vas pakeÅ”u filtrÄ“Å”anas problēmu. ArhitektÅ«ra izrādÄ«jās tik veiksmÄ«ga, ka, dzimusi drudžainajos deviņdesmitajos gados Berkeley UNIX, tā tika pārnesta uz lielāko daļu esoÅ”o operētājsistēmu, izdzÄ«voja trakajos divdesmitajos gados un joprojām atrod jaunas lietojumprogrammas.

Jaunais BPF tika izstrādāts, reaģējot uz 64 bitu maŔīnu, mākoņpakalpojumu visuresamÄ«bu un pieaugoÅ”o vajadzÄ«bu pēc rÄ«kiem SDN izveidei (SaprÄ«kots-dprecizēts ntÄ«kloÅ”ana). Kodola tÄ«kla inženieri izstrādāja kā uzlabotu klasiskā BPF aizstājēju, jaunais BPF burtiski seÅ”us mēneÅ”us vēlāk atrada lietojumprogrammas sarežģītajā Linux sistēmu izsekoÅ”anas uzdevumā, un tagad, seÅ”us gadus pēc tā parādÄ«Å”anās, mums bÅ«s nepiecieÅ”ams vesels nākamais raksts, lai. uzskaitiet dažādu veidu programmas.

Smieklīgas bildes

BPF pamatā ir smilÅ”kastes virtuālā maŔīna, kas ļauj palaist ā€œpatvaļīguā€ kodu kodola telpā, neapdraudot droŔību. BPF programmas tiek izveidotas lietotāja telpā, ielādētas kodolā un savienotas ar kādu notikumu avotu. Notikums varētu bÅ«t, piemēram, paketes piegāde uz tÄ«kla interfeisu, kādas kodola funkcijas palaiÅ”ana utt. Pakotnes gadÄ«jumā BPF programmai bÅ«s piekļuve pakotnes datiem un metadatiem (lasÄ«Å”anai un, iespējams, rakstÄ«Å”anai, atkarÄ«bā no programmas veida); kodola funkcijas palaiÅ”anas gadÄ«jumā argumenti funkcija, tostarp norādes uz kodola atmiņu utt.

ApskatÄ«sim Å”o procesu tuvāk. Sākumā parunāsim par pirmo atŔķirÄ«bu no klasiskā BPF, kuras programmas tika rakstÄ«tas montētājā. Jaunajā versijā arhitektÅ«ra tika paplaÅ”ināta, lai programmas varētu rakstÄ«t augsta lÄ«meņa valodās, galvenokārt, protams, C. Å im nolÅ«kam tika izstrādāta aizmugure priekÅ” llvm, kas ļauj Ä£enerēt baitkodu BPF arhitektÅ«rai.

BPF mazajiem, pirmā daļa: pagarināts BPF

BPF arhitektÅ«ra daļēji tika izstrādāta tā, lai tā efektÄ«vi darbotos modernās iekārtās. Lai tas darbotos praksē, BPF baitkods, kad tas ir ielādēts kodolā, tiek tulkots vietējā kodā, izmantojot komponentu, ko sauc par JIT kompilatoru (Just In Time). Tālāk, ja atceraties, klasiskajā BPF programma tika ielādēta kodolā un pievienota notikuma avotam atomiski - viena sistēmas izsaukuma kontekstā. Jaunajā arhitektÅ«rā tas notiek divos posmos ā€“ pirmkārt, kods tiek ielādēts kodolā, izmantojot sistēmas izsaukumu bpf(2)un vēlāk, izmantojot citus mehānismus, kas atŔķiras atkarÄ«bā no programmas veida, programma tiek pievienota notikuma avotam.

Å eit lasÄ«tājam var rasties jautājums: vai tas bija iespējams? Kā tiek garantēta Ŕāda koda izpildes droŔība? Izpildes droŔību mums garantē BPF programmu ielādes posms, ko sauc par verificētāju (angļu valodā Å”o posmu sauc par verificētāju, un es turpināŔu lietot angļu vārdu):

BPF mazajiem, pirmā daļa: pagarināts BPF

Verifier ir statisks analizators, kas nodroÅ”ina, ka programma netraucē normālu kodola darbÄ«bu. Tas, starp citu, nenozÄ«mē, ka programma nevar traucēt sistēmas darbÄ«bu - BPF programmas atkarÄ«bā no veida var lasÄ«t un pārrakstÄ«t kodola atmiņas sadaļas, atgriezt funkciju vērtÄ«bas, apgriezt, pievienot, pārrakstÄ«t un pat pārsÅ«tÄ«t tÄ«kla paketes. Verifier garantē, ka BPF programmas darbÄ«bas laikā kodols nesabruks un programma, kurai saskaņā ar noteikumiem ir rakstÄ«Å”anas piekļuve, piemēram, izejoŔās paketes datiem, nevarēs pārrakstÄ«t kodola atmiņu ārpus paketes. Mēs apskatÄ«sim verificētāju nedaudz sÄ«kāk attiecÄ«gajā sadaļā pēc tam, kad bÅ«sim iepazinuÅ”ies ar visām pārējām BPF sastāvdaļām.

Tātad, ko mēs esam iemācÄ«juÅ”ies lÄ«dz Å”im? Lietotājs raksta programmu C valodā, ielādē to kodolā, izmantojot sistēmas zvanu bpf(2), kur to pārbauda verificētājs un pārveido vietējā baitkodā. Pēc tam tas pats vai cits lietotājs savieno programmu ar notikuma avotu, un tā sāk izpildÄ«t. SāknÄ“Å”anas un savienojuma atdalÄ«Å”ana ir nepiecieÅ”ama vairāku iemeslu dēļ. Pirmkārt, verificētāja palaiÅ”ana ir salÄ«dzinoÅ”i dārga, un, vairākas reizes lejupielādējot vienu un to paÅ”u programmu, mēs tērējam datora laiku. Otrkārt, tieÅ”i tas, kā programma ir savienota, ir atkarÄ«gs no tās veida, un viens pirms gada izstrādāts ā€œuniversālsā€ interfeiss var nebÅ«t piemērots jauna veida programmām. (Lai gan tagad, kad arhitektÅ«ra kļūst arvien nobrieduŔāka, ir doma unificēt Å”o saskarni lÄ«menÄ« libbpf.)

UzmanÄ«gs lasÄ«tājs var pamanÄ«t, ka mēs vēl neesam pabeiguÅ”i ar attēliem. PatieŔām, viss iepriekÅ” minētais neizskaidro, kāpēc BPF bÅ«tiski maina attēlu salÄ«dzinājumā ar klasisko BPF. Divi jauninājumi, kas bÅ«tiski paplaÅ”ina pielietojamÄ«bas jomu, ir iespēja izmantot koplietojamo atmiņu un kodola palÄ«gfunkcijas. BPF koplietojamā atmiņa tiek ieviesta, izmantojot tā sauktās kartes ā€“ koplietojamās datu struktÅ«ras ar noteiktu API. Viņi, iespējams, ieguva Å”o nosaukumu, jo pirmais kartes veids, kas parādÄ«jās, bija hash tabula. Tad parādÄ«jās masÄ«vi, lokālās (per-CPU) jaucēj tabulas un lokālie masÄ«vi, meklÄ“Å”anas koki, kartes, kas satur norādes uz BPF programmām un daudz kas cits. Tagad mÅ«s interesē tas, ka BPF programmām tagad ir iespēja saglabāt stāvokli starp zvaniem un koplietot to ar citām programmām un lietotāja vietu.

Maps var piekļūt no lietotāja procesiem, izmantojot sistēmas zvanu bpf(2), un no BPF programmām, kas darbojas kodolā, izmantojot palīgfunkcijas. Turklāt palīgi pastāv ne tikai darbam ar kartēm, bet arī, lai piekļūtu citām kodola iespējām. Piemēram, BPF programmas var izmantot palīgfunkcijas, lai pārsūtītu paketes uz citām saskarnēm, ģenerētu perf notikumus, piekļūtu kodola struktūrām utt.

BPF mazajiem, pirmā daļa: pagarināts BPF

Rezumējot, BPF nodroÅ”ina iespēju kodola telpā ielādēt patvaļīgu, t.i., pārbaudÄ«tāju, lietotāja kodu. Å is kods var saglabāt stāvokli starp zvaniem un apmainÄ«ties ar datiem ar lietotāja vietu, kā arÄ« tam ir piekļuve kodola apakÅ”sistēmām, ko atļauj Ŕāda veida programmas.

Tas jau ir lÄ«dzÄ«gs kodola moduļu sniegtajām iespējām, salÄ«dzinājumā ar kurām BPF ir dažas priekÅ”rocÄ«bas (protams, var salÄ«dzināt tikai lÄ«dzÄ«gas programmas, piemēram, sistēmas izsekoÅ”ana - ar BPF nevar uzrakstÄ«t patvaļīgu draiveri). Varat atzÄ«mēt zemāku ieejas slieksni (dažas utilÄ«tas, kas izmanto BPF, lietotājam neprasa kodola programmÄ“Å”anas iemaņas vai programmÄ“Å”anas prasmes kopumā), izpildes droŔību (paceliet roku komentāros tiem, kuri, rakstot, nepārkāpa sistēmu vai testÄ“Å”anas moduļi), atomitāte - pārlādējot moduļus, ir dÄ«kstāves laiks, un BPF apakÅ”sistēma nodroÅ”ina, ka neviens notikums netiek palaists garām (taisnÄ«bas labad jāsaka, ka tas neattiecas uz visu veidu BPF programmām).

Šādu iespēju klātbÅ«tne padara BPF par universālu rÄ«ku kodola paplaÅ”ināŔanai, kas apstiprinās arÄ« praksē: BPF tiek pievienots arvien vairāk jaunu programmu veidu, arvien vairāk lielu uzņēmumu izmanto BPF kaujas serveros 24 Ɨ 7, arvien vairāk un vairāk jaunuzņēmumi veido savu biznesu uz risinājumiem, kuru pamatā ir BPF. BPF tiek izmantots visur: aizsardzÄ«bā pret DDoS uzbrukumiem, SDN izveidē (piemēram, kubernetes tÄ«klu ievieÅ”anā), kā galveno sistēmas izsekoÅ”anas rÄ«ku un statistikas savācēju, ielauÅ”anās atklāŔanas sistēmās un smilÅ”kastes sistēmās utt.

Pabeigsim Å”eit raksta pārskata daļu un aplÅ«kosim virtuālo maŔīnu un BPF ekosistēmu sÄ«kāk.

Atkāpe: komunālie maksājumi

Lai varētu palaist piemērus nākamajās sadaļās, jums var bÅ«t nepiecieÅ”amas vairākas utilÄ«tas, vismaz llvm/clang ar bpf atbalstu un bpftool. Sadaļā Izstrādes rÄ«ki Varat izlasÄ«t instrukcijas par utilÄ«tu komplektÄ“Å”anu, kā arÄ« savu kodolu. Å Ä« sadaļa ir ievietota zemāk, lai netraucētu mÅ«su prezentācijas harmoniju.

BPF virtuālās maŔīnas reÄ£istri un instrukciju sistēma

BPF arhitektÅ«ra un komandu sistēma tika izstrādāta, ņemot vērā to, ka programmas tiks rakstÄ«tas C valodā un pēc ielādes kodolā pārtulkotas vietējā kodā. Tāpēc reÄ£istru skaits un komandu kopa tika izvēlēta, ņemot vērā mÅ«sdienu maŔīnu spēju krustpunktu matemātiskā nozÄ«mē. Turklāt programmām tika noteikti dažādi ierobežojumi, piemēram, vēl nesen nebija iespējams rakstÄ«t cilpas un apakÅ”programmas, un instrukciju skaits bija ierobežots lÄ«dz 4096 (tagad priviliģētās programmas var ielādēt lÄ«dz pat miljonam instrukciju).

BPF ir vienpadsmit lietotājiem pieejami 64 bitu reÄ£istri r0Sākot nor10 un programmu skaitÄ«tājs. ReÄ£istrēties r10 satur rāmja rādÄ«tāju un ir tikai lasāms. Programmām ir piekļuve 512 baitu steksam izpildes laikā un neierobežotam koplietojamās atmiņas apjomam karÅ”u veidā.

BPF programmām ir atļauts palaist noteiktu programmas tipa kodola palÄ«gu komplektu un pēdējā laikā arÄ« parastās funkcijas. Katrai izsauktajai funkcijai var bÅ«t lÄ«dz pieciem argumentiem, kas tiek nodoti reÄ£istros r1Sākot nor5, un atgrieÅ”anas vērtÄ«ba tiek nodota r0. Tiek garantēts, ka pēc atgrieÅ”anās no funkcijas reÄ£istru saturs r6Sākot nor9 NemainÄ«sies.

EfektÄ«vai programmu tulkoÅ”anai, reÄ£istri r0Sākot nor11 visām atbalstÄ«tajām arhitektÅ«rām ir unikāli kartētas ar reāliem reÄ£istriem, ņemot vērā paÅ”reizējās arhitektÅ«ras ABI funkcijas. Piemēram, priekÅ” x86_64 reÄ£istros r1Sākot nor5, ko izmanto funkciju parametru nodoÅ”anai, tiek parādÄ«ti rdi, rsi, rdx, rcx, r8, ko izmanto, lai nodotu parametrus funkcijām x86_64. Piemēram, kreisajā pusē esoÅ”ais kods pārvērÅ”as labajā pusē Ŕādi:

1:  (b7) r1 = 1                    mov    $0x1,%rdi
2:  (b7) r2 = 2                    mov    $0x2,%rsi
3:  (b7) r3 = 3                    mov    $0x3,%rdx
4:  (b7) r4 = 4                    mov    $0x4,%rcx
5:  (b7) r5 = 5                    mov    $0x5,%r8
6:  (85) call pc+1                 callq  0x0000000000001ee8

ReÄ£istrēties r0 izmanto arÄ«, lai atgrieztu programmas izpildes rezultātu, un reÄ£istrā r1 programmai tiek nodots rādÄ«tājs uz kontekstu ā€“ atkarÄ«bā no programmas veida tā varētu bÅ«t, piemēram, struktÅ«ra struct xdp_md (XDP) vai struktÅ«ru struct __sk_buff (dažādām tÄ«kla programmām) vai struktÅ«ru struct pt_regs (dažāda veida izsekoÅ”anas programmām) utt.

Tātad, mums bija reÄ£istru komplekts, kodola palÄ«gi, kaudze, konteksta rādÄ«tājs un koplietota atmiņa karÅ”u veidā. Ne jau tas viss ceļojumā bÅ«tu absolÅ«ti nepiecieÅ”ams, bet...

Turpināsim aprakstu un runāsim par komandu sistēmu darbam ar Å”iem objektiem. Visi (GandrÄ«z visi) BPF instrukcijām ir fiksēts 64 bitu izmērs. Ja paskatās uz vienu instrukciju 64 bitu Big Endian maŔīnā, jÅ«s redzēsit

BPF mazajiem, pirmā daļa: pagarināts BPF

Å eit Code - tas ir instrukcijas kodējums, Dst/Src ir attiecÄ«gi uztvērēja un avota kodējums, Off - 16 bitu paraksta atkāpe un Imm ir 32 bitu vesels skaitlis, ko izmanto dažās instrukcijās (lÄ«dzÄ«gi cBPF konstantei K). KodÄ“Å”ana Code ir viens no diviem veidiem:

BPF mazajiem, pirmā daļa: pagarināts BPF

Instrukciju klases 0, 1, 2, 3 nosaka komandas darbam ar atmiņu. Viņi tiek saukti, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, attiecÄ«gi. 4., 7. klase (BPF_ALU, BPF_ALU64) veido ALU instrukciju kopu. 5., 6. klase (BPF_JMP, BPF_JMP32) satur lēkÅ”anas instrukcijas.

Tālākais BPF instrukciju sistēmas izpētes plāns ir Ŕāds: tā vietā, lai rÅ«pÄ«gi uzskaitÄ«tu visas instrukcijas un to parametrus, mēs Å”ajā sadaļā apskatÄ«sim pāris piemērus un no tiem kļūs skaidrs, kā instrukcijas patiesÄ«bā darbojas un kā manuāli izjaukt jebkuru bināro failu BPF. Lai vēlāk rakstā konsolidētu materiālu, tiksimies arÄ« ar individuāliem norādÄ«jumiem sadaļās par Verifier, JIT kompilatoru, klasiskā BPF tulkoÅ”anu, kā arÄ« pētot kartes, izsaukÅ”anas funkcijas utt.

Runājot par atseviŔķiem norādÄ«jumiem, mēs atsauksimies uz galvenajiem failiem bpf.h Šø bpf_common.h, kas nosaka BPF instrukciju ciparu kodus. Studējot arhitektÅ«ru patstāvÄ«gi un/vai analizējot bināros failus, semantiku varat atrast Ŕādos avotos, kas sakārtoti sarežģītÄ«bas secÄ«bā: Neoficiāla eBPF specifikācija, BPF un XDP uzziņu rokasgrāmata, instrukciju komplekts, Dokumentācija/tÄ«kls/filter.txt un, protams, Linux pirmkodā - verificētājs, JIT, BPF tulks.

Piemērs: BPF izjaukÅ”ana galvā

ApskatÄ«sim piemēru, kurā mēs apkopojam programmu readelf-example.c un apskatÄ«t iegÅ«to bināro. Mēs atklāsim sākotnējo saturu readelf-example.c zemāk, pēc tam, kad esam atjaunojuÅ”i tā loÄ£iku no binārajiem kodiem:

$ clang -target bpf -c readelf-example.c -o readelf-example.o -O2
$ llvm-readelf -x .text readelf-example.o
Hex dump of section '.text':
0x00000000 b7000000 01000000 15010100 00000000 ................
0x00000010 b7000000 02000000 95000000 00000000 ................

Pirmā kolonna izvadā readelf ir atkāpe, un tādējādi mūsu programma sastāv no četrām komandām:

Code Dst Src Off  Imm
b7   0   0   0000 01000000
15   0   1   0100 00000000
b7   0   0   0000 02000000
95   0   0   0000 00000000

Komandu kodi ir vienādi b7, 15, b7 Šø 95. Atcerieties, ka vismazāk nozÄ«mÄ«gie trÄ«s biti ir instrukciju klase. MÅ«su gadÄ«jumā visu instrukciju ceturtais bits ir tukÅ”s, tāpēc instrukciju klases ir attiecÄ«gi vienādas ar 7, 5, 7, 5. 7. klase ir BPF_ALU64, un 5 ir BPF_JMP. Abām klasēm norādÄ«jumu formāts ir vienāds (skat. iepriekÅ”), un mēs varam pārrakstÄ«t savu programmu Ŕādi (tajā paŔā laikā mēs pārrakstÄ«sim pārējās kolonnas cilvēka formā):

Op S  Class   Dst Src Off  Imm
b  0  ALU64   0   0   0    1
1  0  JMP     0   1   1    0
b  0  ALU64   0   0   0    2
9  0  JMP     0   0   0    0

DarbÄ«ba b klase ALU64 -Å o BPF_MOV. Tas pieŔķir vērtÄ«bu galamērÄ·a reÄ£istram. Ja bits ir iestatÄ«ts s (avots), tad vērtÄ«ba tiek ņemta no avota reÄ£istra, un, ja, kā mÅ«su gadÄ«jumā, tā nav iestatÄ«ta, tad vērtÄ«ba tiek ņemta no lauka Imm. Tātad pirmajā un treÅ”ajā instrukcijā mēs veicam operāciju r0 = Imm. Turklāt JMP 1. klases darbÄ«ba ir BPF_JEQ (lēkt, ja vienāds). MÅ«su gadÄ«jumā kopÅ” bit S ir nulle, tas salÄ«dzina avota reÄ£istra vērtÄ«bu ar lauku Imm. Ja vērtÄ«bas sakrÄ«t, tad notiek pāreja uz PC + OffKur PC, kā parasti, satur nākamās instrukcijas adresi. Visbeidzot, JMP Class 9 darbÄ«ba ir BPF_EXIT. Å Ä« instrukcija pārtrauc programmu, atgriežoties kodolā r0. Pievienosim tabulai jaunu kolonnu:

Op    S  Class   Dst Src Off  Imm    Disassm
MOV   0  ALU64   0   0   0    1      r0 = 1
JEQ   0  JMP     0   1   1    0      if (r1 == 0) goto pc+1
MOV   0  ALU64   0   0   0    2      r0 = 2
EXIT  0  JMP     0   0   0    0      exit

Mēs to varam pārrakstīt ērtākā formā:

     r0 = 1
     if (r1 == 0) goto END
     r0 = 2
END:
     exit

Ja atceramies, kas ir reģistrā r1 programmai no kodola un reģistrā tiek nodots rādītājs uz kontekstu r0 kodolam tiek atgriezta vērtība, tad mēs varam redzēt, ka, ja konteksta rādītājs ir nulle, tad mēs atgriežam 1, bet pretējā gadījumā - 2. Pārbaudīsim, vai mums ir taisnība, apskatot avotu:

$ cat readelf-example.c
int foo(void *ctx)
{
        return ctx ? 2 : 1;
}

Jā, tā ir bezjēdzÄ«ga programma, taču tā izpaužas tikai četros vienkārÅ”os norādÄ«jumos.

Izņēmuma piemērs: 16 baitu instrukcija

IepriekÅ” minējām, ka dažas instrukcijas aizņem vairāk nekā 64 bitus. Tas attiecas, piemēram, uz instrukcijām lddw (Kods = 0x18 = BPF_LD | BPF_DW | BPF_IMM) ā€” ielādējiet reÄ£istrā dubultvārdu no laukiem Imm. Tas ir fakts Imm ir 32 lielums, un dubultvārds ir 64 biti, tāpēc 64 bitu tÅ«lÄ«tējas vērtÄ«bas ielāde reÄ£istrā vienā 64 bitu instrukcijā nedarbosies. Lai to izdarÄ«tu, tiek izmantotas divas blakus esoŔās instrukcijas, lai laukā saglabātu 64 bitu vērtÄ«bas otro daļu Imm. Piemērs:

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

Binārajā programmā ir tikai divi norādījumi:

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

Mēs tiksimies vēlreiz ar norādÄ«jumiem lddw, kad mēs runājam par pārvietoÅ”anu un darbu ar kartēm.

Piemērs: BPF izjaukÅ”ana, izmantojot standarta rÄ«kus

Tātad, mēs esam iemācÄ«juÅ”ies lasÄ«t BPF bināros kodus un vajadzÄ«bas gadÄ«jumā esam gatavi parsēt jebkuru instrukciju. Tomēr ir vērts teikt, ka praksē ir ērtāk un ātrāk izjaukt programmas, izmantojot standarta rÄ«kus, piemēram:

$ llvm-objdump -d x64.o

Disassembly of section .text:

0000000000000000 <foo>:
 0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
 2: 95 00 00 00 00 00 00 00 exit

BPF objektu dzīves cikls, bpffs failu sistēma

(Es vispirms uzzināju dažas no Ŕajā apakŔnodaļā aprakstītajām detaļām no badoŔanās Aleksejs Starovoitovs iekŔā BPF emuārs.)

BPF objekti - programmas un kartes - tiek veidoti no lietotāja telpas, izmantojot komandas BPF_PROG_LOAD Šø BPF_MAP_CREATE sistēmas zvans bpf(2), mēs runāsim par to, kā tieÅ”i tas notiek nākamajā sadaļā. Tādējādi tiek izveidotas kodola datu struktÅ«ras un katrai no tām refcount (atsauces skaits) ir iestatÄ«ts uz vienu, un lietotājam tiek atgriezts faila deskriptors, kas norāda uz objektu. Pēc tam, kad rokturis ir aizvērts refcount objekts tiek samazināts par vienu, un, kad tas sasniedz nulli, objekts tiek iznÄ«cināts.

Ja programma izmanto kartes, tad refcount Ŕīs kartes pēc programmas ielādes tiek palielinātas par vienu, t.i. to failu deskriptorus var aizvērt no lietotāja procesa un joprojām refcount nekļūs par nulli:

BPF mazajiem, pirmā daļa: pagarināts BPF

Pēc veiksmÄ«gas programmas ielādes mēs to parasti pievienojam kādam notikumu Ä£eneratoram. Piemēram, mēs varam ievietot to tÄ«kla saskarnē, lai apstrādātu ienākoŔās paketes vai savienotu to ar dažām tracepoint kodolā. Å ajā brÄ«dÄ« arÄ« atsauces skaitÄ«tājs palielināsies par vienu, un mēs varēsim aizvērt faila deskriptoru ielādes programmā.

Kas notiks, ja mēs tagad izslēgsim sāknÄ“Å”anas programmu? Tas ir atkarÄ«gs no notikumu Ä£eneratora (āķa) veida. Visi tÄ«kla āķi pastāvēs pēc iekrāvēja pabeigÅ”anas, tie ir tā sauktie globālie āķi. Un, piemēram, izsekoÅ”anas programmas tiks izlaistas pēc tam, kad beigsies process, kas tās izveidoja (un tāpēc tās tiek sauktas par vietējām, no ā€œlokālās uz procesuā€). Tehniski lokālajiem āķiem lietotāja telpā vienmēr ir atbilstoÅ”s faila deskriptors, un tāpēc tie tiek aizvērti, kad process tiek aizvērts, bet globālajiem āķiem tā nav. Nākamajā attēlā, izmantojot sarkanos krustiņus, mēģinu parādÄ«t, kā iekrāvēja programmas pārtraukÅ”ana ietekmē objektu kalpoÅ”anas laiku lokālo un globālo āķu gadÄ«jumā.

BPF mazajiem, pirmā daļa: pagarināts BPF

Kāpēc pastāv atŔķirÄ«ba starp vietējiem un globālajiem āķiem? Dažu veidu tÄ«kla programmu palaiÅ”ana ir jēga bez lietotāja telpas, piemēram, iedomājieties DDoS aizsardzÄ«bu - sāknÄ“Å”anas ielādētājs raksta noteikumus un savieno BPF programmu ar tÄ«kla interfeisu, pēc kura sāknÄ“Å”anas ielādētājs var doties un nogalināt sevi. No otras puses, iedomājieties atkļūdoÅ”anas izsekoÅ”anas programmu, ko desmit minÅ«Å”u laikā uzrakstÄ«jāt uz ceļiem ā€” kad tā bÅ«s pabeigta, jÅ«s vēlētos, lai sistēmā nepaliktu nekādi atkritumi, un vietējie āķi to nodroÅ”inās.

No otras puses, iedomājieties, ka vēlaties izveidot savienojumu ar izsekoÅ”anas punktu kodolā un apkopot statistiku daudzu gadu garumā. Å ajā gadÄ«jumā jÅ«s vēlaties pabeigt lietotāja daļu un laiku pa laikam atgriezties pie statistikas. Bpf failu sistēma nodroÅ”ina Å”o iespēju. Tā ir tikai atmiņā esoÅ”a pseido failu sistēma, kas ļauj izveidot failus, kas atsaucas uz BPF objektiem un tādējādi palielina refcount objektus. Pēc tam iekrāvējs var iziet, un tā izveidotie objekti paliks dzÄ«vi.

BPF mazajiem, pirmā daļa: pagarināts BPF

Failu izveide bpffs, kas atsaucas uz BPF objektiem, tiek saukta par "piesprauÅ”anu" (kā Å”ajā frāzē: "process var piespraust BPF programmu vai karti"). Failu objektu izveide BPF objektiem ir jēga ne tikai lokālo objektu dzÄ«ves pagarināŔanai, bet arÄ« globālo objektu lietojamÄ«bas dēļ ā€“ atgriežoties pie piemēra ar globālo DDoS aizsardzÄ«bas programmu, mēs vēlamies, lai varētu nākt un apskatÄ«t statistiku. laiku pa laikam.

BPF failu sistēma parasti ir iebÅ«vēta /sys/fs/bpf, bet to var uzstādÄ«t arÄ« lokāli, piemēram, Ŕādi:

$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

Failu sistēmu nosaukumi tiek izveidoti, izmantojot komandu BPF_OBJ_PIN BPF sistēmas izsaukums. Lai ilustrētu, paņemsim programmu, kompilējiet to, augÅ”upielādēsim un piespraužam bpffs. MÅ«su programma nedara neko noderÄ«gu, mēs tikai parādām kodu, lai jÅ«s varētu reproducēt piemēru:

$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
        return 0;
}

char _license[] __attribute__((section("license"), used)) = "GPL";

Kompilēsim Å”o programmu un izveidosim lokālo failu sistēmas kopiju bpffs:

$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

Tagad lejupielādēsim mÅ«su programmu, izmantojot utilÄ«tu bpftool un apskatiet pievienotos sistēmas zvanus bpf(2) (no strace izvades noņemtas dažas neatbilstoÅ”as ā€‹ā€‹rindas):

$ sudo strace -e bpf bpftool prog load ./test.o bpf-mountpoint/test
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="test", ...}, 120) = 3
bpf(BPF_OBJ_PIN, {pathname="bpf-mountpoint/test", bpf_fd=3}, 120) = 0

Å eit mēs esam ielādējuÅ”i programmu, izmantojot BPF_PROG_LOAD, saņēma faila deskriptoru no kodola 3 un izmantojot komandu BPF_OBJ_PIN piesprauda Å”o faila deskriptoru kā failu "bpf-mountpoint/test". Pēc tam sāknÄ“Å”anas programma bpftool pabeidza darbu, bet mÅ«su programma palika kodolā, lai gan mēs to nepievienojām nevienam tÄ«kla interfeisam:

$ sudo bpftool prog | tail -3
783: xdp  name test  tag 5c8ba0cf164cb46c  gpl
        loaded_at 2020-05-05T13:27:08+0000  uid 0
        xlated 24B  jited 41B  memlock 4096B

Faila objektu varam dzēst parasti unlink(2) un pēc tam atbilstoŔā programma tiks dzēsta:

$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory

Objektu dzēŔana

Runājot par objektu dzÄ“Å”anu, jāprecizē, ka pēc tam, kad bÅ«sim atvienojuÅ”i programmu no āķa (notikumu Ä£eneratora), neviens jauns notikums neizraisÄ«s tās palaiÅ”anu, taču visas paÅ”reizējās programmas eksemplāri tiks pabeigti parastajā secÄ«bā. .

Daži BPF programmu veidi ļauj nomainÄ«t programmu lidojuma laikā, t.i. nodroÅ”ināt secÄ«bas atomitāti replace = detach old program, attach new program. Å ajā gadÄ«jumā visas programmas vecās versijas aktÄ«vās instances beigs savu darbu, un no jaunās programmas tiks izveidoti jauni notikumu apstrādātāji, un ā€œatomicityā€ Å”eit nozÄ«mē, ka neviens notikums netiks palaists garām.

Programmu pievienoŔana notikumu avotiem

Å ajā rakstā mēs atseviŔķi neaprakstÄ«sim programmu savienoÅ”anu ar notikumu avotiem, jo ā€‹ā€‹ir jēga to pētÄ«t konkrēta programmas veida kontekstā. Cm. piemērs zemāk, kurā mēs parādām, kā tiek savienotas tādas programmas kā XDP.

ManipulÄ“Å”ana ar objektiem, izmantojot bpf sistēmas izsaukumu

BPF programmas

Visi BPF objekti tiek izveidoti un pārvaldÄ«ti no lietotāja vietas, izmantojot sistēmas zvanu bpf, kam ir Ŕāds prototips:

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

LÅ«k, komanda cmd ir viena no tipa vērtÄ«bām enum bpf_cmd, attr ā€” norāde uz parametriem konkrētai programmai un size ā€” objekta izmērs atbilstoÅ”i rādÄ«tājam, t.i. parasti Å”is sizeof(*attr). Kodolā 5.8 sistēmas izsaukums bpf atbalsta 34 dažādas komandas un noteikt union bpf_attr aizņem 200 rindas. Bet mums tas nevajadzētu nobiedēt, jo mēs iepazÄ«simies ar komandām un parametriem vairāku rakstu laikā.

Sāksim ar komandu BPF_PROG_LOAD, kas veido BPF programmas ā€“ paņem BPF instrukciju kopu un ielādē to kodolā. Ielādes brÄ«dÄ« tiek palaists verificētājs, un pēc tam JIT kompilators un pēc veiksmÄ«gas izpildes programmas faila deskriptors tiek atgriezts lietotājam. To, kas ar viņu notiek tālāk, mēs redzējām iepriekŔējā sadaļā par BPF objektu dzÄ«ves ciklu.

Tagad mēs uzrakstÄ«sim pielāgotu programmu, kas ielādēs vienkārÅ”u BPF programmu, bet vispirms mums ir jāizlemj, kāda veida programmu mēs vēlamies ielādēt - mums bÅ«s jāizvēlas Tips un Ŕī tipa ietvaros uzrakstiet programmu, kas izturēs verificētāja pārbaudi. Tomēr, lai nesarežģītu procesu, Å”eit ir gatavs risinājums: mēs ņemsim tādu programmu kā BPF_PROG_TYPE_XDP, kas atgriezÄ«s vērtÄ«bu XDP_PASS (izlaist visus iepakojumus). BPF montētājā tas izskatās ļoti vienkārÅ”i:

r0 = 2
exit

Pēc tam, kad esam izlēmuÅ”i ka mēs augÅ”upielādēsim, mēs varam jums pastāstÄ«t, kā mēs to darÄ«sim:

#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

static inline __u64 ptr_to_u64(const void *ptr)
{
        return (__u64) (unsigned long) ptr;
}

int main(void)
{
    struct bpf_insn insns[] = {
        {
            .code = BPF_ALU64 | BPF_MOV | BPF_K,
            .dst_reg = BPF_REG_0,
            .imm = XDP_PASS
        },
        {
            .code = BPF_JMP | BPF_EXIT
        },
    };

    union bpf_attr attr = {
        .prog_type = BPF_PROG_TYPE_XDP,
        .insns     = ptr_to_u64(insns),
        .insn_cnt  = sizeof(insns)/sizeof(insns[0]),
        .license   = ptr_to_u64("GPL"),
    };

    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

Interesanti notikumi programmā sākas ar masÄ«va definÄ«ciju insns - mÅ«su BPF programma maŔīnkodā. Å ajā gadÄ«jumā katrs BPF programmas norādÄ«jums tiek iesaiņots struktÅ«rā bpf_insn. Pirmais elements insns atbilst instrukcijām r0 = 2, otrais - exit.

Atkāpties. Kodols nosaka ērtākus makro maŔīnkodu rakstÄ«Å”anai un kodola galvenes faila izmantoÅ”anai tools/include/linux/filter.h mēs varētu rakstÄ«t

struct bpf_insn insns[] = {
    BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
    BPF_EXIT_INSN()
};

Bet tā kā BPF programmu rakstÄ«Å”ana vietējā kodā ir nepiecieÅ”ama tikai kodola testu un rakstu par BPF rakstÄ«Å”anai, Å”o makro neesamÄ«ba izstrādātāja dzÄ«vi Ä«sti nesarežģī.

Pēc BPF programmas definÄ“Å”anas mēs pārejam pie tās ielādes kodolā. MÅ«su minimālisma parametru komplekts attr ietver programmas veidu, instrukciju kopu un skaitu, nepiecieÅ”amo licenci un nosaukumu "woo", ko mēs izmantojam, lai pēc lejupielādes sistēmā atrastu savu programmu. Programma, kā solÄ«ts, tiek ielādēta sistēmā, izmantojot sistēmas izsaukumu bpf.

Programmas beigās mēs nonākam bezgalīgā cilpā, kas simulē lietderīgo slodzi. Bez tā kodols iznīcinās programmu, kad tiks aizvērts faila deskriptors, ko mums atgrieza sistēmas izsaukums. bpf, un mēs to neredzēsim sistēmā.

Nu, mēs esam gatavi testÄ“Å”anai. Saliksim un palaidÄ«sim programmu zem stracelai pārbaudÄ«tu, vai viss darbojas tā, kā vajadzētu:

$ clang -g -O2 simple-prog.c -o simple-prog

$ sudo strace ./simple-prog
execve("./simple-prog", ["./simple-prog"], 0x7ffc7b553480 /* 13 vars */) = 0
...
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0x7ffe03c4ed50, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_V
ERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(

Viss ir kārtībā, bpf(2) atdeva mums rokturi 3, un mēs iegājām bezgalīgā cilpā ar pause(). Mēģināsim atrast mūsu programmu sistēmā. Lai to izdarītu, mēs pāriesim uz citu termināli un izmantosim utilītu bpftool:

# bpftool prog | grep -A3 woo
390: xdp  name woo  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-31T24:66:44+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        pids simple-prog(10381)

Mēs redzam, ka sistēmā ir ielādēta programma woo kura globālais ID ir 390 un paÅ”laik notiek simple-prog ir atvērts faila deskriptors, kas norāda uz programmu (un ja simple-prog tad pabeigs darbu woo pazudÄ«s). Kā gaidÄ«ts, programma woo aizņem 16 baitus - divas instrukcijas - bināro kodu BPF arhitektÅ«rā, bet savā dzimtajā formā (x86_64) tas ir jau 40 baiti. ApskatÄ«sim mÅ«su programmu tās sākotnējā formā:

# bpftool prog dump xlated id 390
   0: (b7) r0 = 2
   1: (95) exit

nekādu pārsteigumu. Tagad apskatīsim JIT kompilatora ģenerēto kodu:

# bpftool prog dump jited id 390
bpf_prog_3b185187f1855c4c_woo:
   0:   nopl   0x0(%rax,%rax,1)
   5:   push   %rbp
   6:   mov    %rsp,%rbp
   9:   sub    $0x0,%rsp
  10:   push   %rbx
  11:   push   %r13
  13:   push   %r14
  15:   push   %r15
  17:   pushq  $0x0
  19:   mov    $0x2,%eax
  1e:   pop    %rbx
  1f:   pop    %r15
  21:   pop    %r14
  23:   pop    %r13
  25:   pop    %rbx
  26:   leaveq
  27:   retq

nav īpaŔi efektīva priekŔ exit(2), bet godīgi sakot, mūsu programma ir pārāk vienkārŔa, un netriviālām programmām, protams, ir nepiecieŔams JIT kompilatora pievienotais prologs un epilogs.

kartes

BPF programmas var izmantot strukturētus atmiņas apgabalus, kas ir pieejami gan citām BPF programmām, gan programmām lietotāja telpā. Å os objektus sauc par kartēm, un Å”ajā sadaļā mēs parādÄ«sim, kā ar tiem manipulēt, izmantojot sistēmas zvanu bpf.

Uzreiz teiksim, ka karÅ”u iespējas neaprobežojas tikai ar piekļuvi koplietotajai atmiņai. Ir Ä«paÅ”as nozÄ«mes kartes, kas satur, piemēram, norādes uz BPF programmām vai norādes uz tÄ«kla saskarnēm, kartes darbam ar perf notikumiem utt. Par tiem Å”eit nerunāsim, lai nemulsinātu lasÄ«tāju. Bez tam mēs ignorējam sinhronizācijas problēmas, jo tas nav svarÄ«gi mÅ«su piemēros. Pilns pieejamo karÅ”u veidu saraksts ir atrodams <linux/bpf.h>, un Å”ajā sadaļā kā piemēru ņemsim vēsturiski pirmo veidu, hash tabulu BPF_MAP_TYPE_HASH.

Ja jÅ«s izveidojat hash tabulu, piemēram, C++ valodā, jÅ«s teiktu unordered_map<int,long> woo, kas krievu valodā nozÄ«mē ā€œMan vajag galdu woo neierobežots izmērs, kura atslēgas ir tipa int, un vērtÄ«bas ir veids long" Lai izveidotu BPF hash tabulu, mums ir jādara tas pats, izņemot to, ka mums ir jānorāda maksimālais tabulas izmērs, un tā vietā, lai norādÄ«tu atslēgu un vērtÄ«bu veidus, mums ir jānorāda to lielums baitos . Lai izveidotu kartes, izmantojiet komandu BPF_MAP_CREATE sistēmas zvans bpf. ApskatÄ«sim vairāk vai mazāk minimālu programmu, kas izveido karti. Pēc iepriekŔējās programmas, kas ielādē BPF programmas, Ŕī jums Ŕķiet vienkārÅ”a:

$ cat simple-map.c
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

int main(void)
{
    union bpf_attr attr = {
        .map_type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(int),
        .value_size = sizeof(int),
        .max_entries = 4,
    };
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

Å eit mēs definējam parametru kopu attr, kurā sakām: ā€œMan ir vajadzÄ«ga jaucēj tabula ar taustiņiem un lieluma vērtÄ«bām sizeof(int), kurā es varu ievietot ne vairāk kā četrus elementus." Veidojot BPF kartes, var norādÄ«t citus parametrus, piemēram, tāpat kā piemērā ar programmu, objekta nosaukumu norādÄ«jām kā "woo".

Apkoposim un palaidīsim programmu:

$ clang -g -O2 simple-map.c -o simple-map
$ sudo strace ./simple-map
execve("./simple-map", ["./simple-map"], 0x7ffd40a27070 /* 14 vars */) = 0
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_HASH, key_size=4, value_size=4, max_entries=4, map_name="woo", ...}, 72) = 3
pause(

Šeit ir sistēmas izsaukums bpf(2) atgrieza mums deskriptora kartes numuru 3 un pēc tam programma, kā paredzēts, gaida turpmākus norādījumus sistēmas izsaukumā pause(2).

Tagad nosÅ«tÄ«sim savu programmu uz fonu vai atveram citu termināli un apskatÄ«sim mÅ«su objektu, izmantojot utilÄ«tu bpftool (mēs varam atŔķirt savu karti no citām pēc tās nosaukuma):

$ sudo bpftool map
...
114: hash  name woo  flags 0x0
        key 4B  value 4B  max_entries 4  memlock 4096B
...

Skaitlis 114 ir mÅ«su objekta globālais ID. Jebkura sistēmas programma var izmantot Å”o ID, lai atvērtu esoÅ”u karti, izmantojot komandu BPF_MAP_GET_FD_BY_ID sistēmas zvans bpf.

Tagad mēs varam spēlēt ar mūsu hash tabulu. Apskatīsim tā saturu:

$ sudo bpftool map dump id 114
Found 0 elements

TukÅ”s. Ieliksim tajā vērtÄ«bu hash[1] = 1:

$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0

Apskatīsim tabulu vēlreiz:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
Found 1 element

Urrā! Mums izdevās pievienot vienu elementu. Ņemiet vērā, ka mums ir jāstrādā baitu lÄ«menÄ«, lai to izdarÄ«tu, kopÅ” bptftool nezina, kāda veida vērtÄ«bas ir hash tabulā. (Å Ä«s zināŔanas viņai var nodot, izmantojot BTF, bet vairāk par to tagad.)

Kā tieŔi bpftool nolasa un pievieno elementus? Paskatīsimies zem pārsega:

$ sudo strace -e bpf bpftool map dump id 114
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=NULL, next_key=0x55856ab65280}, 120) = 0
bpf(BPF_MAP_LOOKUP_ELEM, {map_fd=3, key=0x55856ab65280, value=0x55856ab652a0}, 120) = 0
key: 01 00 00 00  value: 01 00 00 00
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=0x55856ab65280, next_key=0x55856ab65280}, 120) = -1 ENOENT

Vispirms mēs atvērām karti pēc tās globālā ID, izmantojot komandu BPF_MAP_GET_FD_BY_ID Šø bpf(2) atgrieza mums deskriptoru 3. Tālāk izmantojot komandu BPF_MAP_GET_NEXT_KEY pirmo atslēgu tabulā atradām garāmejot NULL kā rādÄ«tājs uz "iepriekŔējo" taustiņu. Ja mums ir atslēga, mēs varam darÄ«t BPF_MAP_LOOKUP_ELEMkas atgriež rādÄ«tājam vērtÄ«bu value. Nākamais solis ir mēģināt atrast nākamo elementu, nododot rādÄ«tāju uz paÅ”reizējo atslēgu, bet mÅ«su tabulā ir tikai viens elements un komanda BPF_MAP_GET_NEXT_KEY atgriežas ENOENT.

Labi, mainÄ«sim vērtÄ«bu ar 1. atslēgu. Pieņemsim, ka mÅ«su biznesa loÄ£ika prasa reÄ£istrāciju hash[1] = 2:

$ sudo strace -e bpf bpftool map update id 114 key 1 0 0 0 value 2 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x55dcd72be260, value=0x55dcd72be280, flags=BPF_ANY}, 120) = 0

Kā gaidÄ«ts, tas ir ļoti vienkārÅ”i: komanda BPF_MAP_GET_FD_BY_ID atver mÅ«su karti pēc ID un komandas BPF_MAP_UPDATE_ELEM pārraksta elementu.

Tātad, izveidojot hash tabulu no vienas programmas, mēs varam lasÄ«t un rakstÄ«t tās saturu no citas. Ņemiet vērā: ja mēs to varējām izdarÄ«t no komandrindas, tad to var izdarÄ«t jebkura cita sistēmas programma. Papildus iepriekÅ” aprakstÄ«tajām komandām darbam ar kartēm no lietotāja vietas, Ŕādi:

  • BPF_MAP_LOOKUP_ELEM: atrast vērtÄ«bu pēc atslēgas
  • BPF_MAP_UPDATE_ELEM: atjaunināt/izveidot vērtÄ«bu
  • BPF_MAP_DELETE_ELEM: noņemt atslēgu
  • BPF_MAP_GET_NEXT_KEY: atrodiet nākamo (vai pirmo) taustiņu
  • BPF_MAP_GET_NEXT_ID: ļauj pārlÅ«kot visas esoŔās kartes, tā tas darbojas bpftool map
  • BPF_MAP_GET_FD_BY_ID: atver esoÅ”u karti pēc tās globālā ID
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: atomiski atjaunināt objekta vērtÄ«bu un atgriezt veco
  • BPF_MAP_FREEZE: padarÄ«t karti nemainÄ«gu no lietotāja telpas (Å”o darbÄ«bu nevar atsaukt)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: masu operācijas. Piemēram, BPF_MAP_LOOKUP_AND_DELETE_BATCH - tas ir vienÄ«gais uzticamais veids, kā nolasÄ«t un atiestatÄ«t visas vērtÄ«bas no kartes

Ne visas Ŕīs komandas darbojas visiem karÅ”u veidiem, taču kopumā darbs ar cita veida kartēm no lietotāja vietas izskatās tieÅ”i tāds pats kā darbs ar jaucēj tabulām.

Kārtības labad pabeigsim mūsu hash tabulas eksperimentus. Atcerieties, ka mēs izveidojām tabulu, kurā var būt līdz četrām atslēgām? Pievienosim vēl dažus elementus:

$ sudo bpftool map update id 114 key 2 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 3 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 4 0 0 0 value 1 0 0 0

Tik tālu, labi:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
key: 02 00 00 00  value: 01 00 00 00
key: 04 00 00 00  value: 01 00 00 00
key: 03 00 00 00  value: 01 00 00 00
Found 4 elements

Mēģināsim pievienot vēl vienu:

$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long

Kā jau gaidīts, mums neizdevās. Apskatīsim kļūdu sīkāk:

$ sudo strace -e bpf bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=80, info=0x7ffe6c626da0}}, 120) = 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x56049ded5260, value=0x56049ded5280, flags=BPF_ANY}, 120) = -1 E2BIG (Argument list too long)
Error: update failed: Argument list too long
+++ exited with 255 +++

Viss ir kārtībā: kā gaidīts, komanda BPF_MAP_UPDATE_ELEM mēģina izveidot jaunu, piekto, atslēgu, bet avarē E2BIG.

Tātad, mēs varam izveidot un ielādēt BPF programmas, kā arÄ« izveidot un pārvaldÄ«t kartes no lietotāja vietas. Tagad ir loÄ£iski aplÅ«kot, kā mēs varam izmantot kartes no paŔām BPF programmām. Mēs par to varētu runāt grÅ«ti lasāmu programmu valodā maŔīnu makro kodos, bet patiesÄ«bā ir pienācis laiks parādÄ«t, kā patiesÄ«bā tiek rakstÄ«tas un uzturētas BPF programmas - izmantojot libbpf.

(Lasītājiem, kuri nav apmierināti ar zema līmeņa piemēra trūkumu: mēs detalizēti analizēsim programmas, kas izmanto kartes un palīgfunkcijas, kas izveidotas, izmantojot libbpf un pastāstīt, kas notiek instrukciju līmenī. Neapmierinātajiem lasītājiem ļoti daudz, mēs pievienojām piemērs attiecīgajā raksta vietā.)

BPF programmu rakstīŔana, izmantojot libbpf

BPF programmu rakstÄ«Å”ana, izmantojot maŔīnas kodus, var bÅ«t interesanta tikai pirmajā reizē, un tad iestājas sāta sajÅ«ta. Å ajā brÄ«dÄ« jums jāpievērÅ” uzmanÄ«ba llvm, kam ir aizmugursistēma koda Ä£enerÄ“Å”anai BPF arhitektÅ«rai, kā arÄ« bibliotēka libbpf, kas ļauj rakstÄ«t BPF lietojumprogrammu lietotāja pusi un ielādēt BPF programmu kodu, kas Ä£enerēts, izmantojot llvm/clang.

Faktiski, kā mēs redzēsim Å”ajā un turpmākajos rakstos, libbpf diezgan daudz strādā bez tā (vai lÄ«dzÄ«giem rÄ«kiem - iproute2, libbcc, libbpf-gou.c.) dzÄ«vot nav iespējams. Viena no projekta galvenajām iezÄ«mēm libbpf ir BPF CO-RE (Compile Once, Run Everywhere) - projekts, kas ļauj rakstÄ«t BPF programmas, kas ir pārnēsājamas no viena kodola uz otru, ar iespēju darboties dažādās API (piemēram, mainoties kodola struktÅ«rai no versijas uz versiju). Lai varētu strādāt ar CO-RE, jÅ«su kodolam jābÅ«t kompilētam ar BTF atbalstu (kā to izdarÄ«t, mēs aprakstām sadaļā Izstrādes rÄ«ki. JÅ«s varat pārbaudÄ«t, vai jÅ«su kodols ir veidots ar BTF vai ne ļoti vienkārÅ”i - izmantojot Ŕādu failu:

$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux

Å ajā failā tiek glabāta informācija par visiem kodolā izmantotajiem datu tipiem, un tas tiek izmantots visos mÅ«su piemēros, izmantojot libbpf. SÄ«kāk par CO-RE mēs runāsim nākamajā rakstā, bet Å”ajā - vienkārÅ”i izveidojiet sev kodolu CONFIG_DEBUG_INFO_BTF.

Bibliotēka libbpf dzÄ«vo tieÅ”i direktorijā tools/lib/bpf kodols un tā izstrāde tiek veikta, izmantojot adresātu sarakstu [email protected]. Tomēr ārpus kodola dzÄ«vojoÅ”o lietojumprogrammu vajadzÄ«bām tiek uzturēta atseviŔķa repozitorija https://github.com/libbpf/libbpf kurā kodola bibliotēka tiek atspoguļota lasÄ«Å”anas piekļuvei vairāk vai mazāk tāda, kāda tā ir.

Å ajā sadaļā apskatÄ«sim, kā varat izveidot projektu, kas izmanto libbpf, uzrakstÄ«sim vairākas (vairāk vai mazāk bezjēdzÄ«gas) testa programmas un detalizēti analizēsim, kā tas viss darbojas. Tas ļaus mums turpmākajās sadaļās vienkārŔāk izskaidrot, kā BPF programmas mijiedarbojas ar kartēm, kodola palÄ«giem, BTF utt.

Parasti projektos izmanto libbpf pievienojiet GitHub repozitoriju kā git apakÅ”moduli, mēs darÄ«sim to paÅ”u:

$ mkdir /tmp/libbpf-example
$ cd /tmp/libbpf-example/
$ git init-db
Initialized empty Git repository in /tmp/libbpf-example/.git/
$ git submodule add https://github.com/libbpf/libbpf.git
Cloning into '/tmp/libbpf-example/libbpf'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 3354 (delta 101), reused 118 (delta 79), pack-reused 3154
Receiving objects: 100% (3354/3354), 2.05 MiB | 10.22 MiB/s, done.
Resolving deltas: 100% (2176/2176), done.

Ejot uz libbpf ļoti vienkārŔi:

$ cd libbpf/src
$ mkdir build
$ OBJDIR=build DESTDIR=root make -s install
$ find root
root
root/usr
root/usr/include
root/usr/include/bpf
root/usr/include/bpf/bpf_tracing.h
root/usr/include/bpf/xsk.h
root/usr/include/bpf/libbpf_common.h
root/usr/include/bpf/bpf_endian.h
root/usr/include/bpf/bpf_helpers.h
root/usr/include/bpf/btf.h
root/usr/include/bpf/bpf_helper_defs.h
root/usr/include/bpf/bpf.h
root/usr/include/bpf/libbpf_util.h
root/usr/include/bpf/libbpf.h
root/usr/include/bpf/bpf_core_read.h
root/usr/lib64
root/usr/lib64/libbpf.so.0.1.0
root/usr/lib64/libbpf.so.0
root/usr/lib64/libbpf.a
root/usr/lib64/libbpf.so
root/usr/lib64/pkgconfig
root/usr/lib64/pkgconfig/libbpf.pc

MÅ«su nākamais plāns Å”ajā sadaļā ir Ŕāds: mēs uzrakstÄ«sim tādu BPF programmu kā BPF_PROG_TYPE_XDP, tāpat kā iepriekŔējā piemērā, bet C, mēs to apkopojam, izmantojot clang, un uzrakstiet palÄ«gprogrammu, kas to ielādēs kodolā. Nākamajās sadaļās mēs paplaÅ”ināsim gan BPF programmas, gan asistentu programmas iespējas.

Piemērs: pilnvērtīgas lietojumprogrammas izveide, izmantojot libbpf

Sākumā mēs izmantojam failu /sys/kernel/btf/vmlinux, kas tika minēts iepriekÅ”, un izveidojiet tā ekvivalentu galvenes faila formā:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

Å ajā failā tiks saglabātas visas mÅ«su kodolā pieejamās datu struktÅ«ras, piemēram, Ŕādi kodolā tiek definēta IPv4 galvene:

$ grep -A 12 'struct iphdr {' vmlinux.h
struct iphdr {
    __u8 ihl: 4;
    __u8 version: 4;
    __u8 tos;
    __be16 tot_len;
    __be16 id;
    __be16 frag_off;
    __u8 ttl;
    __u8 protocol;
    __sum16 check;
    __be32 saddr;
    __be32 daddr;
};

Tagad mēs rakstīsim savu BPF programmu C valodā:

$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
        return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Lai gan mÅ«su programma izrādÄ«jās ļoti vienkārÅ”a, mums joprojām ir jāpievērÅ” uzmanÄ«ba daudzām detaļām. Pirmkārt, pirmais iekļautais galvenes fails ir vmlinux.h, kuru mēs tikko Ä£enerējām, izmantojot bpftool btf dump - tagad mums nav jāinstalē kodola galvenes pakotne, lai uzzinātu, kā izskatās kodola struktÅ«ras. Tālāk norādÄ«tais galvenes fails tiek saņemts no bibliotēkas libbpf. Tagad mums tas ir nepiecieÅ”ams tikai makro definÄ“Å”anai SEC, kas nosÅ«ta rakstzÄ«mi uz attiecÄ«go ELF objekta faila sadaļu. MÅ«su programma ir ietverta sadaļā xdp/simple, kur pirms slÄ«psvÄ«tras mēs definējam programmas veidu BPF - tā ir izmantota vienoÅ”anās libbpf, pamatojoties uz sadaļas nosaukumu, startÄ“Å”anas laikā tas aizstās pareizo veidu bpf(2). BPF programma pati par sevi ir C - ļoti vienkārÅ”s un sastāv no vienas rindas return XDP_PASS. Visbeidzot, atseviŔķa sadaļa "license" satur licences nosaukumu.

Mēs varam kompilēt savu programmu, izmantojot llvm/clang, versija >= 10.0.0 vai, vēl labāk, jaunāka (skatiet sadaļu Izstrādes rīki):

$ clang --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git afc287e0abec710398465ee1f86237513f2b5091)
...

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o

Starp interesantajām iezÄ«mēm: mēs norādām mērÄ·a arhitektÅ«ru -target bpf un ceļŔ uz galvenēm libbpf, kuru mēs nesen instalējām. Tāpat neaizmirstiet par -O2, bez Ŕīs iespējas nākotnē jÅ«s varētu sagaidÄ«t pārsteigumi. ApskatÄ«sim mÅ«su kodu, vai mums izdevās uzrakstÄ«t programmu, kuru gribējām?

$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       r0 = 2
       1:       exit

Jā, izdevās! Tagad mums ir binārs fails ar programmu, un mēs vēlamies izveidot lietojumprogrammu, kas to ielādēs kodolā. Šim nolūkam bibliotēka libbpf piedāvā mums divas iespējas - izmantot zemāka līmeņa API vai augstāka līmeņa API. Mēs iesim otro ceļu, jo mēs vēlamies iemācīties rakstīt, ielādēt un savienot BPF programmas ar minimālu piepūli to turpmākajai izpētei.

Pirmkārt, mums ir jāģenerē mÅ«su programmas ā€œskeletsā€ no tās binārā faila, izmantojot to paÅ”u utilÄ«tu bpftool ā€” BPF pasaules Å veices nazis (ko var uztvert burtiski, jo Daniels Borkmans, viens no BPF radÄ«tājiem un uzturētājiem, ir Å”veicietis):

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h

Failā xdp-simple.skel.h satur mÅ«su programmas bināro kodu un funkcijas mÅ«su objekta pārvaldÄ«Å”anai - ielādei, pievienoÅ”anai, dzÄ“Å”anai. MÅ«su vienkārÅ”ajā gadÄ«jumā tas izskatās kā pārspÄ«lējums, taču tas darbojas arÄ« tad, ja objekta failā ir daudz BPF programmu un karÅ”u, un, lai ielādētu Å”o milzÄ«go ELF, mums vienkārÅ”i jāģenerē skelets un jāizsauc viena vai divas funkcijas no pielāgotās lietojumprogrammas. raksta. TÅ«lÄ«t turpināsim.

Stingri sakot, mūsu iekrāvēja programma ir triviāla:

#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    pause();

    xdp_simple_bpf__destroy(obj);
}

Šeit struct xdp_simple_bpf definēts failā xdp-simple.skel.h un apraksta mūsu objekta failu:

struct xdp_simple_bpf {
    struct bpf_object_skeleton *skeleton;
    struct bpf_object *obj;
    struct {
        struct bpf_program *simple;
    } progs;
    struct {
        struct bpf_link *simple;
    } links;
};

Å eit mēs varam redzēt zema lÄ«meņa API pēdas: struktÅ«ra struct bpf_program *simple Šø struct bpf_link *simple. Pirmā struktÅ«ra Ä«paÅ”i apraksta mÅ«su programmu, kas rakstÄ«ta sadaļā xdp/simple, bet otrajā ir aprakstÄ«ts, kā programma izveido savienojumu ar notikuma avotu.

Funkcija xdp_simple_bpf__open_and_load, atver ELF objektu, parsē to, izveido visas struktÅ«ras un apakÅ”struktÅ«ras (bez programmas ELF satur arÄ« citas sadaļas - dati, tikai lasāmie dati, atkļūdoÅ”anas informācija, licence utt.), un pēc tam, izmantojot sistēmu, ielādē to kodolā. zvanu bpf, ko varam pārbaudÄ«t, apkopojot un palaižot programmu:

$ clang -O2 -I ./libbpf/src/root/usr/include/ xdp-simple.c -o xdp-simple ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_BTF_LOAD, 0x7ffdb8fd9670, 120)  = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0xdfd580, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 8, 0), prog_flags=0, prog_name="simple", prog_ifindex=0, expected_attach_type=0x25 /* BPF_??? */, ...}, 120) = 4

Tagad apskatīsim mūsu programmu, izmantojot bpftool. Atradīsim viņas ID:

# bpftool p | grep -A4 simple
463: xdp  name simple  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-01T01:59:49+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        btf_id 185
        pids xdp-simple(16498)

un dump (mēs izmantojam saīsinātu komandas formu bpftool prog dump xlated):

# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
   0: (b7) r0 = 2
   1: (95) exit

Kaut kas jauns! Programma izdrukāja mÅ«su C avota faila gabalus. To izdarÄ«ja bibliotēka libbpf, kas atrada atkļūdoÅ”anas sadaļu binārajā failā, kompilēja to BTF objektā, ielādēja kodolā, izmantojot BPF_BTF_LOAD, un pēc tam, ielādējot programmu ar komandu, norādÄ«ja iegÅ«to faila deskriptoru BPG_PROG_LOAD.

Kodola palīgi

BPF programmas var palaist ā€œÄrējasā€ funkcijas - kodola palÄ«gus. Å Ä«s palÄ«gfunkcijas ļauj BPF programmām piekļūt kodola struktÅ«rām, pārvaldÄ«t kartes un arÄ« sazināties ar ā€œreālo pasauliā€ - izveidot perfektus notikumus, kontrolēt aparatÅ«ru (piemēram, novirzÄ«Å”anas paketes) utt.

Piemērs: bpf_get_smp_processor_id

Paradigmas ā€œmācÄ«Å”anās pēc piemēraā€ ietvaros aplÅ«kosim vienu no palÄ«gfunkcijām, bpf_get_smp_processor_id(), zināms failā kernel/bpf/helpers.c. Tas atgriež tā procesora numuru, kurā darbojas BPF programma, kas to izsauca. Bet mÅ«s neinteresē tā semantika kā fakts, ka tā Ä«stenoÅ”ana aizņem vienu lÄ«niju:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

BPF palÄ«gfunkciju definÄ«cijas ir lÄ«dzÄ«gas Linux sistēmas izsaukuma definÄ«cijām. Å eit, piemēram, ir definēta funkcija, kurai nav argumentu. (Funkcija, kas izmanto, piemēram, trÄ«s argumentus, tiek definēta, izmantojot makro BPF_CALL_3. Maksimālais argumentu skaits ir pieci.) Tomēr Ŕī ir tikai definÄ«cijas pirmā daļa. Otrajā daļā ir jādefinē tipa struktÅ«ra struct bpf_func_proto, kurā ir verificētājam saprotamās palÄ«gfunkcijas apraksts:

const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
    .func     = bpf_get_smp_processor_id,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

Palīdzības funkciju reģistrēŔana

Lai noteikta veida BPF programmas varētu izmantot Å”o funkciju, tām tā ir jāreÄ£istrē, piemēram, tipam BPF_PROG_TYPE_XDP funkcija ir definēta kodolā xdp_func_proto, kas no palÄ«gfunkcijas ID nosaka, vai XDP atbalsta Å”o funkciju vai ne. MÅ«su funkcija ir atbalsta:

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    ...
    case BPF_FUNC_get_smp_processor_id:
        return &bpf_get_smp_processor_id_proto;
    ...
    }
}

Jaunie BPF programmu veidi ir "definēti" failā include/linux/bpf_types.h izmantojot makro BPF_PROG_TYPE. Definēts pēdiņās, jo tā ir loÄ£iska definÄ«cija, un C valodas terminos citās vietās sastopama vesela betona konstrukciju kopuma definÄ«cija. Jo Ä«paÅ”i failā kernel/bpf/verifier.c visas definÄ«cijas no faila bpf_types.h tiek izmantoti, lai izveidotu struktÅ«ru masÄ«vu bpf_verifier_ops[]:

static const struct bpf_verifier_ops *const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) 
    [_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
};

Tas nozÄ«mē, ka katram BPF programmas veidam ir definēts rādÄ«tājs uz Ŕāda veida datu struktÅ«ru struct bpf_verifier_ops, kas tiek inicializēts ar vērtÄ«bu _name ## _verifier_ops, t.i., xdp_verifier_ops par xdp. StruktÅ«ra xdp_verifier_ops nosaka failā net/core/filter.c Ŕādi:

const struct bpf_verifier_ops xdp_verifier_ops = {
    .get_func_proto     = xdp_func_proto,
    .is_valid_access    = xdp_is_valid_access,
    .convert_ctx_access = xdp_convert_ctx_access,
    .gen_prologue       = bpf_noop_prologue,
};

Šeit mēs redzam mūsu pazīstamo funkciju xdp_func_proto, kas palaiž verificētāju katru reizi, kad saskarsies ar izaicinājumu kaut kāda veida funkcijas BPF programmā, sk verifier.c.

ApskatÄ«sim, kā hipotētiskā BPF programma izmanto Å”o funkciju bpf_get_smp_processor_id. Lai to izdarÄ«tu, mēs pārrakstām programmu no mÅ«su iepriekŔējās sadaļas Ŕādi:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
    if (bpf_get_smp_processor_id() != 0)
        return XDP_DROP;
    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Simbols bpf_get_smp_processor_id nosaka Š² <bpf/bpf_helper_defs.h> bibliotēkas libbpf kā

static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;

tas ir, bpf_get_smp_processor_id ir funkcijas rādītājs, kura vērtība ir 8, kur 8 ir vērtība BPF_FUNC_get_smp_processor_id tips enum bpf_fun_id, kas mums ir definēts failā vmlinux.h (fails bpf_helper_defs.h kodolā tiek ģenerēts ar skriptu, tāpēc "burvju" skaitļi ir labi). Šī funkcija neizmanto argumentus un atgriež tipa vērtību __u32. Kad mēs to palaižam savā programmā, clang ģenerē instrukciju BPF_CALL "pareizais veids" Sastādīsim programmu un apskatīsim sadaļu xdp/simple:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ llvm-objdump -D --section=xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       bf 01 00 00 00 00 00 00 r1 = r0
       2:       67 01 00 00 20 00 00 00 r1 <<= 32
       3:       77 01 00 00 20 00 00 00 r1 >>= 32
       4:       b7 00 00 00 02 00 00 00 r0 = 2
       5:       15 01 01 00 00 00 00 00 if r1 == 0 goto +1 <LBB0_2>
       6:       b7 00 00 00 01 00 00 00 r0 = 1

0000000000000038 <LBB0_2>:
       7:       95 00 00 00 00 00 00 00 exit

Pirmajā rindā mēs redzam instrukcijas call, parametrs IMM kas ir vienāds ar 8, un SRC_REG - nulle. Saskaņā ar verificētāja izmantoto ABI lÄ«gumu Å”is ir izsaukums uz palÄ«gfunkciju Nr. XNUMX. Kad tas ir palaists, loÄ£ika ir vienkārÅ”a. AtgrieÅ”anas vērtÄ«ba no reÄ£istra r0 kopēts uz r1 un 2,3. rindā tas tiek pārveidots par tipu u32 ā€” tiek notÄ«rÄ«ti augŔējie 32 biti. 4,5,6,7 rindā mēs atgriežam 2 (XDP_PASS) vai 1 (XDP_DROP) atkarÄ«bā no tā, vai palÄ«gfunkcija no 0. rindas atgrieza nulles vai nulles vērtÄ«bu.

Pārbaudīsim sevi: ielādējam programmu un apskatīsim izvadi bpftool prog dump xlated:

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple &
[2] 10914

$ sudo bpftool p | grep simple
523: xdp  name simple  tag 44c38a10c657e1b0  gpl
        pids xdp-simple(10915)

$ sudo bpftool p d x id 523
int simple(void *ctx):
; if (bpf_get_smp_processor_id() != 0)
   0: (85) call bpf_get_smp_processor_id#114128
   1: (bf) r1 = r0
   2: (67) r1 <<= 32
   3: (77) r1 >>= 32
   4: (b7) r0 = 2
; }
   5: (15) if r1 == 0x0 goto pc+1
   6: (b7) r0 = 1
   7: (95) exit

Labi, pārbaudītājs atrada pareizo kodola palīgu.

Piemērs: argumentu nodoÅ”ana un visbeidzot programmas palaiÅ”ana!

Visām izpildes līmeņa palīgfunkcijām ir prototips

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

Parametri palÄ«gfunkcijām tiek nodoti reÄ£istros r1Sākot nor5, un vērtÄ«ba tiek atgriezta reÄ£istrā r0. Nav tādu funkciju, kurām bÅ«tu nepiecieÅ”ami vairāk nekā pieci argumenti, un to atbalsts nav paredzēts nākotnē.

ApskatÄ«sim jauno kodola palÄ«gu un to, kā BPF nodod parametrus. PārrakstÄ«sim xdp-simple.bpf.c Ŕādi (pārējās rindas nav mainÄ«tas):

SEC("xdp/simple")
int simple(void *ctx)
{
    bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
    return XDP_PASS;
}

Mūsu programma izdrukā tā CPU numuru, kurā tā darbojas. Apkoposim to un apskatīsim kodu:

$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       r1 = 10
       1:       *(u16 *)(r10 - 8) = r1
       2:       r1 = 8441246879787806319 ll
       4:       *(u64 *)(r10 - 16) = r1
       5:       r1 = 2334956330918245746 ll
       7:       *(u64 *)(r10 - 24) = r1
       8:       call 8
       9:       r1 = r10
      10:       r1 += -24
      11:       r2 = 18
      12:       r3 = r0
      13:       call 6
      14:       r0 = 2
      15:       exit

0-7 rindā mēs rakstām virkni running on CPU%un, un tad 8. rindā mēs palaižam pazÄ«stamo bpf_get_smp_processor_id. 9.-12.rindā sagatavojam palÄ«ga argumentus bpf_printk - reÄ£istri r1, r2, r3. Kāpēc viņi ir trÄ«s, nevis divi? Jo bpf_printk Sākot no Å”is ir makro iesaiņojums ap Ä«sto palÄ«gu bpf_trace_printk, kam jānodod formāta virknes izmērs.

Tagad pievienosim pāris rindiņas xdp-simple.clai mÅ«su programma izveidotu savienojumu ar saskarni lo un tieŔām sākās!

$ cat xdp-simple.c
#include <linux/if_link.h>
#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    __u32 flags = XDP_FLAGS_SKB_MODE;
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    bpf_set_link_xdp_fd(1, -1, flags);
    bpf_set_link_xdp_fd(1, bpf_program__fd(obj->progs.simple), flags);

cleanup:
    xdp_simple_bpf__destroy(obj);
}

Å eit mēs izmantojam funkciju bpf_set_link_xdp_fd, kas savieno XDP tipa BPF programmas ar tÄ«kla saskarnēm. Mēs iekodējām saskarnes numuru lo, kas vienmēr ir 1. Mēs palaižam funkciju divas reizes, lai vispirms atvienotu veco programmu, ja tā bija pievienota. Ievērojiet, ka tagad mums nav nepiecieÅ”ams izaicinājums pause vai bezgalÄ«ga cilpa: mÅ«su ielādes programma tiks aizvērta, bet BPF programma netiks iznÄ«cināta, jo tā ir savienota ar notikuma avotu. Pēc veiksmÄ«gas lejupielādes un savienojuma izveides programma tiks palaista katrai tÄ«kla paketei, kas nonāk pie lo.

Lejupielādēsim programmu un apskatīsim interfeisu lo:

$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp  name simple  tag 4fca62e77ccb43d6  gpl
$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 669

Lejupielādētajai programmai ir ID 669, un mēs redzam to paÅ”u ID interfeisā lo. NosÅ«tÄ«sim pāris pakas uz 127.0.0.1 (pieprasÄ«jums + atbilde):

$ ping -c1 localhost

un tagad apskatÄ«sim atkļūdoÅ”anas virtuālā faila saturu /sys/kernel/debug/tracing/trace_pipe, kurā bpf_printk raksta savus ziņojumus:

# cat /sys/kernel/debug/tracing/trace_pipe
ping-13937 [000] d.s1 442015.377014: bpf_trace_printk: running on CPU0
ping-13937 [000] d.s1 442015.377027: bpf_trace_printk: running on CPU0

Tika pamanīti divi iepakojumi lo un apstrādāts uz CPU0 - mūsu pirmā pilnvērtīgā bezjēdzīgā BPF programma strādāja!

Ir vērts atzÄ«mēt, ka bpf_printk Ne velti tas raksta atkļūdoÅ”anas failā: Å”is nav veiksmÄ«gākais palÄ«gs izmantoÅ”anai ražoÅ”anā, taču mÅ«su mērÄ·is bija parādÄ«t kaut ko vienkārÅ”u.

Piekļuve kartēm no BPF programmām

Piemērs: izmantojot karti no BPF programmas

IepriekŔējās sadaļās mēs uzzinājām, kā izveidot un izmantot kartes no lietotāja vietas, un tagad apskatÄ«sim kodola daļu. Sāksim, kā parasti, ar piemēru. PārrakstÄ«sim savu programmu xdp-simple.bpf.c Ŕādi:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 8);
    __type(key, u32);
    __type(value, u64);
} woo SEC(".maps");

SEC("xdp/simple")
int simple(void *ctx)
{
    u32 key = bpf_get_smp_processor_id();
    u32 *val;

    val = bpf_map_lookup_elem(&woo, &key);
    if (!val)
        return XDP_ABORTED;

    *val += 1;

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Programmas sākumā pievienojām kartes definÄ«ciju woo: Å is ir 8 elementu masÄ«vs, kurā tiek saglabātas tādas vērtÄ«bas kā u64 (C valodā mēs definētu Ŕādu masÄ«vu kā u64 woo[8]). Programmā "xdp/simple" mēs iegÅ«stam paÅ”reizējo procesora numuru mainÄ«gajā key un pēc tam izmantojot palÄ«ga funkciju bpf_map_lookup_element iegÅ«stam rādÄ«tāju uz atbilstoÅ”o ierakstu masÄ«vā, kuru palielinām par vienu. Tulkots krievu valodā: mēs aprēķinām statistiku par to, kurÅ” CPU apstrādāja ienākoŔās paketes. Mēģināsim palaist programmu:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple

Pārbaudīsim, vai viņa ir pieķērusies lo un nosūtiet dažas paketes:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 108

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done

Tagad apskatīsim masīva saturu:

$ sudo bpftool map dump name woo
[
    { "key": 0, "value": 0 },
    { "key": 1, "value": 400 },
    { "key": 2, "value": 0 },
    { "key": 3, "value": 0 },
    { "key": 4, "value": 0 },
    { "key": 5, "value": 0 },
    { "key": 6, "value": 0 },
    { "key": 7, "value": 46400 }
]

GandrÄ«z visi procesi tika apstrādāti CPU7. Tas mums nav svarÄ«gi, galvenais, lai programma strādā un mēs saprotam, kā piekļūt kartēm no BPF programmām - izmantojot хŠµŠ»ŠæŠµŃ€Š¾Š² bpf_mp_*.

Mistisks rādītājs

Tātad, mēs varam piekļūt kartei no BPF programmas, izmantojot tādus zvanus kā

val = bpf_map_lookup_elem(&woo, &key);

kur izskatās palīga funkcija

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

bet mēs ejam garām rādītājam &woo uz nenosauktu struktūru struct { ... }...

Ja mēs skatāmies uz programmas montētāju, mēs redzam, ka vērtÄ«ba &woo faktiski nav definēts (4. rindiņa):

llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
...

un tas ir ietverts pārvietoŔanā:

$ llvm-readelf -r xdp-simple.bpf.o | head -4

Relocation section '.relxdp/simple' at offset 0xe18 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name
0000000000000020  0000002700000001 R_BPF_64_64            0000000000000000 woo

Bet, ja mēs skatāmies uz jau ielādētu programmu, mēs redzam rādÄ«tāju uz pareizo karti (4. rindiņa):

$ sudo bpftool prog dump x name simple
int simple(void *ctx):
   0: (85) call bpf_get_smp_processor_id#114128
   1: (63) *(u32 *)(r10 -4) = r0
   2: (bf) r2 = r10
   3: (07) r2 += -4
   4: (18) r1 = map[id:64]
...

Tādējādi mēs varam secināt, ka mÅ«su iekrāvēja programmas palaiÅ”anas laikā saite uz &woo tika aizstāts ar kaut ko ar bibliotēku libbpf. Vispirms apskatÄ«sim izvadi strace:

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=8, max_entries=8, map_name="woo", ...}, 120) = 4
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="simple", ...}, 120) = 5

Mēs to redzam libbpf izveidoja karti woo un pēc tam lejupielādējām mūsu programmu simple. Sīkāk apskatīsim, kā mēs ielādējam programmu:

  • zvanu xdp_simple_bpf__open_and_load no faila xdp-simple.skel.h
  • kas izraisa xdp_simple_bpf__load no faila xdp-simple.skel.h
  • kas izraisa bpf_object__load_skeleton no faila libbpf/src/libbpf.c
  • kas izraisa bpf_object__load_xattr no libbpf/src/libbpf.c

Cita starpā tiks izsaukta pēdējā funkcija bpf_object__create_maps, kas izveido vai atver esoŔās kartes, pārvērÅ”ot tās par failu deskriptoriem. (Å eit mēs redzam BPF_MAP_CREATE izejā strace.) Tālāk tiek izsaukta funkcija bpf_object__relocate un tieÅ”i viņa mÅ«s interesē, jo mēs atceramies redzēto woo pārvietoÅ”anas tabulā. Izpētot to, mēs galu galā atrodamies funkcijā bpf_program__relocate, kas nodarbojas ar karÅ”u pārvietoÅ”anu:

case RELO_LD64:
    insn[0].src_reg = BPF_PSEUDO_MAP_FD;
    insn[0].imm = obj->maps[relo->map_idx].fd;
    break;

Tāpēc mēs ņemam vērā mūsu norādījumus

18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll

un aizstāt avota reģistru tajā ar BPF_PSEUDO_MAP_FD, un pirmais IMM mūsu kartes faila deskriptoram un, ja tas ir vienāds ar, piemēram, 0xdeadbeef, tad rezultātā saņemsim instrukciju

18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll

Tādā veidā kartes informācija tiek pārsūtīta uz konkrētu ielādētu BPF programmu. Šajā gadījumā karti var izveidot, izmantojot BPF_MAP_CREATEun atvērts ar ID, izmantojot BPF_MAP_GET_FD_BY_ID.

Kopā, lietojot libbpf algoritms ir Ŕāds:

  • kompilācijas laikā pārvietoÅ”anas tabulā tiek izveidoti ieraksti saitēm uz kartēm
  • libbpf atver ELF objektu grāmatu, atrod visas izmantotās kartes un izveido tām failu deskriptorus
  • failu deskriptori tiek ielādēti kodolā kā daļa no instrukcijas LD64

Kā jÅ«s varat iedomāties, priekŔā ir vēl vairāk, un mums bÅ«s jāizpēta kodols. Par laimi, mums ir nojausma ā€“ esam pierakstÄ«juÅ”i nozÄ«mi BPF_PSEUDO_MAP_FD avota reÄ£istrā un mēs varam to apglabāt, kas mÅ«s aizvedÄ«s uz visu svēto svēto vietu - kernel/bpf/verifier.c, kur funkcija ar atŔķirÄ«gu nosaukumu aizstāj faila deskriptoru ar tipa struktÅ«ras adresi struct bpf_map:

static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env) {
    ...

    f = fdget(insn[0].imm);
    map = __bpf_map_get(f);
    if (insn->src_reg == BPF_PSEUDO_MAP_FD) {
        addr = (unsigned long)map;
    }
    insn[0].imm = (u32)addr;
    insn[1].imm = addr >> 32;

(pilnu kodu var atrast ŠæŠ¾ ссыŠ»ŠŗŠµ). Tātad mēs varam paplaÅ”ināt savu algoritmu:

  • ielādējot programmu, verificētājs pārbauda pareizu kartes lietojumu un ieraksta attiecÄ«gās struktÅ«ras adresi struct bpf_map

Lejupielādējot ELF bināro failu, izmantojot libbpf Notiek daudz vairāk, bet mēs to apspriedīsim citos rakstos.

Programmu un karŔu ielāde bez libbpf

Kā solÄ«ts, Å”eit ir piemērs lasÄ«tājiem, kuri vēlas uzzināt, kā bez palÄ«dzÄ«bas izveidot un ielādēt programmu, kas izmanto kartes libbpf. Tas var bÅ«t noderÄ«gi, ja strādājat vidē, kurā nevarat izveidot atkarÄ«bas vai saglabājat katru bitu, vai rakstāt tādu programmu kā ply, kas lidojumā Ä£enerē BPF bināro kodu.

Lai bÅ«tu vieglāk ievērot loÄ£iku, mēs Å”iem nolÅ«kiem pārrakstÄ«sim savu piemēru xdp-simple. Pilns un nedaudz paplaÅ”ināts Å”ajā piemērā aplÅ«kotās programmas kods ir atrodams Å”ajā bÅ«tÄ«ba.

Mūsu lietojumprogrammas loģika ir Ŕāda:

  • izveidot tipa karti BPF_MAP_TYPE_ARRAY izmantojot komandu BPF_MAP_CREATE,
  • izveidot programmu, kas izmanto Å”o karti,
  • savienojiet programmu ar interfeisu lo,

kas tulkojumā nozīmē cilvēku kā

int main(void)
{
    int map_fd, prog_fd;

    map_fd = map_create();
    if (map_fd < 0)
        err(1, "bpf: BPF_MAP_CREATE");

    prog_fd = prog_load(map_fd);
    if (prog_fd < 0)
        err(1, "bpf: BPF_PROG_LOAD");

    xdp_attach(1, prog_fd);
}

Å eit map_create izveido karti tādā paŔā veidā, kā mēs to darÄ«jām pirmajā piemērā par sistēmas zvanu bpf - ā€œkodolu, lÅ«dzu, izveidojiet man jaunu karti 8 elementu masÄ«va veidā, piemēram __u64 un atdodiet man faila deskriptoru":

static int map_create()
{
    union bpf_attr attr;

    memset(&attr, 0, sizeof(attr));
    attr.map_type = BPF_MAP_TYPE_ARRAY,
    attr.key_size = sizeof(__u32),
    attr.value_size = sizeof(__u64),
    attr.max_entries = 8,
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

Programmu ir arī viegli ielādēt:

static int prog_load(int map_fd)
{
    union bpf_attr attr;
    struct bpf_insn insns[] = {
        ...
    };

    memset(&attr, 0, sizeof(attr));
    attr.prog_type = BPF_PROG_TYPE_XDP;
    attr.insns     = ptr_to_u64(insns);
    attr.insn_cnt  = sizeof(insns)/sizeof(insns[0]);
    attr.license   = ptr_to_u64("GPL");
    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

Sarežģītā daļa prog_load ir mūsu BPF programmas definīcija kā struktūru masīvs struct bpf_insn insns[]. Bet, tā kā mēs izmantojam programmu, kas mums ir C, mēs varam nedaudz krāpties:

$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
       7:       b7 01 00 00 00 00 00 00 r1 = 0
       8:       15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2>
       9:       61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0)
      10:       07 01 00 00 01 00 00 00 r1 += 1
      11:       63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1
      12:       b7 01 00 00 02 00 00 00 r1 = 2

0000000000000068 <LBB0_2>:
      13:       bf 10 00 00 00 00 00 00 r0 = r1
      14:       95 00 00 00 00 00 00 00 exit

Kopumā mums ir jāraksta 14 instrukcijas tādu struktÅ«ru veidā kā struct bpf_insn (padoms: paņemiet izgāztuvi no augÅ”as, vēlreiz izlasiet instrukciju sadaļu, atveriet linux/bpf.h Šø linux/bpf_common.h un mēģināt noteikt struct bpf_insn insns[] viens pats):

struct bpf_insn insns[] = {
    /* 85 00 00 00 08 00 00 00 call 8 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 8,
    },

    /* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */
    {
        .code = BPF_MEM | BPF_STX,
        .off = -4,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_10,
    },

    /* bf a2 00 00 00 00 00 00 r2 = r10 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_10,
        .dst_reg = BPF_REG_2,
    },

    /* 07 02 00 00 fc ff ff ff r2 += -4 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_2,
        .imm = -4,
    },

    /* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */
    {
        .code = BPF_LD | BPF_DW | BPF_IMM,
        .src_reg = BPF_PSEUDO_MAP_FD,
        .dst_reg = BPF_REG_1,
        .imm = map_fd,
    },
    { }, /* placeholder */

    /* 85 00 00 00 01 00 00 00 call 1 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 1,
    },

    /* b7 01 00 00 00 00 00 00 r1 = 0 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 0,
    },

    /* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */
    {
        .code = BPF_JMP | BPF_JEQ | BPF_K,
        .off = 4,
        .src_reg = BPF_REG_0,
        .imm = 0,
    },

    /* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */
    {
        .code = BPF_MEM | BPF_LDX,
        .off = 0,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_1,
    },

    /* 07 01 00 00 01 00 00 00 r1 += 1 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 1,
    },

    /* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */
    {
        .code = BPF_MEM | BPF_STX,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* b7 01 00 00 02 00 00 00 r1 = 2 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 2,
    },

    /* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* 95 00 00 00 00 00 00 00 exit */
    {
        .code = BPF_JMP | BPF_EXIT
    },
};

Vingrinājums tiem, kuri paŔi to nav rakstījuŔi - atrodiet map_fd.

MÅ«su programmā ir palikusi vēl viena neatklāta daļa - xdp_attach. Diemžēl tādas programmas kā XDP nevar savienot, izmantojot sistēmas zvanu bpf. Cilvēki, kas izveidoja BPF un XDP, bija no tieÅ”saistes Linux kopienas, kas nozÄ«mē, ka viņi izmantoja sev vispazÄ«stamāko (bet ne normāli cilvēki) saskarne mijiedarbÄ«bai ar kodolu: netlink ligzdas, SkatÄ«t arÄ« RFC3549. VienkārŔākais Ä«stenoÅ”anas veids xdp_attach kopē kodu no libbpf, proti, no faila netlink.c, ko mēs arÄ« izdarÄ«jām, nedaudz saÄ«sinot:

Laipni lūdzam netlink ligzdu pasaulē

Atveriet tīkla saites ligzdas veidu NETLINK_ROUTE:

int netlink_open(__u32 *nl_pid)
{
    struct sockaddr_nl sa;
    socklen_t addrlen;
    int one = 1, ret;
    int sock;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0)
        err(1, "socket");

    if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0)
        warnx("netlink error reporting not supported");

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
        err(1, "bind");

    addrlen = sizeof(sa);
    if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0)
        err(1, "getsockname");

    *nl_pid = sa.nl_pid;
    return sock;
}

Mēs lasām no Ŕīs ligzdas:

static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq)
{
    bool multipart = true;
    struct nlmsgerr *errm;
    struct nlmsghdr *nh;
    char buf[4096];
    int len, ret;

    while (multipart) {
        multipart = false;
        len = recv(sock, buf, sizeof(buf), 0);
        if (len < 0)
            err(1, "recv");

        if (len == 0)
            break;

        for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
                nh = NLMSG_NEXT(nh, len)) {
            if (nh->nlmsg_pid != nl_pid)
                errx(1, "wrong pid");
            if (nh->nlmsg_seq != seq)
                errx(1, "INVSEQ");
            if (nh->nlmsg_flags & NLM_F_MULTI)
                multipart = true;
            switch (nh->nlmsg_type) {
                case NLMSG_ERROR:
                    errm = (struct nlmsgerr *)NLMSG_DATA(nh);
                    if (!errm->error)
                        continue;
                    ret = errm->error;
                    // libbpf_nla_dump_errormsg(nh); too many code to copy...
                    goto done;
                case NLMSG_DONE:
                    return 0;
                default:
                    break;
            }
        }
    }
    ret = 0;
done:
    return ret;
}

Visbeidzot, Å”eit ir mÅ«su funkcija, kas atver ligzdu un nosÅ«ta tai Ä«paÅ”u ziņojumu, kurā ir faila deskriptors:

static int xdp_attach(int ifindex, int prog_fd)
{
    int sock, seq = 0, ret;
    struct nlattr *nla, *nla_xdp;
    struct {
        struct nlmsghdr  nh;
        struct ifinfomsg ifinfo;
        char             attrbuf[64];
    } req;
    __u32 nl_pid = 0;

    sock = netlink_open(&nl_pid);
    if (sock < 0)
        return sock;

    memset(&req, 0, sizeof(req));
    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    req.nh.nlmsg_type = RTM_SETLINK;
    req.nh.nlmsg_pid = 0;
    req.nh.nlmsg_seq = ++seq;
    req.ifinfo.ifi_family = AF_UNSPEC;
    req.ifinfo.ifi_index = ifindex;

    /* started nested attribute for XDP */
    nla = (struct nlattr *)(((char *)&req)
            + NLMSG_ALIGN(req.nh.nlmsg_len));
    nla->nla_type = NLA_F_NESTED | IFLA_XDP;
    nla->nla_len = NLA_HDRLEN;

    /* add XDP fd */
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FD;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(int);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd));
    nla->nla_len += nla_xdp->nla_len;

    /* if user passed in any flags, add those too */
    __u32 flags = XDP_FLAGS_SKB_MODE;
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FLAGS;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags));
    nla->nla_len += nla_xdp->nla_len;

    req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len);

    if (send(sock, &req, req.nh.nlmsg_len, 0) < 0)
        err(1, "send");
    ret = bpf_netlink_recv(sock, nl_pid, seq);

cleanup:
    close(sock);
    return ret;
}

Tātad, viss ir gatavs pārbaudei:

$ cc nolibbpf.c -o nolibbpf
$ sudo strace -e bpf ./nolibbpf
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, map_name="woo", ...}, 72) = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=15, prog_name="woo", ...}, 72) = 4
+++ exited with 0 +++

Apskatīsim, vai mūsu programma ir savienota ar lo:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 160

Sūtīsim pingus un skatīsimies kartē:

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
$ sudo bpftool m dump name woo
key: 00 00 00 00  value: 90 01 00 00 00 00 00 00
key: 01 00 00 00  value: 00 00 00 00 00 00 00 00
key: 02 00 00 00  value: 00 00 00 00 00 00 00 00
key: 03 00 00 00  value: 00 00 00 00 00 00 00 00
key: 04 00 00 00  value: 00 00 00 00 00 00 00 00
key: 05 00 00 00  value: 00 00 00 00 00 00 00 00
key: 06 00 00 00  value: 40 b5 00 00 00 00 00 00
key: 07 00 00 00  value: 00 00 00 00 00 00 00 00
Found 8 elements

Urā, viss darbojas. Starp citu, ņemiet vērā, ka mÅ«su karte atkal tiek parādÄ«ta baitu veidā. Tas ir saistÄ«ts ar faktu, ka atŔķirÄ«bā no libbpf mēs neielādējām tipa informāciju (BTF). Bet par to vairāk parunāsim nākamreiz.

Izstrādes rīki

Šajā sadaļā mēs apskatīsim minimālo BPF izstrādātāja rīku komplektu.

VispārÄ«gi runājot, jums nav nepiecieÅ”ams nekas Ä«paÅ”s, lai izstrādātu BPF programmas ā€” BPF darbojas uz jebkura pienācÄ«ga izplatÄ«Å”anas kodola, un programmas tiek veidotas, izmantojot clang, ko var piegādāt no iepakojuma. Taču sakarā ar to, ka BPF ir izstrādes stadijā, kodols un rÄ«ki nemitÄ«gi mainās, ja nevēlaties rakstÄ«t BPF programmas ar vecmodÄ«gām metodēm no 2019. gada, tad nāksies kompilēt

  • llvm/clang
  • pahole
  • tās kodols
  • bpftool

(Uzziņai Ŕī sadaļa un visi rakstā minētie piemēri tika palaisti Debian 10.)

llvm/clang

BPF ir draudzÄ«gs ar LLVM un, lai gan pēdējā laikā BPF programmas var kompilēt, izmantojot gcc, visa paÅ”reizējā izstrāde tiek veikta LLVM. Tāpēc, pirmkārt, mēs veidosim paÅ”reizējo versiju clang no git:

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... Š¼Š½Š¾Š³Š¾ Š²Ń€ŠµŠ¼ŠµŠ½Šø сŠæустя
$

Tagad mēs varam pārbaudīt, vai viss sanāca pareizi:

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 11.0.0git
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver1

  Registered Targets:
    bpf    - BPF (host endian)
    bpfeb  - BPF (big endian)
    bpfel  - BPF (little endian)
    x86    - 32-bit X86: Pentium-Pro and above
    x86-64 - 64-bit X86: EM64T and AMD64

(Montāžas instrukcijas clang es paņēmu no bpf_devel_QA.)

Mēs neinstalēsim tikko izveidotās programmas, bet vienkārÅ”i pievienosim tām PATH, piemēram:

export PATH="`pwd`/bin:$PATH"

(To var pievienot .bashrc vai uz atseviŔķu failu. Personīgi es pievienoju Ŕādas lietas ~/bin/activate-llvm.sh un kad vajag, es to daru . activate-llvm.sh.)

Pahole un BTF

LietderÄ«ba pahole izmanto, veidojot kodolu, lai izveidotu atkļūdoÅ”anas informāciju BTF formātā. Å ajā rakstā mēs nerunāsim par BTF tehnoloÄ£ijas detaļām, izņemot to, ka tā ir ērta un mēs vēlamies to izmantot. Tātad, ja jÅ«s gatavojaties izveidot savu kodolu, vispirms izveidojiet pahole (bez pahole jÅ«s nevarēsit izveidot kodolu ar opciju CONFIG_DEBUG_INFO_BTF:

$ git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git
$ cd pahole/
$ sudo apt install cmake
$ mkdir build
$ cd build/
$ cmake -D__LIB=lib ..
$ make
$ sudo make install
$ which pahole
/usr/local/bin/pahole

Kodoli eksperimentiem ar BPF

Izpētot BPF iespējas, es vēlos samontēt savu kodolu. VispārÄ«gi runājot, tas nav nepiecieÅ”ams, jo jÅ«s varēsiet kompilēt un ielādēt BPF programmas izplatÄ«Å”anas kodolā, taču, ja jums ir savs kodols, varat izmantot jaunākās BPF funkcijas, kas labākajā gadÄ«jumā parādÄ«sies jÅ«su izplatÄ«Å”anā pēc mēneÅ”iem. , vai, tāpat kā dažu atkļūdoÅ”anas rÄ«ku gadÄ«jumā, pārskatāmā nākotnē netiks iepakoti vispār. Turklāt tā kodols liek justies svarÄ«giem eksperimentēt ar kodu.

Lai izveidotu kodolu, jums ir nepiecieÅ”ams, pirmkārt, pats kodols un, otrkārt, kodola konfigurācijas fails. Lai eksperimentētu ar BPF, mēs varam izmantot parasto vaniļa kodolu vai kādu no izstrādes kodoliem. Vēsturiski BPF izstrāde notiek Linux tÄ«klu kopienā, un tāpēc visas izmaiņas agrāk vai vēlāk notiek ar Deivida Millera, Linux tÄ«kla uzturētāja, starpniecÄ«bu. AtkarÄ«bā no to rakstura ā€” labojumi vai jaunas funkcijas ā€” tÄ«kla izmaiņas ietilpst vienā no diviem kodoliem. net vai net-next. BPF izmaiņas tiek sadalÄ«tas tādā paŔā veidā starp bpf Šø bpf-next, kas pēc tam tiek apvienoti attiecÄ«gi neto un neto-next. SÄ«kāku informāciju skatiet bpf_devel_QA Šø netdev-FAQ. Tāpēc izvēlieties kodolu, pamatojoties uz savu gaumi un testējamās sistēmas stabilitātes vajadzÄ«bām (*-next kodoli ir nestabilākie no uzskaitÄ«tajiem).

Å is raksts neietilpst runāt par kodola konfigurācijas failu pārvaldÄ«bu ā€” tiek pieņemts, ka jÅ«s jau zināt, kā to izdarÄ«t, vai gatavs mācÄ«ties paÅ”a spēkiem. Tomēr ar Å”iem norādÄ«jumiem vajadzētu bÅ«t vairāk vai mazāk pietiekamiem, lai nodroÅ”inātu funkcionējoÅ”u sistēmu ar iespējotu BPF.

Lejupielādējiet vienu no iepriekÅ” minētajiem kodoliem:

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next

Izveidojiet minimālu strādājoŔu kodola konfigurāciju:

$ cp /boot/config-`uname -r` .config
$ make localmodconfig

Iespējot failā BPF opcijas .config pēc jÅ«su izvēles (visticamāk CONFIG_BPF jau bÅ«s iespējots, jo systemd to izmanto). Å eit ir Å”ajā rakstā izmantotā kodola opciju saraksts:

CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_IPV6_SEG6_BPF=y
# CONFIG_NETFILTER_XT_MATCH_BPF is not set
# CONFIG_BPFILTER is not set
CONFIG_NET_CLS_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y

Pēc tam mēs varam viegli salikt un instalēt moduļus un kodolu (starp citu, jūs varat salikt kodolu, izmantojot tikko salikto clangpievienojot CC=clang):

$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install

un restartējiet ar jauno kodolu (es izmantoju Å”im nolÅ«kam kexec no iepakojuma kexec-tools):

v=5.8.0-rc6+ # ŠµŃŠ»Šø Š²Ń‹ ŠæŠµŃ€ŠµŃŠ¾Š±ŠøрŠ°ŠµŃ‚Šµ тŠµŠŗущŠµŠµ яŠ“рŠ¾, тŠ¾ Š¼Š¾Š¶Š½Š¾ Š“ŠµŠ»Š°Ń‚ŃŒ v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -e

bpftool

Rakstā visbiežāk izmantotā utilÄ«ta bÅ«s lietderÄ«ba bpftool, kas tiek piegādāts kā daļa no Linux kodola. To ir rakstÄ«juÅ”i un uzturējuÅ”i BPF izstrādātāji BPF izstrādātājiem, un to var izmantot visu veidu BPF objektu pārvaldÄ«Å”anai ā€“ programmu ielādei, karÅ”u izveidei un rediģēŔanai, BPF ekosistēmas dzÄ«ves izpētei utt. Var atrast dokumentāciju rokasgrāmatu avota kodu veidā kodolā vai jau apkopots, tÄ«klā.

Å Ä«s rakstÄ«Å”anas laikā bpftool ir gatavs tikai RHEL, Fedora un Ubuntu (skatiet, piemēram, Å”is pavediens, kas stāsta par nepabeigto iepakojuma stāstu bpftool programmā Debian). Bet, ja jau esat izveidojis savu kodolu, tad izveidojiet bpftool tik vienkārÅ”i kā pÄ«rāgs:

$ cd ${linux}/tools/bpf/bpftool
# ... ŠæрŠ¾ŠæŠøшŠøтŠµ ŠæутŠø Šŗ ŠæŠ¾ŃŠ»ŠµŠ“Š½ŠµŠ¼Ńƒ clang, ŠŗŠ°Šŗ рŠ°ŃŃŠŗŠ°Š·Š°Š½Š¾ Š²Ń‹ŃˆŠµ
$ make -s

Auto-detecting system features:
...                        libbfd: [ on  ]
...        disassembler-four-args: [ on  ]
...                          zlib: [ on  ]
...                        libcap: [ on  ]
...               clang-bpf-co-re: [ on  ]

Auto-detecting system features:
...                        libelf: [ on  ]
...                          zlib: [ on  ]
...                           bpf: [ on  ]

$

(Å”eit ${linux} - tas ir jÅ«su kodola direktorijs.) Pēc Å”o komandu izpildes bpftool tiks apkopoti direktorijā ${linux}/tools/bpf/bpftool un to var pievienot ceļam (vispirms lietotājam root) vai vienkārÅ”i kopējiet uz /usr/local/sbin.

Savākt bpftool vislabāk ir izmantot pēdējo clang, samontēts, kā aprakstÄ«ts iepriekÅ”, un pārbaudiet, vai tas ir pareizi salikts - izmantojot, piemēram, komandu

$ sudo bpftool feature probe kernel
Scanning system configuration...
bpf() syscall for unprivileged users is enabled
JIT compiler is enabled
JIT compiler hardening is disabled
JIT compiler kallsyms exports are enabled for root
...

kas parādīs, kuras BPF funkcijas ir iespējotas jūsu kodolā.

Starp citu, iepriekŔējo komandu var palaist kā

# bpftool f p k

Tas tiek darīts pēc analoģijas ar komunālajiem pakalpojumiem no iepakojuma iproute2, kur mēs varam, piemēram, teikt ip a s eth0 nevis ip addr show dev eth0.

Secinājums

BPF ļauj jums apavu blusu, lai efektÄ«vi izmērÄ«tu un lidojuma laikā mainÄ«tu kodola funkcionalitāti. Sistēma izrādÄ«jās ļoti veiksmÄ«ga atbilstoÅ”i labākajām UNIX tradÄ«cijām: vienkārÅ”s mehānisms, kas ļauj (pār)programmēt kodolu, ļāva eksperimentēt ļoti daudziem cilvēkiem un organizācijām. Un, lai gan eksperimenti, kā arÄ« paÅ”as BPF infrastruktÅ«ras attÄ«stÄ«ba vēl nebÅ«t nav pabeigta, sistēmai jau ir stabils ABI, kas ļauj izveidot uzticamu un, pats galvenais, efektÄ«vu biznesa loÄ£iku.

Es vēlos atzÄ«mēt, ka, manuprāt, tehnoloÄ£ija ir kļuvusi tik populāra, jo, no vienas puses, tā var spēlēt (maŔīnas arhitektÅ«ru var saprast vairāk vai mazāk vienā vakarā), un no otras puses, lai atrisinātu problēmas, kuras nevarēja atrisināt (skaisti) pirms tās parādÄ«Å”anās. Å Ä«s divas sastāvdaļas kopā liek cilvēkiem eksperimentēt un sapņot, kas noved pie arvien inovatÄ«vāku risinājumu raÅ”anās.

Å is raksts, lai gan tas nav Ä«paÅ”i Ä«ss, ir tikai ievads BPF pasaulē un neapraksta ā€œuzlabotasā€ funkcijas un svarÄ«gas arhitektÅ«ras daļas. Turpmākais plāns ir apmēram Ŕāds: nākamais raksts bÅ«s BPF programmu veidu pārskats (5.8 kodolā tiek atbalstÄ«ti 30 programmu veidi), tad beidzot apskatÄ«sim, kā rakstÄ«t Ä«stas BPF lietojumprogrammas, izmantojot kodola izsekoÅ”anas programmas. Piemēram, ir pienācis laiks padziļinātam kursam par BPF arhitektÅ«ru, kam seko BPF tÄ«klu un droŔības lietojumprogrammu piemēri.

IepriekŔējie raksti Å”ajā sērijā

  1. BPF mazajiem, nulles daļa: klasiskais BPF

Saites

  1. BPF un XDP uzziņu rokasgrāmata ā€” dokumentācija par BPF no cilium vai precÄ«zāk no Daniela Borkmana, viena no BPF radÄ«tājiem un uzturētājiem. Å is ir viens no pirmajiem nopietnajiem aprakstiem, kas no pārējiem atŔķiras ar to, ka Daniels precÄ«zi zina par ko raksta un tur nav nekādu kļūdu. Jo Ä«paÅ”i Å”ajā dokumentā ir aprakstÄ«ts, kā strādāt ar XDP un TC tipa BPF programmām, izmantojot labi zināmo utilÄ«tu. ip no iepakojuma iproute2.

  2. Dokumentācija/tÄ«kls/filter.txt ā€” oriÄ£inālais fails ar dokumentāciju klasiskajam un pēc tam paplaÅ”inātajam BPF. Laba lasāmviela, ja vēlaties iedziļināties montāžas valodā un tehniskajās arhitektÅ«ras detaļās.

  3. Emuārs par BPF no Facebook. Tas tiek atjaunināts reti, bet trāpīgi, kā tur raksta Aleksejs Starovoitovs (eBPF autors) un Andrii Nakryiko - (apkopējs) libbpf).

  4. Bpftool noslēpumi. IzklaidējoÅ”s Twitter pavediens no Quentin Monnet ar bpftool izmantoÅ”anas piemēriem un noslēpumiem.

  5. Iedziļinieties BPF: lasāmvielu sarakstā. MilzÄ«gs (un joprojām uzturēts) saiÅ”u saraksts uz BPF dokumentāciju no Quentin Monnet.

Avots: www.habr.com

Pievieno komentāru