Alguses oli tehnoloogia, mida nimetati BPF-iks. Me vaatasime selle peale , vana testamendi artiklis sellest tsĂŒklist. 2013. aastal Alexei Starovoitovi ja Daniel Borkmani pingutuste abil töötati vĂ€lja ja lisati Linuxi kernelisse selle tĂ€iustatud versioon, mis oli optimeeritud kaasaegsetele 64-bitistele masinatele. See uus tehnoloogia kandis lĂŒhikest aega nime Internal BPF, seejĂ€rel muudeti see Extended BPF-iks ja nĂŒĂŒd, pĂ€rast mitmeid aastaid, nimetavad kĂ”ik seda lihtsalt BPF-iks.
Ăldiselt vĂ”imaldab BPF kĂ€ivitada kasutaja poolt antud arbitraarset koodi Linuxi kernelis ja uus arhitektuur osutus nii edukaks, et meil on vaja veel umbes tosina artikli jagu, et kirjeldada kĂ”iki selle rakendusi. (Ainus, millega arendajad hakkama ei saanud, nagu nĂ€ete alloleval plakatil, on korraliku logo loomine.)
Selles artiklis kirjeldatakse BPF-i virtuaalse masina struktuuri, kerneliga töötamise liideseid, arendustööriistu ning ka lĂŒhikest, vĂ€ga lĂŒhikest ĂŒlevaadet olemasolevatest vĂ”imalustest, st kĂ”ike seda, mida vajame edasise sĂŒgava uurimise jaoks BPF-i praktiliste rakenduste osas.
Artikli kokkuvÔte
Alguses vaatame BPF-i arhitektuuri linnulennult ja mÀÀratleme peamised koostisosad.
Olles juba omandanud arusaama arhitektuurist, kirjeldame BPF-i virtuaalse masina ehitust.
Selles osas vaatame lĂ€hemalt BPF-i objektide â programmide ja kaartide â elutsĂŒklit.
Olles saanud juba teatud arusaama sĂŒsteemist, vaatame lĂ”puks, kuidas luua ja hallata objekte kasutajaruumi kaudu erilise sĂŒsteemi kĂ”ne abil â bpf(2).
Programmi kirjutamine sĂŒsteemi kĂ”ne abil on muidugi vĂ”imalik. Kuid keeruline. Rohkem realistliku stsenaariumi jaoks on tuumaprogrammeerijad töötanud vĂ€lja raamatukogu libbpf. Loome kĂ”ige lihtsama BPF-i rakenduse karkassi, mida kasutame edaspidi nĂ€idetes.
Siin saame teada, kuidas BPF programmid saavad pöörduda tuumasĂŒsteemide abifunktsioonide poole â tööriista, mis koos kaartidega oluliselt laiendab uue BPF vĂ”imalusi vĂ”rreldes klassikalisega.
Selleks ajaks teame piisavalt, et mÔista, kuidas luua programme, mis kasutavad kaarte. Ja isegi piilume suure ja vÔimsa verifitseerija sisse.
Tehnilised juhised selle kohta, kuidas koguda vajalikud utiliidid ja tuum eksperimenteerimiseks.
Artikli lĂ”pus leiavad need, kes siiani jĂ”udsid, julgustavaid sĂ”nu ja lĂŒhikese ĂŒlevaate sellest, millest jĂ€rgmistes artiklites juttu tuleb. Samuti loetleme mitmeid lingid iseseisvaks uurimiseks neile, kellel ei ole soovi vĂ”i vĂ”imalust oodata jĂ€rgmist osa.
BPF arhitektuuri sissejuhatus
Enne kui hakkame vaatama BPF arhitektuuri, viitame viimast korda (kas tĂ”esti?) , mis töötati vĂ€lja vastusena RISC masinate tekkele ja lahendas pakettide tĂ”hususe filtreerimise probleemi. Arhitektuur osutus nii Ă”nnestunuks, et sĂŒndis 90ndate alguses Berkeley UNIX-is, see portiti enamikule olemasolevatest operatsioonisĂŒsteemidest ja on ellu jÀÀnud kahekĂŒmnendate hullustes, leidnud uusi rakendusi.
Uus BPF töötati vĂ€lja vastusena 64-bitiste masinate, pilveteenuste ja hĂŒppeliselt suurenenud nĂ”udmistele SDN (Starkvara-dmÀÀra nvĂ”rgustik). Uute BPF loomine tuumainĆŸenere on tĂ€iustatud asendus klassikalisele BPF-le, ja juba poole aasta pĂ€rast leidis uus BPF rakendusi keerulises Linuxi sĂŒsteemide jĂ€lgimise praktikas, ja nĂŒĂŒd, kuus aastat pĂ€rast selle ilmumist, vajame me jĂ€rgmiseks artikliks ainult selleks, et loetleda erinevad programmide tĂŒĂŒbid.
VeSÔLeVd PiltId
BPF on virtuaalmasin, mis vĂ”imaldab kĂ€ivitada "tavalist" koodi kerneli ruumis ilma turvalisust ohustamata. BPF-i programmid luuakse kasutaja ruumis, laaditakse kerneli sisse ja ĂŒhendatakse mingisuguse sĂŒndmuse allikaga. SĂŒndmuseks vĂ”ib olla nĂ€iteks paketi edastamine vĂ”rguliidesesse, kerneli funktsiooni kĂ€ivitamine jne. Paketi puhul on BPF-programmile saadaval paketi andmed ja metaandmed (lugemiseks ning vĂ”ib-olla ka kirjutamiseks, sĂ”ltuvalt programmi tĂŒĂŒbist), kerneli funktsiooni kĂ€ivitamisel â funktsiooni argumendid, sealhulgas mĂ€lupunktid kerneli mĂ€lus jne.
Vaatame seda protsessi lÀhemalt. Esiteks rÀÀgime esimesest erinevusest klassikalisest BPF-ist, mille programmid olid kirjutatud assembleris. Uues versioonis muudeti arhitektuuri nii, et programme saab kirjutada kÔrgetasemelistel keeltele, eelkÔige C-le. Selle jaoks loodi llvm-i backend, mis vÔimaldab genereerida BPF-i arhitektuurile sobivat baitkoodi.

BPF-i arhitektuuri arendati peamiselt selleks, et see saaks tĂ”husalt töötada tĂ€napĂ€evastes seadmetes. Selleks, et see praktikas töötaks, tĂ”lgitakse BPF-i baitkood pĂ€rast kerneli laadimist masinkoodiks komponente nimega JIT compiler abil (Just In Time). Lisaks, kui te mĂ€letate, laaditi klassikalises BPF-is programm kerneli ja ĂŒhendati sĂŒndmuse allikaga atomaarsetena â ĂŒhe sĂŒsteemikĂ”ne kontekstis. Uues arhitektuuris toimub see kahel etapil â alguses laaditakse kood kerneli sĂŒsteemikĂ”ne abil bpf(2), seejĂ€rel, hiljem, erinevate mehhanismide abil, sĂ”ltuvalt programmi tĂŒĂŒbist, programmil on vĂ”imalus (attaches) sĂŒndmuse allikaga ĂŒhenduda.
Siin vĂ”ib lugejal tekkida kĂŒsimus: kas tĂ”esti oli see vĂ”imalik? Kuidas tagatakse sellise koodi kĂ€itamise turvalisus? Turvalisus tagatakse BPF-programmide laadimise etappimisega, mida nimetatakse valideerijaks (inglise keeles nimetus on verifier ja ma kasutan edaspidi ingliskeelset sĂ”na):

Verifier on staatiline analĂŒĂŒsija, mis tagab, et programm ei sega tuuma normaalset toimimist. See aga ei tĂ€henda, et programm ei saaks sĂŒsteemi sekkuda â BPF programmid, sĂ”ltuvalt tĂŒĂŒbist, saavad lugeda ja kirjutada tuuma mĂ€lu osi, muuta funktsioonide tagastatavaid vÀÀrtusi, kĂ€rpida, tĂ€iendada, kirjutada ja isegi ĂŒmber suunata vĂ”rgu pakette. Verifier tagab, et BPF programmi kĂ€ivitamine ei pĂ”hjusta tuuma kokku kukkumist ja et programm, mis vĂ”ib reeglitesse tuginedes kirjutada andmeid, nĂ€iteks vĂ€ljamineva paketi, ei suuda kirjutada tuuma mĂ€lu vĂ€ljaspool paketti. Natuke pĂ”hjalikumalt vaatame verifierit vastavas osas, pĂ€rast seda, kui tutvume kĂ”igi teiste BPF komponentidega.
Nii et, mida me siiani Ă”ppisime? Kasutaja kirjutab programmi C keeles, laadib selle tuuma sĂŒsteemikĂ”ne abil, bpf(2), kus see lĂ€bib verifi katse ja tĂ”lgitakse masinkoodiks. SeejĂ€rel ĂŒhendab sama vĂ”i teine kasutaja programmi sĂŒndmuste allikaga ja see hakkab töötama. Laadimise ja ĂŒhendamise eristamine on vajalik mitmel pĂ”hjusel. Esiteks, verifi kĂ€ivitamine on suhteliselt kallis ja laadides sama programmi mitu korda, raiskame me arvuti aega asjata. Teiseks, kuidas programm tĂ€pselt ĂŒhendatakse, sĂ”ltub selle tĂŒĂŒbist ja ĂŒks "ĂŒldine" liides, mis loodi aasta tagasi, ei pruugi sobida uute programmide tĂŒĂŒpide jaoks. (Kuigi nĂŒĂŒd, kui arhitektuur muutub kĂŒpsemaks, on idee see liides ĂŒhtlustada tasemel) libbpf.)
Hooliv lugeja vĂ”ib mĂ€rkida, et me pole veel piltide osas lĂ”petanud. TĂ”epoolest, eespool öeldu ei selgita, kuidas BPF pĂ”himĂ”tteliselt muudab pilti vĂ”rreldes klassikalise BPF-iga. Kaks uuendust, mis oluliselt laiendavad rakendamisvĂ”imalusi, on jagatud mĂ€lu kasutamise vĂ”imalus ja tuuma abifunktsioonid (kernel helpers). BPF-is on jagatud mĂ€lu rakendatud nii-öelda maps'i abil - jagatud andmestruktuurid kindla API-ga. Sellise nime nad said tĂ”enĂ€oliselt seetĂ”ttu, et esimene ilmunud maps'i tĂŒĂŒp oli rĂ€sitabel. Edasi on tulnud massiivid, lokaalsed (per-CPU) rĂ€sitabelid ja lokaalsed massiivid, otsingupuud, maps'id, mis sisaldavad viiteid BPF-programmidele, ja palju muud. Meid huvitab praegu fakt, et BPF-programmid said vĂ”imaluse salvestada seisundi kutsungite vahel ja jagada seda teiste programmide ning kasutajaruumi vahel.
Maps'esse pÀÀseb kasutajaprotsessidest sĂŒsteemi kĂ”ne abil bpf(2), ning BPF-programmid, mis töötavad tuumas, pÀÀsevad sellele ligi abifunktsioonide kaudu. Veelgi enam, abifunktsioonid ei eksisteeri mitte ainult maps'iga töötamiseks, vaid ka muude tuuma vĂ”imaluste kasutamiseks. NĂ€iteks saavad BPF-programmid kasutada abifunktsioone pakettide ĂŒmbersuunamiseks teistele liidesele, perf-alamsĂŒsteemi sĂŒndmuste genereerimiseks, tuuma struktuuridele ja nii edasi pÀÀsemiseks.

KokkuvĂ”ttes annab BPF vĂ”imaluse laadida tuuma ruumi suvalist, st verifikaatoril lĂ€bi kĂ€inud kasutaja koodi. See kood vĂ”ib salvestada seisundi kutsungite vahel ja vahetada andmeid kasutajaruumi ning tal on juurdepÀÀs selle tĂŒĂŒbi programmidele lubatud tuuma alamsĂŒsteemidele.
See on juba sarnane vĂ”imalustele, mida pakuvad tuumamoodulid, mille suhtes BPF-l on mĂ”ned eelised (loomulikult saab sarnaseid rakendusi vĂ”rrelda ainult sarnastega, nĂ€iteks sĂŒsteemi jĂ€lgimisega â BPF-le ei saa kirjutada suvalist draiverit). VĂ”ib mĂ€rkida madalamat sisenemislĂ€ve (osad BPF-i kasutavad utiliitide ei eelda, et kasutajal oleks tuumaprogrammeerimise oskused, ega ka ĂŒldse programmeerimisoskused), kĂ€itamisohutust (tĂ”stke kĂ€si kommentaarides need, kellel pole olnud sĂŒsteemi riket mooduleid kirjutades vĂ”i testides), aatomilisust â moodulite laadimise ajal on seisaku aeg, kuid BPF-i alamsĂŒsteem tagab, et mitte ĂŒkski sĂŒndmus ei jÀÀ tĂ€helepanuta (aususe nimel, see ei kehti mitte kĂ”ikide BPF-i programmiliikide kohta).
Need vĂ”imalused muudavad BPF-i universaalseks tööriistaks tuuma laiendamiseks, mida tĂ”endab praktika: jĂ€rjest rohkem ja rohkem uut tĂŒĂŒpi programme lisandub BPF-ile, ĂŒha rohkem suurettevĂ”tteid kasutab BPF-i tootmisserverites 24Ă7, ĂŒha rohkem idufirmasid ehitab oma Ă€ri lahendustele, mille aluseks on BPF. BPF-i kasutatakse kĂ”ikjal: DDoS-rĂŒnnakute kaitseks, SDN-i loomisel (nĂ€iteks Kubernetesest vĂ”rkude rakendamine), peamise sĂŒsteemi jĂ€lgimise tööriistana ja statistika kogumisena, sissetungi tuvastamise sĂŒsteemides ja liivakasti sĂŒsteemides jms.
LĂ”petame selle ĂŒlevaateosa artiklist ja vaatame virtuaalmasinat ning BPF-i ökosĂŒsteemi pĂ”hjalikumalt.
NĂŒanss: utiliidid
Et saaksite jÀrgmistes osades nÀidatud nÀiteid kÀivitada, vÔib teil olla vaja teatud arvu utiliite, vÀhemalt llvm/clang BPF-i toe ja bpftool. Osas jaotatud juhised nende utiliitide ning oma tuuma koostamiseks. See osa on paigutatud alla, et mitte segada meie arutelu selgust.
BPF virtuaalmasina registrid ja kĂ€skude sĂŒsteem
BPF arhitektuur ja kĂ€skude sĂŒsteem on loodud silmas pidades, et programmid kirjutatakse C keeles ja tĂ”lgitakse pĂ€rast laadimist tuumasse natiivkoodi. SeetĂ”ttu valiti registrite ja kĂ€skude arv, arvestades tĂ€napĂ€evaste masinate vĂ”imaluste ristumisi matemaatilises mĂ”ttes. Lisaks kehtestati programmidele mitmesuguseid piiranguid; nĂ€iteks ei olnud kuni hiljuti vĂ”imalik kirjutada silmuseid ja alamprogramme, ning kĂ€skude arv oli piiratud 4096-ga (praegu saavad privileeritud programmid laadida kuni miljon kĂ€sku).
BPF-s on kasutajale ligipÀÀsetavad ĂŒheksa 64-bitist registreid r0âr10 ja kĂ€skude loendur (program counter). Register r10 hoiab raami nĂ€idikut (frame pointer) ja on ainult lugemiseks saadaval. Programmidele on tĂ€itmise ajal ligipÀÀsetav 512-bytesit tagasihoidlik salvestus ja piiramatu jagatud mĂ€lu kaardina (maps).
BPF programmidele on lubatud kĂ€ivitada kindel funktsioonide kogum sĂ”ltuvalt programmi tĂŒĂŒbist (kernel helpers) ja hiljuti ka tavalisi funktsioone. Iga kutsutav funktsioon vĂ”ib vastu vĂ”tta kuni viis argumendit, mis edastatakse registrites r1âr5, ning tagastatav vÀÀrtus edastatakse registrisse r0. Tagatakse, et pĂ€rast funktsioonist vĂ€ljumist registrite sisu r6âr9 ei muutu.
Kuna programmide tĂ”husaks tĂ”lkimiseks on kĂ”ik toetatud arhitektuuride registrid r0âr11 kaardistatud selgelt tegelikele registeritele, arvestades hetke arhitektuuri ABI eripĂ€rasid. NĂ€iteks kasutatakse x86_64 registreid r1âr5, mis edastavad funktsioonide parameetreid, registreerimiseks rdi, rsi, rdx, rcx, r8, mis on kasutusel parameetrite edastamiseks funktsioonidesse x86_64. NĂ€iteks tĂ”lgitakse vasakul olev kood paremal poole nii:
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 0x0000000000001ee8Register r0 kasutatakse ka programmi tĂ€itmise tulemuse tagastamiseks, ja registris r1 edastatakse programmile konteksti nĂ€idik â sĂ”ltuvalt programmi tĂŒĂŒbist vĂ”ib see olla nĂ€iteks struktuur (XDP jaoks) vĂ”i struktuur (erinevate vĂ”rguprogrammide jaoks) vĂ”i struktuur (erinevate jĂ€lgimisprogrammide jaoks) jne.
Nii, meil oli registeride komplekt, kernel helpers, stakk, konteksti nĂ€itaja ja jagatud mĂ€lu kujul maps. Mitte et kĂ”ik see oleks kindlasti vajalik reisil, agaâŠ
JĂ€tkame kirjeldust ja rÀÀgime kĂ€skude sĂŒsteemist nende objektide töötlemiseks. KĂ”ik () BPF-kĂ€sud on fikseeritud 64-bitise suurusega. Kui vaatate ĂŒht kĂ€sku 64-bitises Big Endian masinas, nĂ€ete
![]()
Siin Kood â see on kĂ€su kodeering, Dst/Src â need on vastavalt siht- ja allika kodeeringud, KĂ”rval â 16-bitine signeeritud nihkevÀÀrtus, ja Imm â see on 32-bitine signeeritud tĂ€isarv, mida kasutatakse mĂ”nedes kĂ€skudes (sarnane konstant K-st cBPF-is). Kodeering Kood on ĂŒhes kahest vormist:

KĂ€skude klassid 0, 1, 2, 3 mÀÀratlevad kĂ€sud mĂ€lu töötlemiseks. Nad , BPF_LD, BPF_LDX, BPF_ST, BPF_STX, vastavalt. Klassid 4, 7 (BPF_ALU, BPF_ALU64) koosnevad ALU-kĂ€skudest. Klassid 5, 6 (BPF_JMP, BPF_JMP32) sisaldavad hĂŒppe kĂ€ske.
Edasi on BPF-kĂ€skude Ă”ppimise plaan jĂ€rgmine: selle asemel, et hoolikalt loetleda kĂ”iki kĂ€ske ja nende parameetreid, vaatame siin paar nĂ€idet ja selgub, kuidas kĂ€skud tegelikult on ĂŒles ehitatud ja kuidas kĂ€sitsi disassembeldada igasuguseid binaarfailide BPF jaoks. Teema kinnistamiseks kohtame artikli edasistes osades individuaalseid kĂ€ske, mis puudutavad Verifierit, JIT kompilaatorit, klassikalise BPF tĂ”lkimist ning ka mapside ja funktsioonide kutsumist jne.
Kui rÀÀgime individuaalsetest kĂ€skudest, viitame me kernelifailidele ja , kus on mÀÀratletud BPF kĂ€su numbrilised koodid. Kui uurite arhitektuuri ja/vĂ”i vaatate ĂŒle binaare, vĂ”ite leida semantika jĂ€rgmistest allikatest, mis on jĂ€rjestatud keerukuse jĂ€rgi: , , ja muidugi Linuxi lĂ€htekoodis â verifier, JIT, BPF tĂ”lkija.
NÀide: disassembeldame BPF mÔttes
Vaadake seda nÀidet, kus kompileerime programmi readelf-example.c ja vaatame, mis binaar on saadud. Avame originaalse sisu readelf-example.c allpool, pÀrast seda, kui oleme taastanud selle loogika binaarkoodidest:
$ 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 ................Esimene veerg vĂ€ljundis readelf â see on sisend ja meie programm koosneb seega neljast kĂ€sklusest:
Code Dst Src Off Imm
b7 0 0 0000 01000000
15 0 1 0100 00000000
b7 0 0 0000 02000000
95 0 0 0000 00000000KĂ€skluskoodid on vĂ”rdsed b7, 15, b7 ja 95. Meenutame, et kolm madalaimat bitti tĂ€histavad kĂ€su klassi. Meie puhul on neljas bitt kĂ”ikide kĂ€skluste jaoks tĂŒhi, seega on kĂ€su klassid vĂ”rdsed, vastavalt 7, 5, 7, 5. Klass 7 on BPF_ALU64, aga 5 on BPF_JMP. MĂ”lema klassi korral on kĂ€su formaat sama (vt eelnevat) ja saame meie programmi ĂŒmber kirjutada nii (samuti kirjutame ĂŒlejÀÀnud veerud inimlikus vormis):
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 0Tehe b klassist ALU64 â see on . See mÀÀrab vÀÀrtuse sihtregistrisse. Kui on seatud bitt s (source), siis vÀÀrtus vĂ”etakse allikaregistrist, kuid kui, nagu meie puhul, see ei ole seatud, siis vÀÀrtus vĂ”etakse vĂ€ljast Imm. Seega teeme esimese ja kolmanda kĂ€su korral operatsiooni r0 = Imm. JĂ€rgmine kĂ€sk 1 klassi JMP â see on (hĂŒpe, kui vĂ”rdsed). Meie puhul, kuna bitt S on null, vĂ”rdleb see allikaregistri vÀÀrtust vĂ€ljadega Imm. Kui vÀÀrtused kattuvad, toimub ĂŒleminek PC + Off, kus PC, nagu tavaliselt, sisaldab see jĂ€rgmise kĂ€su aadressi. LĂ”puks, kĂ€sk 9 klassi JMP â see on . See kĂ€sk lĂ”petab programmi toimimise, tagastades kernelile r0. Lisame meie tabelisse uue veeru:
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 exitSaame selle ĂŒmber kirjutada mugavamas vormis:
r0 = 1
if (r1 == 0) goto END
r0 = 2
END:
exitKui me meenutame, et registris r1 edastatakse programmi kaudu konteksti viidatud tuumale, ning registris r0 tagastatakse vÀÀrtus tuumale, siis vÔime nÀha, et kui konteksti viidatud on null, siis tagastame 1, vastasel juhul 2. Kontrollime, et oleme Ôiged, vaadates lÀhtekoodi:
$ cat readelf-example.c
int foo(void *ctx)
{
return ctx ? 2 : 1;
}Jah, see on mÔttetu programm, kuid see kompileeritakse vaid neljaks lihtsaks kÀsuks.
NÀide-erand: 16-baidine kÀsk
Oleme varem maininud, et mĂ”ned kĂ€sud vĂ”tavad rohkem kui 64 bitti. See kehtib nĂ€iteks kĂ€sku lddw (Code = 0x18 = | | ) â laadige registrisse kahe sĂ”naga andmed vĂ€ljadest Imm. Asi on selles, et Imm on 32 byte size, and a double word â 64 bits, so we cannot load a 64-bit immediate value into a register in one 64-bit instruction. For this, two adjacent instructions are used to store the second part of the 64-bit value in the field Imm. NĂ€idis:
$ 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 ........The binary program contains only two instructions:
Binary Disassm
18000000 ddccbbaa 00000000 44332211 r0 = Imm[0]|Imm[1]
95000000 00000000 exitWe will meet again with the instruction lddw, when we talk about relocations and working with maps.
Example: disassembling BPF using standard tools
So, we have learned to read BPF binary codes and are ready to disassemble any instruction if needed. However, it is worth noting that in practice, it is more convenient and faster to disassemble programs using standard tools, for example:
$ llvm-objdump -d x64.o
Disassembly of section .text:
0000000000000000 :
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 exitThe life cycle of BPF objects, the bpffs filesystem
(Some details presented in this subsection were first learned from by Alexei Starovoitov in .)
BPF objects â programs and maps â are created from user space using the commands BPF_PROG_LOAD ja BPF_MAP_CREATE system call bpf(2), we will discuss how this happens in the next section. This creates kernel data structures and for each of them refcount (reference count) is set to one, and the user is returned a file descriptor pointing to the object. After closing the descriptor refcount the objectâs reference count reduces by one, and when it reaches zero, the object is destroyed.
If the program uses maps, then refcount the reference count of these maps increases by one after the program is loaded, i.e., their file descriptors can be closed from the user process without causing the count refcount to drop to zero:

After successfully loading the program, we usually attach it to some event generator. For example, we can attach it to a network interface to process incoming packets or connect it to some tracepoint in the kernel. At this moment, the reference count will also increase by one, and we will be able to close the file descriptor in the loading program.
Mis juhtub, kui me nĂŒĂŒd laadijaga töö lĂ”petame? see sĂ”ltub sĂŒndmuste generaatori (hook) tĂŒĂŒbist. KĂ”ik vĂ”rguhooked jÀÀvad alles pĂ€rast laadija lĂ”petamist, need on nn globaalhookid. NĂ€iteks jĂ€lgimisprogrammid vabanevad pĂ€rast nende loonud protsessi lĂ”puleviimist (seega nimetatakse neid kohalikeks, mis tuleneb terminist 'local to the process'). Tehniliselt on kohalikud hookid alati seotud vastava failidĆŸa vaba ruumiga ja seega sulguvad protsessi lĂ”petamisega, samas kui globaalhookid ei sulgu. JĂ€rgmises joonises pĂŒĂŒan punaste ristikestega nĂ€idata, kuidas laadija programmi lĂ”petamine mĂ”jutab objektide eluiga kohalike ja globaalsete hookide korral.

Miks on olemas kohalike ja globaalsete hookide eraldamine? MĂ”nede tĂŒĂŒpi vĂ”rguprogrammide kĂ€itamine on mĂ”ttekas ka ilma userspace'ita, nĂ€iteks kujutage ette DDoS-kaitset - laadija mÀÀrab reeglid ja ĂŒhendab BPF programmi vĂ”rguliidesega, pĂ€rast mida laadija vĂ”ib ise lĂ”petada. Teisest kĂŒljest, kujutage ette jĂ€lgimisprogrammi, mille te kĂŒmne minutiga ise kokku keerasite - pĂ€rast selle lĂ”petamist sooviksite, et sĂŒsteemis ei jÀÀks prĂŒgi, ja kohalikud hookid tagavad selle.
Teiselt poolt, kujutage ette, et soovite ĂŒhenduda jĂ€lgimispunktiga (tracepoint) tuumas ja koguda statistikat aastaid. Sel juhul sooviksite lĂ”petada kasutaja osa ja aeg-ajalt statistika juurde tagasi tulla. Selle vĂ”imaluse pakub bpf failisĂŒsteem. See on pseudo-failisĂŒsteem, mis eksisteerib ainult mĂ€lus ja vĂ”imaldab luua faile, mis viitavad BPF objektidele, ja seelĂ€bi pikendada refcount objekte. PĂ€rast seda vĂ”ib laadija lĂ”petada oma töö ja loodud objektid jÀÀvad elama.

BPF objektide pindamine ("pin", nagu jÀrgmises lauses: "process can pin a BPF program or map") BPF objektide jaoks failide loomine on mÔttekas mitte ainult kohalike objektide elu pikendamiseks, vaid ka globaalsete objektide mugavamaks kasutamiseks - naastes DDoS-i kaitse globaalprogrammi nÀite juurde, tahame meil oleks aeg-ajalt vÔimalik tagasi tulla ja statistika peale vaadata.
BPF faili sĂŒsteem monteeritakse tavaliselt /sys/fs/bpf, kuid selle saab monteerida ka kohapeal, nĂ€iteks nii:
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpointFailisĂŒsteemis nimed luuakse kĂ€suga BPF_OBJ_PIN BPF sĂŒsteemikĂ”ne abil. NĂ€itena vĂ”tame mĂ”ne programmi, kompileerime, laadime ja kinnitame selle bpffs. Meie programm ei tee midagi kasulikku, toome selle koodi vaid selleks, et saaksite nĂ€idist korrata:
$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
return 0;
}
char _license[] __attribute__((section("license"), used)) = "GPL";Kompileerime selle programmi ja loome kohaliku koopia faili sĂŒsteemist bpffs:
$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpointNĂŒĂŒd laadime meie programmi utiliidi abil bpftool ja vaatame kaasnevaid sĂŒsteemikĂ”nesid bpf(2) (strace vĂ€ljundist on mĂ”ned asjakohatud read eemaldatud):
$ 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) = 0Siin laadisime programmi BPF_PROG_LOAD, saime kernelilt faili deskripti 3 ja kĂ€suga BPF_OBJ_PIN kinnitasime selle faili deskripti failina "bpf-mountpoint\/test". PĂ€rast seda lĂ”petas laadimisprogramm töö, kuid meie programm jĂ€i karneli, kuigi me ei kinnitanud seda ĂŒhelgi vĂ”rguliidesele: bpftool $ 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
Saame failiobjekti tavaliseunlink(2) ja pÀrast seda vastav programm kustutatakse: $ sudo rm .\/bpf-mountpoint\/test $ sudo bpftool prog show id 783 Error: get by id (783): No such file or directory
Objektide kustutamineRÀÀkides objektide kustutamisest, tuleb tĂ€psustada, et pĂ€rast programmi eraldamist konksust (sĂŒndmuste generaatorist) ei pĂ”hjusta ĂŒkski uus sĂŒndmus selle kĂ€ivitamist, kuid kĂ”ik jooksvaid programmi eksemplarid lĂ”petatakse korrektselt.
MĂ”ned BPF programmide tĂŒĂŒbid vĂ”imaldavad programmi töö ajal vahetada, st pakuvad jĂ€rjepidevust
replace = detach old program, attach new program . Sellega lĂ”petavad kĂ”ik vanade programmide aktiivsed eksemplarid oma töö ja uued sĂŒndmuste töötlejad luuakse juba uuest programmist, ning "jĂ€rjepidevus" tĂ€hendab siin, et ĂŒkski sĂŒndmus ei jÀÀ vahele.Sellega lĂ”petavad kĂ”ik aktiivsed vanade programmide eksemplarid oma töö ning uued sĂŒndmuste töötlejad luuakse juba uue programmi pĂ”hjal, kusjuures "aatomiilisus" tĂ€hendab seda, et ĂŒhtegi sĂŒndmust ei jÀÀ vahele.
Programmid sĂŒndmustele liitumine
Selles artiklis ei hakka me eraldi kirjeldama programmide ĂŒhendamist sĂŒndmustele, kuna seda on mĂ”ttekas uurida konkreetse programmiliigi kontekstis. Vaata. allpool, kus nĂ€itame, kuidas liituvad XDP tĂŒĂŒpi programmid.
Objektide haldamine BPF sĂŒsteemikĂ”ne abil
BPF programmid
KĂ”ik BPF objektid luuakse ja hallatakse kasutaja ruumist sĂŒsteemikĂ”ne abil bpf, millel on jĂ€rgmine prototĂŒĂŒp:
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);Siin kĂ€sk cmd on ĂŒks vÀÀrtusi , attr â nĂ€itaja konkreetse programmi parameetritele ja size â objekti suurus nĂ€itaja jĂ€rgi, st tavaliselt see on sizeof(*attr). Kerneli versioonis 5.8 toetab sĂŒsteemikĂ”ne bpf 34 erinevat kĂ€sku, ja union bpf_attr koosneb 200 reast. Kuid see ei peaks meid hirmutama, kuna tutvume kĂ€skude ja parameetritega mitme artikli jooksul.
Alustame kĂ€sust BPF_PROG_LOAD, mis loob BPF programme â vĂ”tab BPF kĂ€sukogumi ja laadib selle ykerneli. Laadimise hetkel aktiveeritakse valideerija ning seejĂ€rel JIT kompilaator ja pĂ€rast edukat tĂ€itmist tagastatakse kasutajale programmifaili deskriptor. Olime varem nĂ€inud, mida sealt edasi toimub .
Praegu kirjutame kasutaja programmi, mis laadib lihtsa BPF programmi, kuid kĂ”igepealt peame otsustama, millise programmi tahame laadida â peame valima ja selle tĂŒĂŒbi raames kirjutama programmi, mis lĂ€bib valideerijat. Kuid et protsessi keerulisemaks mitte muuta, siin on valmis lahendus: vĂ”tame programmi tĂŒĂŒpi BPF_PROG_TYPE_XDP, mis tagastab vÀÀrtuse XDP_PASS (kĂ”ik paketid jĂ€tta vahele). BPF assambleeris see vĂ€lja nĂ€eb vĂ€ga lihtne:
r0 = 2
exitPÀrast seda, kui oleme otsustanud, mida mida me laadime, saame rÀÀkida, kuidas me seda teeme:
#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();
}Huvilised sĂŒndmused programmis algavad massiivi mÀÀratlemisest insns â meie BPF programm masinakoode sÀÀstev. Samuti sisaldab iga BPF kĂ€su juhist struktuuris . Esimene element insns vastab kĂ€skudele r0 = 2, teine â exit.
KÀik. Kernelis on mÀÀratletud mugavamad makrod masinakoode kirjutamiseks ja kasutades kernelipeade faili tools/include/linux/filter.h saaksime kirjutada
struct bpf_insn insns[] = {
BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
BPF_EXIT_INSN()
};Kuna BPF programmide kirjutamine masinkoodides on vajalik ainult testide ja BPF artiklite kirjutamiseks, siis nende makrode puudumine ei tee arendaja elu tegelikult keerulisemaks.
PĂ€rast BPF programmi mÀÀratlemist liigume selle laadimise juurde kernelisse. Meie minimalistlik parameetrite komplekt attr sisaldab programmi tĂŒĂŒpi, kĂ€skude arvu ja komplekti, kohustuslikku litsentsi ning nime "woo", mida me kasutame, et leida meie programm sĂŒsteemis pĂ€rast selle laadimist. Programm laaditakse sĂŒsteemi nagu lubatud sĂŒsteemikĂ”ne abil bpf.
Programmi lĂ”pus satume lĂ”pmatusse tsĂŒklisse, mis simuleerib kasulikku koormust. Ilma selleta hĂ€vitab programm kernel, kui faili deskriptor, mille sĂŒsteemikĂ”ne meile andis, suletakse bpf, ja me ei nĂ€e seda sĂŒsteemis.
Noh, oleme testimiseks valmis. Kogume ja kÀivitame programmi strace, et kontrollida, kas kÔik töötab nagu peab:
$ 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_VERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(KĂ”ik on korras, bpf(2) meil on deskriptor 3 ja me suundume lĂ”pmatusse tsĂŒklisse koos pause(). Proovime nĂŒĂŒd leida meie programmi sĂŒsteemis. Selleks lĂ€heme teise terminali ja kasutame utiliiti 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)NĂ€eme, et sĂŒsteemis on laaditud programm woo mille globaalne ID on 390, ja et praegu protsessis simple-prog on avatud faili deskriptor, mis viitab programmile (ja kui simple-prog see lĂ”petab töö, siis woo kaob). Nagu oodata oli, programm woo vĂ”tab 16 baiti â kaks kĂ€sku â BPF arhitektuuri binaarkoodis, kuid originaali (x86_64) formaat on juba 40 baiti. Vaadake meie programmi originaalses vormis:
# bpftool prog dump xlated id 390
0: (b7) r0 = 2
1: (95) exitilma ĂŒllatusteta. NĂŒĂŒd vaatame koodi, mille on loonud JIT kompilaator:
# 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: retqei ole kuigi tÔhus exit(2), kuid Ôiguse nimel, meie programm on liiga lihtne, ja mitte triviaalsete programmide puhul on JIT kompilaatori poolt lisatud proloog ja epiloog kindlasti vajalikud.
Maps
BPF-programmid saavad kasutada struktuuritud mĂ€lu alasid, mis on kergesti kĂ€ttesaadavad nii teistele BPF-programmidele kui ka kasutajaruumi programmidele. Need objekte nimetatakse "maps" ja kĂ€esolevas osas nĂ€itame, kuidas neid hallata sĂŒsteemikutsumise abil. bpf.
Ătleme kohe, et maps'i vĂ”imalused ei piirdu ainult juurdepÀÀsuga jagatud mĂ€lule. On olemas spetsiaalsed kaardid, mis sisaldavad nĂ€iteks viiteid BPF-programmidele vĂ”i viiteid vĂ”rguliidestele, kaardid perf-events'i jaoks ja nii edasi. Siin me neist rÀÀkima ei hakka, et lugejat segadusse ei ajada. Lisaks ignoreerime sĂŒnkroniseerimisprobleeme, kuna need ei ole meie nĂ€idete jaoks olulised. TĂ€ieliku nimekirja saadaval olevatest kaarditĂŒĂŒpidest leiate. , ja kĂ€esolevas osas vĂ”tame me nĂ€itena ajalooliselt esimese tĂŒĂŒbi, hash-tabeli. BPF_MAP_TYPE_HASH.
Kui loote hash-tabeli, ĂŒtleme, et C++-s ĂŒtlete: unordered_map woo, mis eesti keeles tĂ€hendab "mul on vaja tabelit, woo mis on piiranguteta suurusega, mille vĂ”tmed on tĂŒĂŒpi int, ja vÀÀrtused on tĂŒĂŒpi long." BPF-hash-tabeli loomiseks peame me tegema enam-vĂ€hem sama, arvestades, et peame mÀÀrama tabeli maksimaalse suuruse ja vĂ”tmete ning vÀÀrtuste tĂŒĂŒbi asemel peame me tĂ€psustama nende suurused baitides. Kaartide loomiseks kasutatakse kĂ€sku BPF_MAP_CREATE system call bpf. Vaatame ĂŒle enam-vĂ€hem minimaalse programmi, mis loob kaardi. Eelmise BPF-programmi laadimise jĂ€rel peaks see teile tunduma lihtne:
$ cat simple-map.c
#define _GNU_SOURCE
#include
#include
#include
#include
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();
}Siin mÀÀratleme parametrites kogumi, attr, kus ĂŒtlen "mul on vaja hash-tabelit, mille vĂ”tmed ja vÀÀrtused on suurusega sizeof(int), kuhu ma saan paigutada maksimaalselt neli elementi." BPF-kaartide loomisel vĂ”ib mÀÀrata ka teisi parameetreid, nĂ€iteks, nagu programmide nĂ€ites, mÀÀrasime objekti nimeks "woo".
Kompileerime ja kÀivitame programmi:
$ 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(Siin on sĂŒsteemikutsung bpf(2) tagastas meile kaardiga seotud deskriptorinumbri 3 ja edasi programm, nagu oodatud, ootab sĂŒsteemikutses edasisi juhiseid pause(2).
NĂŒĂŒd saadame meie programmi taustale vĂ”i avame teise terminali ning vaatame meie objekti seejĂ€rel utiliidi abil bpftool (me saame eristada meie kaarti teistest selle nime jĂ€rgi):
$ sudo bpftool map
...
114: hash nimi woo flags 0x0
key 4B value 4B max_entries 4 memlock 4096B
...Number 114 on meie objekti globaalne ID. Iga programm sĂŒsteemis vĂ”ib kasutada seda ID-d, et avada juba olemasolev kaart kĂ€suga BPF_MAP_GET_FD_BY_ID system call bpf.
NĂŒĂŒd saame hakata katsetama meie hash-tabeliga. Vaatame selle sisu:
$ sudo bpftool map dump id 114
Leitud 0 elementiTĂŒhi. Paneme nĂŒĂŒd sinna vÀÀrtuse hash[1] = 1:
$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0Vaatame tabelit veel kord:
$ sudo bpftool map dump id 114
key: 01 00 00 00 value: 01 00 00 00
Leitud 1 elementHurraa! Meil Ă”nnestus lisada ĂŒks element. Pane tĂ€hele, et selleks peame töötama baitide tasemel, kuna bptftool ei tea, milline tĂŒĂŒp on vÀÀrtustel hash-tabelis. (Seda teavet saab anda, kasutades BTF-d, kuid mitte nĂŒĂŒd.)
Kuidas bpftool tÀpselt elemente loeb ja lisab? Uurime, mis peidab end kapoti all:
$ 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 ENOENTEsmalt avasime kaardi selle globaalsete ID-de pĂ”hjal kĂ€suga BPF_MAP_GET_FD_BY_ID ja bpf(2) tagastas meile deskriptor 3. Edasi kĂ€suga BPF_MAP_GET_NEXT_KEY leidis me tabelis esimese vĂ”tme, edastades NULL nagu "eelneva" vĂ”tme nĂ€itaja. Kui meil on vĂ”tme olemas, saame teha BPF_MAP_LOOKUP_ELEM, mis tagastab vÀÀrtuse nĂ€itaja value. JĂ€rgmine samm â proovime leida jĂ€rgmise elemendi, edastades nĂ€itaja praegusele vĂ”tmele, kuid meie tabelis on ainult ĂŒks element ja kĂ€sk BPF_MAP_GET_NEXT_KEY toob tagasi ENOENT.
HÀsti, muudetakse vÀÀrtust vÔtme 1 kaudu, oletame, et meie Àri loogika nÔuab selle mÀÀramist 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) = 0Kuna oodatud, on see vĂ€ga lihtne: kĂ€sk BPF_MAP_GET_FD_BY_ID ava meie kaardi ID kaudu, ja kĂ€sk BPF_MAP_UPDATE_ELEM ĂŒlekirjutab elemendi.
KokkuvĂ”ttes, pĂ€rast rĂ€sitabeli loomist ĂŒhest programmist saame lugeda ja kirjutada selle sisu teisest. Pange tĂ€hele, et kui me suutsime seda teha kĂ€surealt, siis suudab seda ka iga teine programm sĂŒsteemis. Ăheks koos kĂ€itumiseks on kasutusel :
BPF_MAP_LOOKUP_ELEM: leida vÀÀrtus vĂ”tme jĂ€rgiBPF_MAP_UPDATE_ELEM: vĂ€rskendada/luua vÀÀrtusBPF_MAP_DELETE_ELEM: kustutada vĂ”tiBPF_MAP_GET_NEXT_KEY: leida jĂ€rgmine (vĂ”i esimene) vĂ”tiBPF_MAP_GET_NEXT_ID: vĂ”imaldab lĂ€bida kĂ”ik olemasolevad mapid, nii töötabbpftool mapBPF_MAP_GET_FD_BY_ID: avada olemasolev map selle globaalsete ID-de jĂ€rgiBPF_MAP_LOOKUP_AND_DELETE_ELEM: atomaarne objekti vÀÀrtuse vĂ€rskendamine ja vana tagastamineBPF_MAP_FREEZE: muuta map muutumatuks userspace'ist (seda toimingut ei saa tĂŒhistada)BPF_MAP_LOOKUP_BATCH,BPF_MAP_LOOKUP_AND_DELETE_BATCH,BPF_MAP_UPDATE_BATCH,BPF_MAP_DELETE_BATCH: massilised toimingud. NĂ€iteks,BPF_MAP_LOOKUP_AND_DELETE_BATCHâ see on ainus usaldusvÀÀrne viis lugeda ja nullida kĂ”ik vÀÀrtused mapist
Mitte kĂ”ik need kĂ€sud ei tööta kĂ”igi mapitĂŒĂŒpide puhul, kuid ĂŒldiselt on töö teiste mapide tĂŒĂŒpidega userspace'ist sama, nagu rĂ€sitabelitega.
Korraks lÔpetame meie katsetused rÀsitabeliga. Pidage meeles, et me lÔime tabeli, kuhu vÔib mahtuda kuni neli vÔtit? Lisame veel mÔned elemendid:
$ 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 0Praegu on kÔik hÀsti:
$ 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
Leitud 4 elementiProovime lisada veel ĂŒhe:
$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: vÀrskendamine ebaÔnnestus: Argumendi loend on liiga pikkNagu oodatud, ei Ônnestunud meil. Vaatame viga lÀhemalt:
$ 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 (Argumendi loend on liiga pikk)
Error: vÀrskendamine ebaÔnnestus: Argumendi loend on liiga pikk
+++ lĂ”petas 255 +++KĂ”ik on korras: nagu oodatud, kĂ€sk BPF_MAP_UPDATE_ELEM ĂŒritab luua uut, viiendat, vĂ”tit, kuid ebaĂ”nnestub E2BIG.
Nii, me oskame luua ja laadida BPF programme ning hallata kaarte kasutajaruumi kaudu. NĂŒĂŒd on loogiline vaadata, kuidas saame kaarte kasutada BPF programmides endis. Me vĂ”iksime rÀÀkida sellest raskesti loetavate masinkoodide makrode keeles, kuid tegelikult on kĂ€tte jĂ”udnud aeg nĂ€idata, kuidas BPF programme kirjutatakse ja hooldatakse â abi lĂ€bi libbpf.
(Lugejatele, kes on rahulolematu madala taseme nĂ€idete puudumise ĂŒle: me arutame ĂŒksikasjalikult programme, mis kasutavad kaarte ja abifunktsioone, mis on loodud lĂ€bi libbpf ja rÀÀgime, mis toimub kĂ€su tasemel. Lugejatele, kes on rahulolematu vĂ€ga tugevalt, oleme lisanud seoses artikli sobivasse kohta.)
Kirjutame BPF-i programme libbpf-kasutades
BPF programmide kirjutamine masinkoodide kaudu vĂ”ib olla huvitav ainult esialgu, seejĂ€rel muutub see tĂŒĂŒtuks. Sel hetkel tuleks oma pilk pöörata llvm, mis sisaldab tagakĂŒlge BPF arhitektuurile koodi genereerimiseks, ja ka raamatukogu libbpf, mis vĂ”imaldab kirjutada BPF rakenduste kasutajapoolset osa ja laadida BPF programmide koodi, mis on genereeritud lĂ€bi llvm/clang.
Tegelikult, nagu nĂ€eme selles ja jĂ€rgnevates artiklites, libbpf teeb ĂŒsna palju tööd ja ilma selleta (vĂ”i sarnaste tööriistadeta â iproute2, libbcc, libbpf-go, jne.) ei saa elada. Ăks projekti killer-funktsioone libbpf on BPF CO-RE (Compile Once, Run Everywhere) â projekt, mis vĂ”imaldab kirjutada BPF programe, mis on ĂŒlekandmiseks erinevate tuumade vahel ja töötamine erinevate API-dega (nĂ€iteks, kui tuuma struktuur muutub erinevate versioonide vahel). Selleks, et CO-RE-ga töötada, peab teie tuum olema BTF toega kompileeritud (kuidas seda teha, rÀÀgime jaotises . Kontrollida, kas teie tuum on BTF toega kompileeritud, on vĂ€ga lihtne â olemasoleva faili jĂ€rgi:
$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinuxSee fail sisaldab teavet kĂ”igi andmetĂŒĂŒpide kohta, mida tuumas kasutatakse, ja seda kasutatakse kĂ”igis meie nĂ€idetes, mis kasutavad libbpf. RÀÀgime CO-RE-st pĂ”hjalikult jĂ€rgnevates artiklites, aga sel korral â lihtsalt koostage oma tuum CONFIG_DEBUG_INFO_BTF.
Raamatukogu libbpf elab otse kataloogis tools/lib/bpf tuuma ja selle arendamist juhitakse postiloendi kaudu bpf@vger.kernel.org. Kuid rakenduste jaoks, mis elavad kestast vÀljaspool, toetatakse eraldi hoidlat , kus kernelirakenduse teek peegeldub lugemiseks enam-vÀhem nii nagu on.
Selles osas vaatame, kuidas luua projekt, mis kasutab libbpf, kirjutame mÔned (enam-vÀhem mÔttetud) testprogrammid ja vaatame, kuidas see kÔik töötab. See vÔimaldab meil jÀrgnevates osades lihtsamalt selgitada, kuidas just BPF programmid interakteeruvad maps, kernel helpers, BTF jne.
Tavaliselt lisavad projektid, mis kasutavad libbpf , GitHubi hoidla git submodulina, teeme seda ka meie:
$ 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.Kogumine libbpf on vÀga lihtne:
$ 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.pcMeie edasine plaan selles osas on jĂ€rgmine: kirjutame BPF tĂŒĂŒpi programmi BPF_PROG_TYPE_XDP, sama, mis eelnevas nĂ€ites, aga C keeles, kompileerime selle kasutades clang, ja kirjutame abiprogrammi, mis laadib selle kernelisse. JĂ€rgnevates osades laiendame nii BPF programmi kui ka abiprogrammi funktsioone.
NÀide: loome tÀisfunktsionaalse rakenduse libbpf abil
Alustuseks kasutame ĂŒlalpool mainitud faili /sys/kernel/btf/vmlinux, ja loome selle ekvivalent pea failina:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.hSelles failis hoitakse kÔiki andmestruktuure, mis meie kernelis eksisteerivad, nÀiteks mÀÀratletakse siin IPv4 pÀis nii:
$ 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;
};NĂŒĂŒd kirjutame oma BPF programmi C keeles:
$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include
SEC("xdp/simple")
int simple(void *ctx)
{
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";Kuigi meie programm on vĂ€ga lihtne, peame siiski tĂ€helepanu pöörama mitmetele detailidele. Esiteks on esimene pĂ€isfail, mida me kaasame, vmlinux.h, mille me just genereerisime abiga bpftool btf dump â nĂŒĂŒd ei pea me enam installima kernel-headers paketti, et teada saada, kuidas tuumstruktuurid vĂ€lja nĂ€evad. JĂ€rgmine pĂ€isfail tuleb meile raamatukogust libbpf. Praegu on see meile vajalik ainult selleks, et makro SEC, mis saadab sĂŒmboli ELF objektifaili vastavasse sektsiooni, mÀÀratletaks. Meie programm on sektsioonis xdp/simple, kus enne kaldkriipsu mÀÀrame BPF programmi tĂŒĂŒbi â see on kokkulepe, mida kasutatakse libbpf, mis pĂ”hineb sektsiooni nimele, sisestab Ă”ige tĂŒĂŒbi kĂ€ivitamisel bpf(2). BPF programm ise on C â vĂ€ga lihtne ja koosneb ĂŒhisest reast return XDP_PASS. LĂ”puks sisaldab eraldi sektsioon "license" litsentsi nime.
Saame meie programmi kompileerida llvm/clang abil, versiooniga >= 10.0.0, veel parem â uuema (vt jaotist ):
$ 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.oHuvitavad omadused: me mÀÀrame sihtarhitektuuri -target bpf ja teed pĂ€isfailideni libbpf, mille me hiljuti installisime. Samuti Ă€rge unustage -O2, ilma selle valikuta vĂ”ivad teid oodata edaspidi ĂŒllatused. Vaatame meie koodi, kas suutsime kirjutada programmi, mida soovisime?
$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o
xdp-simple.bpf.o: file format elf64-bpf
Sektsiooni xdp/simple dekoodeerimine:
0000000000000000 :
0: r0 = 2
1: exitJah, see Ă”nnestus! NĂŒĂŒd on meil binaarfail programmiga ja tahame luua rakenduse, mis laadib selle tuuma. Selleks pakub raamatukogu meile kahte vĂ”imalust â kasutada madalama taseme API-d vĂ”i kĂ”rgema taseme API-d. Me valime teise tee, kuna tahame Ă”ppida, kuidas kirjutada, laadida ja ĂŒhendada BPF programme minimaalse vaevaga nende edasise uurimise jaoks. libbpf Esiteks peame genereerima meie programmi "skelett" sellest binaarfailist sama utiliidi abil
Alustuseks peame meie programmi âskeletiâ genereerima selle binaarfailist, kasutades sama utiliiti. bpftool â ĆĄveitsi armee noa BPF (mida saab mĂ”ista ka otseselt, sest Daniel Borkman on ĂŒks BPF loojatest ja hoidlaseisunditest â ĆĄveitslane):
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.hFailis xdp-simple.skel.h Sisaldab meie programmi binaarkoodi ja funktsioone haldamiseks â laadimiseks, ĂŒhendamiseks, meie objekti eemaldamiseks. Meie lihtsal juhul nĂ€eb see vĂ€lja ĂŒlemÀÀrasena, kuid see töötab ka olukordades, kus objektifail sisaldab palju BPF programme ja kaarte, ning selle tohutu ELF laadimiseks piisab, kui genereerida skelett ja kutsuda ĂŒhe vĂ”i kahe funktsiooni kasutajarakendusest, mille kirjutamise juurde me nĂŒĂŒd jĂ”uame.
Tegelikult on meie laadimisprogramm triviaalne:
#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);
}Siin struct xdp_simple_bpf mÀÀratakse failis xdp-simple.skel.h ja kirjeldab meie objektifaili:
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;
};Siit vĂ”ib mĂ€rgata madalamal tasemel API jĂ€lgi: struktuur struct bpf_program *simple ja struct bpf_link *simple. Esimene struktuur kirjeldab konkreetselt meie programmi, mis on salvestatud sektsiooni xdp/simple, ja teine kirjeldab seda, kuidas programm on ĂŒhendatud sĂŒndmuste allikaga.
Funktsioon xdp_simple_bpf__open_and_load, avab ELF objekti, parsib selle, loob kĂ”ik struktuurid ja alastruktuurid (kuna ELF-is on programme ja teisi sektsioone â andmed, ainult lugemiseks mĂ”eldud andmed, silumisinfo, litsents jne), ja laadib siis sĂŒdamikku sĂŒsteemikĂ”ne kaudu bpf, mille vĂ”ime kontrollida, kompileerides ja kĂ€ivitades programmi:
$ 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) = 4Vaatame nĂŒĂŒd oma programmi lĂ€bi bpftool. Leiame selle 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)ja teeme dumpimise (kasutame kĂ€skluse lĂŒhendatud vormi bpftool prog dump xlated):
# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
0: (b7) r0 = 2
1: (95) exitMidagi uut! Programm printis meie allika faili osad C keeles. Seda tegi teek libbpf, mis leidis binaaris silumissektsiooni, kompileeris selle BTF objektiks, laadis selle sĂŒdamikku kĂ€sku BPF_BTF_LOAD, ja seejĂ€rel kasutas saadud failide descriptorit programmi laadimiseks kĂ€su BPG_PROG_LOAD.
Kernel Helpers
BPF-i programmid vĂ”ivad kĂ€ivitada 'vĂ€list' funktsioone â kernel helpers. Need abifunktsioonid vĂ”imaldavad BPF-i programmidel pÀÀseda ligi kernelistruktuuridele, hallata maps'e ning suhelda 'reaalse maailmaga' â luua perf events, hallata riistvara (nt suunata pakette) jne.
NĂ€ide: bpf_get_smp_processor_id
Konteksti 'Ă”pime nĂ€idete kaudu' raames vaatame ĂŒhte abifunktsiooni, bpf_get_smp_processor_id(), failis kernel/bpf/helpers.c. See tagastab protsessori numbri, millel kĂ€ivitub seda kutsuv BPF-i programm. Kuid meid huvitab rohkem mitte selle semantika, vaid see, et selle rakendamine vĂ”tab ĂŒhe rea:
BPF_CALL_0(bpf_get_smp_processor_id)
{
return smp_processor_id();
}BPF-i abifunktsioonide mÀÀratlemine sarnaneb Linuxi sĂŒsteemikĂ”nede mÀÀratlemisega. Siin mÀÀratletakse nĂ€iteks argumentideta funktsioon. (Funktsioon, mis vĂ”tab nĂ€iteks kolm argumenti, mÀÀratletakse makroga BPF_CALL_3. Maksimaalne argumentide arv on viis.) Kuid see on ainult mÀÀratlemise esmane osa. Teine osa seisneb tĂŒĂŒbi mÀÀratlemises struct bpf_func_proto, mis sisaldab abifunktsiooni kirjeldust, mis on verifikaatorile arusaadav:
const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
.func = bpf_get_smp_processor_id,
.gpl_only = false,
.ret_type = RET_INTEGER,
};Abifunktsioonide registreerimine
Selleks, et teatud tĂŒĂŒpi BPF-i programmid saaksid seda funktsiooni kasutada, peavad nad selle registreerima, nĂ€iteks tĂŒĂŒbi jaoks BPF_PROG_TYPE_XDP kernelis mÀÀratletakse funktsioon xdp_func_proto, mis funktsiooni ID kaudu mÀÀrab, kas XDP toetab seda funktsiooni vĂ”i mitte. Meie funktsiooni :
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;
...
}
}Uued BPF-i programmide tĂŒĂŒbid 'mÀÀratakse' failis makro abil BPF_PROG_TYPE. 'MÀÀratlemine' on jutumĂ€rkides, kuna see on ratsionaalne mÀÀratlemine, kuid C keele kontekstis mÀÀratakse konkreetsete struktuuride kogum teistes kohtades. Eriti failis kernel/bpf/verifier.c kĂ”ik mÀÀratlused failist bpf_types.h kasutatakse struktuuride massiivi loomiseks 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
#undef BPF_PROG_TYPE
};See, for each type of BPF program, a pointer to a data type structure is defined struct bpf_verifier_ops, which is initialized with the value _name ## _verifier_ops, i.e., xdp_verifier_ops jaoks xdp. The structure xdp_verifier_ops failis net/core/filter.c jÀrgmiselt:
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,
};Here we see our familiar function xdp_func_proto, which will be invoked by the verifier every time it encounters a call of some function inside the BPF program, see .
Let's look at how a hypothetical BPF program uses the function bpf_get_smp_processor_id. To do this, we will rewrite the program from our previous section as follows:
#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";Symbol bpf_get_smp_processor_id ja <bpf/bpf_helper_defs.h> teeki libbpf kuidas
static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;that is, bpf_get_smp_processor_id â this is a pointer to a function, the value of which is 8, where 8 is the value BPF_FUNC_get_smp_processor_id tĂŒĂŒpi enum bpf_fun_id, which is defined for us in the file vmlinux.h (the file bpf_helper_defs.h is generated by a script in the kernel, so âmagicâ numbers are ok). This function takes no arguments and returns a value of type __u32. When we run it in our program, clang it generates an instruction BPF_CALL of the âcorrect formâ. Let's compile the program and look at the section 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 :
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
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
6: b7 00 00 00 01 00 00 00 r0 = 1
0000000000000038 :
7: 95 00 00 00 00 00 00 00 exitIn the very first line, we see the instruction call, parameeter IMM which equals 8, and SRC_REG â zero. According to the ABI convention used by the verifier, this is indeed a call to the helper function number eight. After it is executed, the logic is straightforward. The returned value from the register r0 is copied into r1 and on lines 2,3 converted to the type u32 â the upper 32 bits are zeroed. In lines 4,5,6,7 we return 2 (XDP_PASS) or 1 (XDP_DROP) depending on whether the helper function from line 0 returned zero or non-zero.
Let's check ourselves: load the program and look at the output 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
4: (b7) r0 = 2
; }
5: (15) if r1 == 0x0 goto pc+1
6: (b7) r0 = 1
7: (95) exitHÀsti, verifikaator leidis Ôige kernel-helperi.
NÀide: edastame argumendid ja lÔpuks kÀivitame programmi!
KĂ”ik jooksutamise abifunktsioonid omavad prototĂŒĂŒpe
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)Abifunktsioonide parameetrid edastatakse registrites r1âr5, ja tagastatav vÀÀrtus on registris r0. Funktsioone, mis vĂ”tavad rohkem kui viis argumenti, ei ole ja nende toetamine ei ole tulevikus plaanis.
Vaadakem uut kernel helperit ja seda, kuidas BPF edastab parameetreid. Kirjutame ĂŒmber xdp-simple.bpf.c jĂ€rgmiste sĂ”nadega (muud read ei muutunud):
SEC("xdp/simple")
int simple(void *ctx)
{
bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
return XDP_PASS;
}Meie programm prindib CPU numbri, millel see töötab. Kompileerime selle ja vaatame koodi:
$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o
0000000000000000 :
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: exitRidade 0-7 salvestame kuhja rea running on CPU%un, ja siis real 8 kĂ€ivitame tuttava bpf_get_smp_processor_id. Ridades 9-12 valmistame ette abifunktsiooni bpf_printk â registrid r1, r2, r3. Miks neid on kolm, mitte kaks? Sest bpf_printk â tĂ”elist abifunktsiooni bpf_trace_printk, millele tuleb edastada formaadi stringi suurus.
NĂŒĂŒd lisame paar rida xdp-simple.c, et meie programm ĂŒhenduks liidesega lo ja töötaks tĂ”eliselt!
$ cat xdp-simple.c
#include
#include
#include
#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);
}Siin kasutame funktsiooni bpf_set_link_xdp_fd, mis ĂŒhendab BPF XDP tĂŒĂŒpi programmid vĂ”rguliidestega. Oleme kĂ”va koodiga mÀÀranud liidese numbri lo, mis on alati 1. KĂ€ivitame funktsiooni kaks korda, et esmalt katkestada vana programm, kui see on ĂŒhendatud. Pange tĂ€hele, et nĂŒĂŒd ei ole meil enam vaja kutsuda pause vĂ”i lĂ”pmatust tsĂŒklit: meie laadimisprogramm lĂ”petab töö, kuid BPF-programm ei hĂ€vine, kuna see on ĂŒhendatud sĂŒndmuste allikaga. PĂ€rast eduka laadimise ja ĂŒhendamise korral kĂ€ivitatakse programm iga vĂ”rkpaketi jaoks, mis jĂ”uab lo.
Laadime programmi ja vaatame liidest lo:
$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp name simple tag 4fca62e77ccb43d6 gpl
$ ip l show dev lo
1: lo: 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 669Laaditud programm on ID-ga 669 ja sama ID on nÀha liideses lo. Saatame paar paketti aadressile 127.0.0.1 (pÀring + vastus):
$ ping -c1 localhostja nĂŒĂŒd vaatame silumine virtuaalsesse faili /sys/kernel/debug/tracing/trace_pipe, kuhu bpf_printk kirjutab oma sĂ”numid:
# 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 CPU0Kaks paketti tuvastati lo ja töödeldi CPU0-l â meie esimene tĂ€ielik mĂ”tetu BPF-programm töötas!
Tuleb mÀrkida, et bpf_printk ei kirjuta asjata silumise faili: see ei ole parim abivahend tootmiskeskkonnas kasutamiseks, kuid meie eesmÀrk oli nÀidata midagi lihtsat.
JuurdepÀÀs BPF-programmidest maps
NĂ€ide: kasutame mappi BPF-programmist
Eelmistes lĂ”ikudes Ă”ppisime looma ja kasutama mappe kasutaja ruumis, nĂŒĂŒd vaatame tuuma osa. Alustame, nagu tavaliselt, nĂ€itest. Kirjutame oma programmi ĂŒmber xdp-simple.bpf.c jĂ€rgmiselt:
#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";Programmi alguses lisasime mapi defineerimise woo: see on 8 elemendi massiiv, kuhu salvestatakse andmed, mille tĂŒĂŒp on u64 (C-s defineeriksime sellise massiivi kui u64 woo[8]). Programmis "xdp/simple" saame kĂ€imasoleva protsessori numbri muutuja key ja seejĂ€rel helper-funktsiooni abil bpf_map_lookup_element saame vastava kirje massiivist ja suurendame seda ĂŒhe vĂ”rra. Venekeelsena: loeme statistikat selle kohta, millisel CPU-l on töödeldud sissetulevad paketid. Proovime programmi kĂ€ivitada:
$ 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-simpleKontrollime, et see on ĂŒhendatud lo ja saadame natuke pakette:
$ ip l show dev lo
1: lo: 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; doneNĂŒĂŒd vaatame massiivi sisu:
$ 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 }
]Peaaegu kĂ”ik protsessid töötati CPU7 peal. Meie jaoks pole see oluline, peamine on, et programm töötab ja me mĂ”istsime, kuidas pÀÀseda BPF-i kaartidele â kasutades .
MĂŒstiline nĂ€itaja
Nii saame me BPF-programmist kaardile juurdepÀÀsu, kasutades kÔnesid nagu
val = bpf_map_lookup_elem(&woo, &key);kus abifunktsioon nÀeb vÀlja nagu
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)aga me edastame nĂ€itaja &woo nimetule struktuurile struct { ... }âŠ
Kui vaatame programmi assemblerit, siis nÀeme, et vÀÀrtus &woo ei ole tegelikult mÀÀratletud (rida 4):
llvm-objdump -D --section xdp/simple xdp-simple.bpf.o
xdp-simple.bpf.o: faili formaat elf64-bpf
XRP lihtsate sektsioonide vÀljavÔte:
0000000000000000 :
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
...ja sisaldub relegatsioonides:
$ 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 wooAga kui vaatame juba laaditud programmi, siis nÀeme viit Ôigesse kaardile (rida 4):
$ 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]
...Nii saame jĂ€reldada, et meie ĂŒhenduslikus programmi kĂ€ivitamise hetkel viidatakse &woo oli asendatud midagi raamatukoguga libbpf. Esmalt vaatame vĂ€ljundit 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) = 5Me nÀeme, et libbpf loomine kaardina woo ja seejÀrel laadis meie programmi simple. Vaatame lÀhemalt, kuidas me programmi laadime:
- kutsume
xdp_simple_bpf__open_and_loadfailistxdp-simple.skel.h - mis kutsub esile
xdp_simple_bpf__loadfailistxdp-simple.skel.h - mis kutsub esile
bpf_object__load_skeletonfailistlibbpf/src/libbpf.c - mis kutsub esile
bpf_object__load_xattrAPI-slibbpf/src/libbpf.c
Viimane funktsioon kutsub muuhulgas esile bpf_object__create_maps, mis loob vĂ”i avab olemasolevaid kaarte, muutes need failikirjeldajateks. (Siin me nĂ€eme BPF_MAP_CREATE vĂ€ljastuses strace.) Edasi kutsutakse funktsioon bpf_object__relocate , ja just see meid huvitab, kuna me mĂ€letame, et nĂ€gime woo ĂŒlejÀÀnud tabelis. Seda uurides jĂ”uame lĂ”puks funktsiooni bpf_program__relocate, mis :
case RELO_LD64:
insn[0].src_reg = BPF_PSEUDO_MAP_FD;
insn[0].imm = obj->maps[relo->map_idx].fd;
break;Nii et me vÔtame meie kÀsu
18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 llja asendame selles allika registri BPF_PSEUDO_MAP_FD, ja esimese IMM failikirjeldajaga meie kaardist. Ja kui see on nÀiteks 0xdeadbeef, siis tulemuseks saame kÀsu
18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 llNii edastatakse teave kaartide kohta konkreetsele laaditud BPF programmile. Samal ajal vÔib kaart olla loodud BPF_MAP_CREATE, vÔi avatud ID-ga, kasutades BPF_MAP_GET_FD_BY_ID.
Seega, kasutades libbpf on algoritm jÀrgmine:
- kompileerimise ajal luuakse viidete jaoks kaartidele relokatsiooni tabelis kirjed
libbpfava ELF objekt, leidke kÔik kasutatud kaardid ja looge neile failikirjeldajad- failikirjeldajad laetakse tuuma osana kÀskudest
LD64
Nagu aru saate, see pole veel kĂ”ik ja meil tuleb vaadata tuuma. Ănneks on meil vihje â oleme mÀÀranud vÀÀrtuse BPF_PSEUDO_MAP_FD allika registrisse ja saame seda otsida, mis viib meid kĂ”igi pĂŒhamate pĂŒhade â kernel/bpf/verifier.c, kus funktsioon, millel on iseloomulik nimi, asendab failikirjeldaja struktuuri aadressiga 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;(tÀielik kood on saadaval ). Nii et saame tÀiendada meie algoritmi:
- programmi laadimise ajal kontrollib verifier kaardi Ôiget kasutamist ja mÀÀrab vastava struktuuri aadressi
struct bpf_map
Eesti ELF binaari laadimisel libbpf juhtuda veel palju sĂŒndmusi, kuid me arutame seda teiste artiklite raames.
Laadime programme ja kaarte ilma libbpf-ita
Nagu lubatud, siin on nÀide lugejatele, kes soovivad teada, kuidas luua ja laadida programmi, mis kasutab kaarte, ilma abita. libbpf. See vÔib olla kasulik, kui töötate keskkonnas, kus te ei saa sÔltuvusi kokku koguda, vÔi sÀÀstate iga bitti vÔi kirjutate sarnast programmi , mis genereerib BPF binaarkoodi lennult.
Kergemaks jĂ€rgimiseks kirjutame meie nĂ€ite ĂŒmber xdp-simple. TĂ€ieliku ja veidi laiendatud programmi koodi, mida selles nĂ€ites kĂ€sitleme, leiate siit .
Meie rakenduse loogika on jÀrgmine:
- luua kaarditĂŒĂŒp
BPF_MAP_TYPE_ARRAYkÀskluse abilBPF_MAP_CREATE, - luua programm, mis kasutab seda kaarti,
- ĂŒhendada programm liidesega
lo,
mis tÔlgib inimkeelde kui
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);
}Siin map_create loob kaardi tĂ€pselt nii, nagu me seda tegime esimeses nĂ€ites sĂŒsteemikĂ”nest bpf â "tuum, palun tee mulle uus kaart massiivi kujul, kus on 8 elementi tĂŒĂŒpi __u64 ja naita mulle failide deskriptor":
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));
}Programm laaditakse samuti lihtsalt:
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));
}Raske osa prog_load â see on meie BPF programmi mÀÀratlemine struktuuride massiivina struct bpf_insn insns[]. Kuid kuna me kasutame programmi, mis meil on C-s, saame natuke pettuse kasutada:
$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o
0000000000000000 :
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
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 :
13: bf 10 00 00 00 00 00 00 r0 = r1
14: 95 00 00 00 00 00 00 00 exitKokkuvĂ”ttes peame kirjutama 14 kĂ€sku struktuuride tĂŒĂŒpidena struct bpf_insn (nĂ”uanne: vĂ”tke ĂŒlevaade, lugege uuesti juhendit, avage ja ja proovige tuvastada struct bpf_insn insns[] iseselt):
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
},
};Harjutus neile, kes seda ise ei kirjutanud â leidke map_fd.
Meie programmis on veel ĂŒks avaldamata osa â xdp_attach. Kahjuks ei saa XDP tĂŒĂŒpi programme sĂŒsteemi kutse kaudu ĂŒhenduda bpf. Inimesed, kes lĂ”id BPF-i ja XDP-d, olid Linuxi vĂ”rgu kogukonnast, seega kasutasid nad kĂ”ige tuttavamat neile (aga mitte tavalistele inimestele) suhtlemisliidest tuumaga: , vt ka . Lihtsaim viis rakendamiseks xdp_attach â on koodi kopeerimine libbpf, nimelt failist , mida me just tegime, seda veidi lĂŒhendades:
Tere tulemast netlink socketite maailma
Avame netlink socketi tĂŒĂŒbi 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;
}Loeme sellisest soketist:
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 nlmsg_pid != nl_pid)
errx(1, "vale 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); liiga palju koodi, mida kopeerida...
goto done;
case NLMSG_DONE:
return 0;
default:
break;
}
}
}
ret = 0;
done:
return ret;
}LÔpuks, siin on meie funktsioon, mis avab soketi ja saadab sellesse spetsiaalse sÔnumi, mis sisaldab failihalduri descriptorit:
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 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;
}Nii, kÔik on testimiseks valmis:
$ 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 +++Vaadakem, kas meie programm on ĂŒhendatud lo:
$ ip l show dev lo
1: lo: 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 160Saadame ping-sÔnumeid ja vaatame kaarti:
$ 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
Leidsime 8 elementiHĂ€sti, kĂ”ik töötab. MĂ€rkige, et meie kaart kuvatakse jĂ€lle baitidena. See tuleneb sellest, et erinevalt libbpf ei ole me tĂŒĂŒbinfot (BTF) laadinud. Kuid sellest rÀÀgime jĂ€rgmine kord.
Arendustooted
Selles osas vaatame pÔhitehniliste tööriistade komplekti BPF arendajatele.
Ăldiselt ei ole BPF programmide arendamiseks midagi erilist vaja â BPF töötab igas korralikus jaotuse tĂ”ukes ja programmid koostatakse clang, mille saab paketi kaudu installida. Ent kuna BPF on arenduses, muutuvad tuum ja tööriistad pidevalt. Kui te ei soovi kirjutada BPF programme eelmise sajandi meetoditega, peate selle koguma.
llvm/clangpahole- oma tuum
bpftool
(Teabe jaoks: see ja kÔik nÀidised artiklis jooksutati Debian 10-l.)
llvm/clang
BPF töötab koos LLVM-iga ja kuigi hiljutistel aegadel saab BPF programme kompileerida ka gcc abil, toimub kogu praegune arendus LLVM-i jaoks. Seega kogume kÔigepealt praeguse versiooni clang git-ist:
$ 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
... palju aega hiljem
$NĂŒĂŒd saame kontrollida, kas kĂ”ik on Ă”igesti kokku pandud:
$ ./bin/llc --version
LLVM (http://llvm.org/):
LLVM versioon 11.0.0git
Optimeeritud versioon.
Vaikimisi sihtmÀrk: x86_64-unknown-linux-gnu
Host CPU: znver1
Registreeritud sihtmÀrkide
bpf - BPF (host endian)
bpfeb - BPF (big endian)
bpfel - BPF (little endian)
x86 - 32-bit X86: Pentium-Pro ja kÔrgem
x86-64 - 64-bit X86: EM64T ja AMD64(Kogumise juhised clang on mulle saadud .)
Me ei paigalda alles kogutud programme, vaid lisame need lihtsalt PATH, nÀiteks:
export PATH="`pwd`/bin:$PATH"(Selle saab lisada .bashrc vÔi eraldi faili. Isiklikult lisan sellised asjad ~/.bin/activate-llvm.sh ja kui on vaja, teen . activate-llvm.sh.)
Pahole ja BTF
Utiliit pahole kasutatakse tuuma kogumise ajal BTF formaadis silumisinfote loobmiseks. Me ei kĂ€sitle selle artikli raames BTF tehnoloogia ĂŒksikasju, vĂ€lja arvatud see, et see on mugav ja me tahame seda kasutada. Seega, kui kavatsete koguda oma tuuma, koguge esmalt pahole (ilma pahole te ei saa tuuma koguda, kui valik 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/paholeTuuma BPF-iga eksperimenteerimiseks
BPF vĂ”imaluste uurimisel tasub kokku panna oma kerneli. Ăldiselt ei ole see kohustuslik, kuna saate kokku panna ja laadida BPF programme ka jaotuse kernelil, kuid oma kernel vĂ”imaldab kasutada kĂ”ige uuemaid BPF vĂ”imalusi, mis jĂ”uavad teie jaotusse parimal juhul alles pĂ€rast mitut kuud vĂ”i, nagu mĂ”nedate silumisriistade puhul, ei panda neid ĂŒldse lĂ€hitulevikus kokku. Samuti vĂ”imaldab oma kernel tunda end olulisena, katsetades koodi.
Kernelâi ehitamiseks vajate kĂ”igepealt ise kernelit ja teiseks kernelikonfiguratsiooni faili. BPF eksperimentide jaoks saame kasutada tavapĂ€rast kernelit vĂ”i ĂŒht arenduskernelit. Ajalooliselt toimub BPF arendamine Linuxi vĂ”rgukogukonna raames ja seega lĂ€hevad kĂ”ik muudatused varem vĂ”i hiljem lĂ€bi David Milleri (David Miller) â Linuxi vĂ”rguosa hooldaja. sĂ”ltuvalt oma iseloomust â parandused vĂ”i uued funktsioonid â jĂ”uavad vĂ”rgu muudatused ĂŒhte kahest kernelist â vĂ”i . BPF muudatused jaotatakse samuti ja , mis hiljem jĂ”uavad vastavalt net-i ja net-next-i. Lisainfot vt ja . Valige seega kernel oma maitse ja sĂŒsteemi stabiilsuse vajaduste pĂ”hjal, millel testite (*-next kerneli on kĂ”ige ebastabiilsemad kĂ”ikidest loetletud).
KĂ€esoleva artikli raames ei ole juttu kernelikonfiguratsiooni failide haldamisest â eeldatakse, et oskate seda juba teha vĂ”i Siiski, jĂ€rgmised juhised peaksid olema enam-vĂ€hem piisavad, et teil oleks toimiv sĂŒsteem BPF toe saavutamiseks.
Laadige alla ĂŒks ĂŒlalmainitud kernelitest:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-nextKĂŒpsetage minimaalne töötav kernelikonfiguratsioon:
$ cp /boot/config-`uname -r` .config
$ make localmodconfigAktiveerige BPF valikud failis .config oma valiku jÀrgi (tÔenÀoliselt on CONFIG_BPF juba aktiveeritud, kuna seda kasutab systemd). Siin on valikuvÔimaluste loetelu kernelist, mida selle artikli jaoks kasutati:
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=yJÀrgmiseks saame kergesti koguda ja installida mooduleid ja kerneli (muide, kerneli saab koguda just vÀrskelt koostatud) clang, lisades CC=clang):
$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make installja taaskÀivitades uue kerneliga (kasutan selleks kexec paketist kexec-tools):
v=5.8.0-rc6+ # kui uuendate olemasolevat kernelit, siis saate teha v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -ebpftool
Artiklis kĂ”ige sagedamini kasutatav utiliit on utiliit bpftool, mis on tarnitud Linuxi kerneli koosseisus. Selle on kirjutanud ja hooldanud BPF arendajad BPF arendajatele ning selle abil saab hallata kĂ”iki BPF objekte â laadida programme, luua ja muuta kaarte, uurida BPF ökosĂŒsteemi elu jne. Dokumentatsiooni man pages'i allikaid saab leida vĂ”i juba kompileerituna, .
Artikli kirjutamise hetkeks bpftool tarnitakse valmis kujul ainult RHELile, Federale ja Ubuntule (vt nĂ€iteks, , kus rÀÀgitakse lĂ”petamata loost pakendamisest bpftool Debianis). Kuid kui olete juba oma kernelit kogunud, siis on selle kogumine imelihtne: bpftool $ cd ${linux}/tools/bpf/bpftool # ... kirjuta viimase clang'i teed nagu eespool kirjeldatud $ make -sSĂŒsteemi omaduste automaatne tuvastamine: ... libbfd: [ on ] ... disassembler-four-args: [ on ] ... zlib: [ on ] ... libcap: [ on ] ... clang-bpf-co-re: [ on ]SĂŒsteemi omaduste automaatne tuvastamine: ... libelf: [ on ] ... zlib: [ on ] ... bpf: [ on ]$
(siin${linux} â see on teie kerneli kataloog.) PĂ€rast nende kĂ€skude tĂ€itmist kolitakse katalooge bpftool ${linux}/tools/bpf/bpftool ja seda saab kirja panna teele (esimesele kasutajale ) vĂ”i lihtsalt kopeerida rootKogumine /usr/local/sbin.
on kĂ”ige parem lĂ€bi viia viimasel bpftool , nagu eespool kirjeldatud, ning kontrollida, kas see on Ă”igesti koostatud â nĂ€iteks jĂ€rgmise kĂ€su abil clang$ sudo bpftool feature probe kernel SĂŒsteemi konfiguratsiooni skaneerimine... bpf() syscall mittenĂ”utud kasutajatele on lubatud JIT kompilaator on lubatud JIT kompilaatorite kĂ”vade seadistuste muutmine on keelatud JIT kompilaatori kallasymlsi ekspordid on juurte jaoks lubatud ...
mis nÀitab, millised BPF omadused on teil kernelis lubatud.Muide, eelmist kÀsku saab kÀivitada nagu
See on sarnane utiliitide tööriistade pakettidest
# bpftool f p kSee on tehtud sarnaselt paketi utiliitidele. iproute2, kus me saame nÀiteks öelda ip a s eth0 asemel ip addr show dev eth0.
KokkuvÔte
BPF vĂ”imaldab tĂ”husalt hiire suurust mÔÔta ja sĂŒsteemi tuuma funktsionaalsust reaalajas muuta. SĂŒsteem on osutunud vĂ€ga edukaks, jĂ€rgides parimaid UNIX-i traditsioone: lihtne mehhanism, mis vĂ”imaldab tuuma (uuesti)programmeerida, on vĂ”imaldanud tohutul hulgal inimesi ja organisatsioone eksperimenteerida. Ja kuigi eksperimentide, nagu ka BPF infrastruktuuri arendamise, lĂ”pp on veel kaugel, on sĂŒsteemil juba stabiilne ABI, mis vĂ”imaldab luua usaldusvÀÀrset ja mis kĂ”ige tĂ€htsam, efektiivset Ă€riloogikat.
Tahaksin mĂ€rkida, et minu arvates on tehnoloogia saamas nii populaarseks, kuna selle kaudu saab mĂ€ngida (masina arhitektuuri vĂ”ib enam-vĂ€hem mĂ”ista ĂŒhe Ă”htu jooksul), ning samas lahendada ĂŒlesandeid, mis enne selle ilmumist ei olnud (ilusasti) lahendatavad. Need kaks komponenti koos panevad inimesi eksperimenteerima ja unistama, mis viib uute ja uute uuenduste tekkimiseni.
See artikkel, kuigi ei osutunud eriti lĂŒhikeseks, on vaid sissejuhatus BPF maailma ning ei kĂ€sitle âedasiarendatudâ vĂ”imalusi ja olulisi arhitektuuri osi. Edasi-plaan on ligikaudu jĂ€rgmine: jĂ€rgmine artikkel kĂ€sitleb BPF programmide tĂŒĂŒpide ĂŒlevaadet (kernel 5.8 toetab 30 programmi tĂŒĂŒpi), seejĂ€rel vaatame lĂ”puks, kuidas kirjutada tĂ”elisi rakendusi BPF, kasutades tuuma jĂ€lgimise programme, seejĂ€rel saab aeg sĂŒvitsi minekuks BPF arhitektuuri juurde ja seejĂ€rel tulevad nĂ€ited vĂ”rgurakendustest ja BPF turbarakendustest.
Selles tsĂŒklis eelnevad artiklid
Lingid
â BPF dokumentatsioon ciliumilt, tĂ€psemalt Daniel Borkmanilt, ĂŒhelt BPF looja ja hooldaja poolt. See on ĂŒks esimesi tĂ”siseid kirjeldusi, mis eristub ĂŒlejÀÀnutest, kuna Daniel teab tĂ€pselt, millest ta kirjutab ja vigu seal ei leidu. EelkĂ”ige kĂ€sitletakse selles dokumendis, kuidas töötada BPF programmidega, mille tĂŒĂŒbid on XDP ja TC tuntud utiliidi abil
ippaketistiproute2.â originaaldokument klassikalise, ja seejĂ€rel laiendatud BPF kohta. Kasulik lugeda, kui soovite sĂŒveneda assemblerisse ja arhitektuuri tehnilistesse detailidesse.
. Uuendatakse harva, kuid tĂ€pselt, kuna kirjutavad Alexei Starovoitov (eBPF autor) ja Andrii Nakryiko â (hooldaja
libbpf).. Huvitav Twitteri teema Quentin Monnetilt nÀidete ja bpftooli kasutamise saladustega.
. Ăks suurimaid (ja jĂ€tkuvalt toetatavaid) linkide loetelusid BPF-i dokumentatsioonile Quentin Monnetilt.
Allikas: habr.com
