U početku je postojala tehnologija i zvala se BPF. Pogledali smo je prijašnji, starozavjetni članak iz ove serije. Godine 2013., zahvaljujući naporima Alexei Starovoitov i Daniel Borkman, njegova poboljšana verzija, optimizirana za moderne 64-bitne strojeve, razvijena je i uključena u Linux kernel. Ova nova tehnologija je kratko nazvana Internal BPF, zatim je preimenovana u Extended BPF, a sada, nakon nekoliko godina, svi je jednostavno zovu BPF.
Grubo govoreći, BPF vam omogućuje pokretanje proizvoljnog korisničkog koda u Linux kernel prostoru, a nova se arhitektura pokazala toliko uspješnom da će nam trebati još desetak članaka da opišemo sve njezine primjene. (Jedina stvar koju programeri nisu dobro napravili, kao što možete vidjeti u kodu izvedbe ispod, je stvaranje pristojnog logotipa.)
Ovaj članak opisuje strukturu BPF virtualnog stroja, kernel sučelja za rad s BPF-om, razvojne alate, kao i kratak, vrlo kratak pregled postojećih mogućnosti, tj. sve što će nam u budućnosti trebati za dublje proučavanje praktičnih primjena BPF-a.
Sažetak članka
Uvod u BPF arhitekturu. Prvo ćemo baciti pogled na BPF arhitekturu iz ptičje perspektive i prikazati glavne komponente.
Upravljanje objektima pomoću bpf sistemskog poziva. Uz određeno razumijevanje sustava koji već postoji, konačno ćemo pogledati kako kreirati i manipulirati objektima iz korisničkog prostora pomoću posebnog sistemskog poziva − bpf(2).
Пишем программы BPF с помощью libbpf. Naravno, možete pisati programe koristeći sistemski poziv. Ali teško je. Za realističniji scenarij nuklearni programeri razvili su biblioteku libbpf. Napravit ćemo osnovni kostur BPF aplikacije koji ćemo koristiti u sljedećim primjerima.
Pomoćnici kernela. Ovdje ćemo naučiti kako BPF programi mogu pristupiti kernel helper funkcijama – alatu koji uz mape iz temelja proširuje mogućnosti novog BPF-a u odnosu na klasični.
Pristup kartama iz BPF programa. Do ove točke znat ćemo dovoljno da shvatimo kako točno možemo stvoriti programe koji koriste karte. I čak zavirimo na brzinu u veliki i moćni verifikator.
Razvojni alati. Odjeljak pomoći o tome kako sastaviti potrebne pomoćne programe i kernel za eksperimente.
Zaključak. Na kraju članka, oni koji su dovde pročitali naći će poticajne riječi i kratak opis onoga što će se dogoditi u narednim člancima. Također ćemo navesti niz poveznica za samostalno učenje za one koji nemaju želju ili mogućnost čekati nastavak.
Uvod u BPF arhitekturu
Prije nego počnemo razmatrati BPF arhitekturu, posljednji put ćemo se osvrnuti na (oh). klasični BPF, koji je razvijen kao odgovor na pojavu RISC strojeva i riješio je problem učinkovitog filtriranja paketa. Arhitektura se pokazala toliko uspješnom da je, rođena u burnim devedesetima u Berkeley UNIX-u, portirana na većinu postojećih operativnih sustava, preživjela lude dvadesete i još uvijek nalazi nove primjene.
Novi BPF razvijen je kao odgovor na sveprisutnost 64-bitnih strojeva, usluga u oblaku i povećanu potrebu za alatima za stvaranje SDN-a (Ssoftver-dusavršen numrežavanje). Razvijen od strane mrežnih inženjera kernela kao poboljšanu zamjenu za klasični BPF, novi BPF je doslovno šest mjeseci kasnije pronašao primjenu u teškom zadatku praćenja Linux sustava, a sada, šest godina nakon njegove pojave, trebat će nam cijeli sljedeći članak samo da navesti različite vrste programa.
Smiješne slike
U svojoj jezgri, BPF je virtualni stroj sandbox koji vam omogućuje pokretanje "proizvoljnog" koda u prostoru kernela bez ugrožavanja sigurnosti. BPF programi se stvaraju u korisničkom prostoru, učitavaju u kernel i povezuju s nekim izvorom događaja. Događaj može biti, na primjer, isporuka paketa mrežnom sučelju, pokretanje neke kernel funkcije itd. U slučaju paketa, BPF program će imati pristup podacima i metapodacima paketa (za čitanje i, eventualno, pisanje, ovisno o vrsti programa); u slučaju pokretanja kernel funkcije, argumenti funkcija, uključujući pokazivače na memoriju jezgre itd.
Pogledajmo pobliže ovaj proces. Za početak, razgovarajmo o prvoj razlici od klasičnog BPF-a, programi za koje su napisani u asembleru. U novoj verziji, arhitektura je proširena tako da se programi mogu pisati na jezicima visoke razine, prvenstveno, naravno, u C. Za to je razvijen backend za llvm, koji vam omogućuje generiranje bajt koda za BPF arhitekturu.
Arhitektura BPF-a dizajnirana je djelomično za učinkovit rad na modernim strojevima. Kako bi ovo funkcioniralo u praksi, BPF bajt kod, nakon što se učita u kernel, prevodi se u izvorni kod pomoću komponente koja se zove JIT kompajler (Jgornji In Time). Dalje, ako se sjećate, u klasičnom BPF-u program je učitavan u kernel i pripojen izvoru događaja atomski - u kontekstu jednog poziva sustava. U novoj arhitekturi to se događa u dvije faze - prvo se kod učitava u kernel pomoću sistemskog poziva bpf(2)a zatim, kasnije, putem drugih mehanizama koji variraju ovisno o vrsti programa, program se pripaja izvoru događaja.
Ovdje čitatelj može imati pitanje: je li to bilo moguće? Kako je zajamčena sigurnost izvršenja takvog koda? Sigurnost izvršenja jamči nam faza učitavanja BPF programa koja se zove verifier (na engleskom se ova faza zove verifier i ja ću nastaviti koristiti englesku riječ):
Verifier je statički analizator koji osigurava da program ne ometa normalan rad kernela. To, usput, ne znači da program ne može ometati rad sustava - BPF programi, ovisno o vrsti, mogu čitati i prepisivati dijelove memorije kernela, vraćati vrijednosti funkcija, rezati, dodavati, prepisivati pa čak i prosljeđivanje mrežnih paketa. Verifier jamči da izvođenje BPF programa neće srušiti kernel i da program koji, prema pravilima, ima pristup za pisanje, na primjer, podataka odlaznog paketa, neće moći prebrisati memoriju kernela izvan paketa. Verifikator ćemo pogledati malo detaljnije u odgovarajućem odjeljku, nakon što se upoznamo sa svim ostalim komponentama BPF-a.
Pa što smo do sada naučili? Korisnik piše program u C-u, učitava ga u kernel pomoću sistemskog poziva bpf(2), gdje ga provjerava verifikator i prevodi u izvorni bajt kod. Tada isti ili drugi korisnik povezuje program s izvorom događaja i on se počinje izvršavati. Odvajanje pokretanja i povezivanja potrebno je iz nekoliko razloga. Prvo, pokretanje verifikatora je relativno skupo i skidanjem istog programa nekoliko puta gubimo računalo. Drugo, točno kako je program povezan ovisi o njegovoj vrsti, a jedno "univerzalno" sučelje razvijeno prije godinu dana možda neće biti prikladno za nove vrste programa. (Iako sada kada arhitektura postaje zrelija, postoji ideja da se ovo sučelje unificira na razini libbpf.)
Pažljivi čitatelj može primijetiti da još nismo završili sa slikama. Dapače, sve navedeno ne objašnjava zašto BPF bitno mijenja sliku u odnosu na klasični BPF. Dvije inovacije koje značajno proširuju opseg primjenjivosti su mogućnost korištenja zajedničke memorije i kernel pomoćne funkcije. U BPF-u, zajednička memorija implementirana je pomoću takozvanih mapa - zajedničkih struktura podataka sa specifičnim API-jem. Vjerojatno su dobili ovo ime jer je prva vrsta karte koja se pojavila bila hash tablica. Zatim su se pojavili nizovi, lokalne (po CPU) hash tablice i lokalni nizovi, stabla pretraživanja, karte koje sadrže pokazivače na BPF programe i još mnogo toga. Ono što nam je sada zanimljivo je da BPF programi sada imaju mogućnost zadržavanja stanja između poziva i dijeljenja s drugim programima i korisničkim prostorom.
Kartama se pristupa iz korisničkih procesa pomoću sistemskog poziva bpf(2), i iz BPF programa koji se izvode u kernelu koristeći pomoćne funkcije. Štoviše, pomoćnici ne postoje samo za rad s kartama, već i za pristup drugim mogućnostima jezgre. Na primjer, BPF programi mogu koristiti pomoćne funkcije za prosljeđivanje paketa drugim sučeljima, generiranje perf događaja, pristup strukturama kernela i tako dalje.
Ukratko, BPF pruža mogućnost učitavanja proizvoljnog, tj. testiranog verifikatorom korisničkog koda u prostor kernela. Ovaj kod može spremati stanje između poziva i razmjenjivati podatke s korisničkim prostorom, a također ima pristup podsustavima jezgre koje dopušta ova vrsta programa.
Ovo je već slično mogućnostima koje pružaju moduli jezgre, u usporedbi s kojima BPF ima neke prednosti (naravno, možete usporediti samo slične aplikacije, na primjer, praćenje sustava - ne možete pisati proizvoljni upravljački program s BPF-om). Možete primijetiti niži ulazni prag (neki uslužni programi koji koriste BPF ne zahtijevaju od korisnika vještine programiranja kernela ili općenito vještine programiranja), sigurnost tijekom rada (podignite ruku u komentarima za one koji nisu pokvarili sustav prilikom pisanja ili testiranje modula), atomičnost - dolazi do zastoja prilikom ponovnog učitavanja modula, a BPF podsustav osigurava da nijedan događaj nije propušten (pravo rečeno, to ne vrijedi za sve vrste BPF programa).
Prisutnost takvih mogućnosti čini BPF univerzalnim alatom za proširenje kernela, što je potvrđeno u praksi: sve više i više novih vrsta programa dodaje se u BPF, sve više i više velikih tvrtki koristi BPF na borbenim poslužiteljima 24×7, sve više i više startupi grade svoje poslovanje na rješenjima na temelju kojih se temelje BPF. BPF se koristi posvuda: u zaštiti od DDoS napada, stvaranju SDN-a (na primjer, implementacija mreža za kubernetes), kao glavni alat za praćenje sustava i sakupljač statistike, u sustavima za otkrivanje upada i sandbox sustavima itd.
Završimo pregledni dio članka ovdje i pogledajmo virtualni stroj i BPF ekosustav detaljnije.
Digresija: komunalije
Da biste mogli pokrenuti primjere u sljedećim odjeljcima, možda će vam trebati nekoliko uslužnih programa, barem llvm/clang uz podršku bpf-a i bpftool, U odjeljku Razvojni alati Možete pročitati upute za sastavljanje uslužnih programa, kao i svoj kernel. Ovaj odjeljak je postavljen ispod kako ne bi narušio sklad naše prezentacije.
Registri virtualnog stroja BPF i sustav instrukcija
Arhitektura i sustav naredbi BPF-a razvijeni su uzimajući u obzir činjenicu da će programi biti napisani u jeziku C i nakon učitavanja u kernel prevedeni u izvorni kod. Stoga su broj registara i skup naredbi odabrani imajući u vidu presjek, u matematičkom smislu, mogućnosti modernih strojeva. Osim toga, programima su nametnuta razna ograničenja, primjerice donedavno nije bilo moguće pisati petlje i potprograme, a broj instrukcija bio je ograničen na 4096 (sada privilegirani programi mogu učitati do milijun instrukcija).
BPF ima jedanaest korisnički dostupnih 64-bitnih registara r0-r10 i brojač programa. Registar r10 sadrži pokazivač okvira i samo je za čitanje. Programi imaju pristup 512-bajtnom stogu tijekom izvođenja i neograničenu količinu zajedničke memorije u obliku mapa.
BPF programima dopušteno je pokretanje određenog skupa kernel pomoćnika tipa programa i, u novije vrijeme, redovitih funkcija. Svaka pozvana funkcija može uzeti do pet argumenata koji se prosljeđuju u registrima r1-r5, a povratna vrijednost se prosljeđuje r0. Zajamčeno je da se nakon povratka s funkcije sadržaj registrira r6-r9 Neće se promijeniti.
Za učinkovito prevođenje programa, registri r0-r11 za sve podržane arhitekture jedinstveno su preslikani u stvarne registre, uzimajući u obzir ABI značajke trenutne arhitekture. Na primjer, za x86_64 registri r1-r5, koji se koristi za prosljeđivanje parametara funkcije, prikazani su na rdi, rsi, rdx, rcx, r8, koji se koriste za prosljeđivanje parametara funkcijama na x86_64. Na primjer, kôd s lijeve strane prevodi se u kôd s desne strane ovako:
Registar r0 također se koristi za vraćanje rezultata izvršenja programa, te u registru r1 programu se prosljeđuje pokazivač na kontekst - ovisno o vrsti programa, to može biti, na primjer, struktura struct xdp_md (za XDP) ili strukturu struct __sk_buff (za različite mrežne programe) ili strukturu struct pt_regs (za različite vrste programa za praćenje), itd.
Dakle, imali smo skup registara, pomoćnike kernela, stog, pokazivač konteksta i zajedničku memoriju u obliku mapa. Nije da je sve ovo prijeko potrebno na putovanju, ali...
Nastavimo s opisom i razgovarajmo o sustavu naredbi za rad s ovim objektima. Svi (Gotovo sve) BPF instrukcije imaju fiksnu 64-bitnu veličinu. Ako pogledate jednu instrukciju na 64-bitnom Big Endian stroju vidjet ćete
Ovdje Code - ovo je kodiranje instrukcije, Dst/Src su kodiranja prijemnika i izvora, redom, Off - 16-bitno uvlačenje s predznakom, i Imm je 32-bitni cijeli broj s predznakom koji se koristi u nekim uputama (slično cBPF konstanti K). Kodiranje Code ima jednu od dvije vrste:
Klase instrukcija 0, 1, 2, 3 definiraju naredbe za rad s memorijom. Oni se zovu, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, odnosno. Razredi 4, 7 (BPF_ALU, BPF_ALU64) čine skup ALU instrukcija. Razredi 5, 6 (BPF_JMP, BPF_JMP32) sadrže upute za skok.
Daljnji plan proučavanja sustava BPF instrukcija je sljedeći: umjesto pedantnog nabrajanja svih instrukcija i njihovih parametara, pogledat ćemo par primjera u ovom dijelu i iz njih će postati jasno kako instrukcije zapravo funkcioniraju i kako ručno rastaviti bilo koju binarnu datoteku za BPF. Da bismo konsolidirali materijal kasnije u članku, također ćemo se susresti s pojedinačnim uputama u odjeljcima o Verifikatoru, JIT kompajleru, prijevodu klasičnog BPF-a, kao i kod proučavanja mapa, pozivanja funkcija itd.
Pogledajmo primjer u kojem kompiliramo program readelf-example.c i pogledajte rezultirajuću binarnu datoteku. Otkrit ćemo izvorni sadržaj readelf-example.c u nastavku, nakon što obnovimo njegovu logiku iz binarnih kodova:
Kodovi naredbi su jednaki b7, 15, b7 и 95. Podsjetimo se da su tri najmanje značajna bita klasa instrukcije. U našem slučaju, četvrti bit svih instrukcija je prazan, tako da su klase instrukcija 7, 5, 7, 5, redom. Klasa 7 je BPF_ALU64, a 5 je BPF_JMP. Za obje klase, format instrukcija je isti (vidi gore) i možemo prepisati naš program ovako (istovremeno ćemo prepisati preostale stupce u ljudskom obliku):
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
Operacija b razred ALU64 - Je BPF_MOV. Dodjeljuje vrijednost odredišnom registru. Ako je bit postavljen s (izvor), tada se vrijednost preuzima iz izvornog registra, a ako, kao u našem slučaju, nije postavljen, tada se vrijednost preuzima iz polja Imm. Dakle, u prvoj i trećoj uputi izvodimo operaciju r0 = Imm. Nadalje, rad JMP klase 1 je BPF_JEQ (skok ako je jednako). U našem slučaju, budući da je bit S je nula, uspoređuje vrijednost izvornog registra s poljem Imm. Ako se vrijednosti podudaraju, tada dolazi do prijelaza PC + OffGdje PC, kao i obično, sadrži adresu sljedeće instrukcije. Konačno, JMP Class 9 Operation je BPF_EXIT. Ova instrukcija prekida program, vraćajući se u jezgru r0. Dodajmo novi stupac u našu tablicu:
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
Ovo možemo prepisati u prikladnijem obliku:
r0 = 1
if (r1 == 0) goto END
r0 = 2
END:
exit
Ako se sjetimo što stoji u registru r1 programu se prosljeđuje pokazivač na kontekst iz kernela iu registru r0 vrijednost se vraća jezgri, tada možemo vidjeti da ako je pokazivač na kontekst nula, tada vraćamo 1, au suprotnom - 2. Provjerimo da li smo u pravu gledajući izvor:
Da, to je besmislen program, ali prevodi se u samo četiri jednostavne upute.
Primjer iznimke: 16-bajtna instrukcija
Ranije smo spomenuli da neke upute zauzimaju više od 64 bita. To se, primjerice, odnosi na upute lddw (Kod = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — učitavanje dvostruke riječi iz polja u registar Imm, Činjenica je to Imm ima veličinu 32, a dvostruka riječ je 64 bita, tako da učitavanje 64-bitne neposredne vrijednosti u registar u jednoj 64-bitnoj instrukciji neće raditi. U tu svrhu koriste se dvije susjedne instrukcije za pohranjivanje drugog dijela 64-bitne vrijednosti u polje Imm, Primjer:
Ponovno ćemo se naći s uputama lddw, kada govorimo o selidbama i radu s kartama.
Primjer: rastavljanje BPF-a pomoću standardnih alata
Dakle, naučili smo čitati BPF binarne kodove i spremni smo analizirati svaku instrukciju ako je potrebno. Međutim, vrijedi reći da je u praksi praktičnije i brže rastaviti programe pomoću standardnih alata, na primjer:
Životni ciklus BPF objekata, bpffs datotečni sustav
(Prvi put sam saznao neke detalje opisane u ovom pododjeljku od post Aleksej Starovoitov u Blog BPF-a.)
BPF objekti - programi i mape - kreiraju se iz korisničkog prostora pomoću naredbi BPF_PROG_LOAD и BPF_MAP_CREATE sistemski poziv bpf(2), govorit ćemo o tome kako se to točno događa u sljedećem odjeljku. Ovo stvara strukture podataka jezgre i za svaku od njih refcount (broj referenci) je postavljen na jedan, a deskriptor datoteke koji pokazuje na objekt vraća se korisniku. Nakon što je ručka zatvorena refcount objekt se smanjuje za jedan, a kada dosegne nulu, objekt se uništava.
Ako program koristi karte, onda refcount te se karte povećavaju za jedan nakon učitavanja programa, tj. njihovi deskriptori datoteka mogu se zatvoriti iz korisničkog procesa i dalje refcount neće postati nula:
Nakon uspješnog učitavanja programa obično ga priključimo na neku vrstu generatora događaja. Na primjer, možemo ga staviti na mrežno sučelje za obradu dolaznih paketa ili ga povezati s nekim od njih tracepoint u jezgri. U ovom trenutku će se brojač referenci također povećati za jedan i moći ćemo zatvoriti deskriptor datoteke u programu za učitavanje.
Što se događa ako sada isključimo bootloader? Ovisi o vrsti generatora događaja (hooka). Sve mrežne kuke postojat će nakon što loader završi, to su takozvane globalne kuke. I, na primjer, programi praćenja bit će pušteni nakon završetka procesa koji ih je stvorio (i stoga se nazivaju lokalnim, od "lokalno prema procesu"). Tehnički, lokalne kuke uvijek imaju odgovarajući deskriptor datoteke u korisničkom prostoru i stoga se zatvaraju kada se proces zatvori, ali globalne kuke nemaju. Na sljedećoj slici, pomoću crvenih križića, pokušavam pokazati kako prekid programa učitavanja utječe na životni vijek objekata u slučaju lokalnih i globalnih zakačica.
Zašto postoji razlika između lokalnih i globalnih udica? Pokretanje nekih vrsta mrežnih programa ima smisla bez korisničkog prostora, na primjer, zamislite DDoS zaštitu - bootloader napiše pravila i poveže BPF program s mrežnim sučeljem, nakon čega se bootloader može ubiti. S druge strane, zamislite debugging trace program koji ste napisali na koljenima u deset minuta – kada bude gotov, htjeli biste da u sustavu ne ostane smeća, a lokalne kuke će to osigurati.
S druge strane, zamislite da se želite spojiti na točku praćenja u kernelu i prikupljati statistiku tijekom mnogo godina. U ovom slučaju, trebali biste dovršiti korisnički dio i povremeno se vraćati na statistiku. Datotečni sustav bpf pruža ovu priliku. To je sustav pseudo datoteka samo u memoriji koji omogućuje stvaranje datoteka koje referenciraju BPF objekte i time povećavaju refcount objekti. Nakon toga, loader može izaći, a objekti koje je stvorio ostat će živi.
Stvaranje datoteka u bpffs koje referenciraju BPF objekte naziva se "pinning" (kao u sljedećem izrazu: "proces može pin BPF program ili mapu"). Stvaranje datotečnih objekata za BPF objekte ima smisla ne samo zbog produljenja života lokalnih objekata, već i zbog upotrebljivosti globalnih objekata - vraćajući se na primjer s globalnim programom zaštite od DDoS-a, želimo imati mogućnost doći i pogledati statistiku s vremena na vrijeme.
Datotečni sustav BPF obično se montira /sys/fs/bpf, ali se može montirati i lokalno, na primjer, ovako:
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
Nazivi datotečnih sustava kreiraju se pomoću naredbe BPF_OBJ_PIN Poziv BPF sustava. Za ilustraciju, uzmimo program, prevedimo ga, učitajmo i prikvačimo bpffs. Naš program ne čini ništa korisno, mi samo predstavljamo kôd kako biste mogli reproducirati primjer:
Sada preuzmimo naš program pomoću uslužnog programa bpftool i pogledajte prateće sistemske pozive bpf(2) (neki nevažni redovi uklonjeni iz strace izlaza):
Ovdje smo učitali program pomoću BPF_PROG_LOAD, primio je deskriptor datoteke od kernela 3 i pomoću naredbe BPF_OBJ_PIN prikvačio je ovaj deskriptor datoteke kao datoteku "bpf-mountpoint/test". Nakon ovoga program za podizanje sustava bpftool završio s radom, ali naš je program ostao u kernelu, iako ga nismo priključili ni na jedno mrežno sučelje:
$ 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
Objekt datoteke možemo normalno izbrisati unlink(2) i nakon toga odgovarajući program će biti izbrisan:
$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory
Brisanje objekata
Govoreći o brisanju objekata, potrebno je pojasniti da nakon što odspojimo program sa kuke (generatora događaja), niti jedan novi događaj neće pokrenuti njegovo pokretanje, međutim, sve trenutne instance programa će se završiti normalnim redoslijedom .
Neke vrste BPF programa omogućuju zamjenu programa u hodu, tj. osigurati atomičnost sekvence replace = detach old program, attach new program. U tom će slučaju sve aktivne instance stare verzije programa završiti s radom, a novi rukovatelji događajima bit će kreirani iz novog programa, a “atomičnost” ovdje znači da niti jedan događaj neće biti propušten.
Prilaganje programa izvorima događaja
U ovom članku nećemo posebno opisivati povezivanje programa s izvorima događaja, budući da to ima smisla proučavati u kontekstu specifične vrste programa. Cm. primjer u nastavku, u kojem pokazujemo kako su povezani programi poput XDP-a.
Manipuliranje objektima pomoću bpf sistemskog poziva
BPF programi
Svi BPF objekti kreiraju se i njima se upravlja iz korisničkog prostora pomoću sistemskog poziva bpf, koji ima sljedeći prototip:
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
Evo tima cmd je jedna od vrijednosti tipa enum bpf_cmd, attr — pokazivač na parametre za određeni program i size — veličina objekta prema pokazivaču, tj. obično ovo sizeof(*attr). U kernelu 5.8 sistemski poziv bpf podržava 34 različite naredbe, i utvrđivanjeunion bpf_attr zauzima 200 redaka. No, to nas ne bi trebalo zastrašiti, budući da ćemo se s naredbama i parametrima upoznati tijekom nekoliko članaka.
Počnimo s ekipom BPF_PROG_LOAD, koji stvara BPF programe - uzima skup BPF instrukcija i učitava ih u kernel. U trenutku učitavanja pokreće se verifikator, a potom JIT prevodilac i nakon uspješnog izvođenja korisniku se vraća deskriptor programske datoteke. Vidjeli smo što se dalje s njim događa u prethodnom odjeljku o životnom ciklusu BPF objekata.
Sada ćemo napisati prilagođeni program koji će učitati jednostavan BPF program, ali prvo moramo odlučiti kakvu vrstu programa želimo učitati - morat ćemo odabrati тип te u okviru ovog tipa napisati program koji će proći test verifikatora. No, kako ne bismo zakomplicirali proces, evo gotovog rješenja: uzet ćemo program poput BPF_PROG_TYPE_XDP, koji će vratiti vrijednost XDP_PASS (preskoči sve pakete). U BPF asembleru to izgleda vrlo jednostavno:
r0 = 2
exit
Nakon što smo se odlučili za da prenijet ćemo, možemo vam reći kako ćemo to učiniti:
Zanimljivi događaji u programu počinju s definiranjem niza insns - naš BPF program u strojnom kodu. U ovom slučaju, svaka instrukcija BPF programa je upakirana u strukturu bpf_insn. Prvi element insns u skladu s uputama r0 = 2, drugi - exit.
Povlačenje. Kernel definira prikladnije makronaredbe za pisanje strojnih kodova i korištenje datoteke zaglavlja kernela tools/include/linux/filter.h mogli bismo napisati
No budući da je pisanje BPF programa u izvornom kodu potrebno samo za pisanje testova u kernelu i članaka o BPF-u, odsutnost ovih makronaredbi zapravo ne komplicira život programera.
Nakon definiranja BPF programa, prelazimo na njegovo učitavanje u kernel. Naš minimalistički skup parametara attr uključuje vrstu programa, skup i broj uputa, potrebnu licencu i naziv "woo", koji koristimo za pronalaženje našeg programa u sustavu nakon preuzimanja. Program se, kao što je obećano, učitava u sustav pomoću sistemskog poziva bpf.
Na kraju programa završavamo u beskonačnoj petlji koja simulira korisni teret. Bez toga, kernel će ugasiti program kada se zatvori deskriptor datoteke koji nam je sistemski poziv vratio bpf, a nećemo ga vidjeti u sustavu.
Pa, spremni smo za testiranje. Sastavimo i pokrenimo program pod straceda provjerite radi li sve kako treba:
Sve je u redu, bpf(2) vratio nam je handle 3 i ušli smo u beskonačnu petlju s pause(). Pokušajmo pronaći naš program u sustavu. Da bismo to učinili, otići ćemo na drugi terminal i koristiti uslužni program bpftool:
Vidimo da postoji učitani program na sustavu woo čiji je globalni ID 390 i trenutno je u tijeku simple-prog postoji deskriptor otvorene datoteke koji upućuje na program (i ako simple-prog onda će završiti posao woo nestat će). Očekivano, program woo uzima 16 bajtova - dvije instrukcije - binarnih kodova u BPF arhitekturi, ali u izvornom obliku (x86_64) već ima 40 bajtova. Pogledajmo naš program u izvornom obliku:
bez iznenađenja. Sada pogledajmo kod koji je generirao JIT kompajler:
# 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
nije baš učinkovit za exit(2), ali pošteno govoreći, naš program je previše jednostavan, a za netrivijalne programe su, naravno, potrebni prolog i epilog koje je dodao JIT kompajler.
Karte
BPF programi mogu koristiti strukturirana memorijska područja koja su dostupna i drugim BPF programima i programima u korisničkom prostoru. Ti se objekti nazivaju mapama i u ovom odjeljku pokazat ćemo kako njima manipulirati pomoću sistemskog poziva bpf.
Recimo odmah da mogućnosti karata nisu ograničene samo na pristup zajedničkoj memoriji. Postoje mape posebne namjene koje sadrže, na primjer, pokazivače na BPF programe ili pokazivače na mrežna sučelja, mape za rad s perf događajima itd. O njima ovdje nećemo govoriti, da ne zbunimo čitatelja. Osim ovoga, zanemarujemo probleme sinkronizacije, jer to nije važno za naše primjere. Potpuni popis dostupnih vrsta karata može se pronaći u <linux/bpf.h>, au ovom odjeljku ćemo uzeti kao primjer povijesno prvi tip, hash tablicu BPF_MAP_TYPE_HASH.
Ako kreirate hash tablicu u, recimo, C++, rekli biste unordered_map<int,long> woo, što na ruskom znači “Trebam stol woo neograničene veličine, čiji su ključevi vrste int, a vrijednosti su tip long" Kako bismo stvorili BPF hash tablicu, moramo učiniti gotovo istu stvar, osim što moramo navesti maksimalnu veličinu tablice, a umjesto da navedemo tipove ključeva i vrijednosti, moramo navesti njihove veličine u bajtovima . Za izradu karata koristite naredbu BPF_MAP_CREATE sistemski poziv bpf. Pogledajmo manje-više minimalan program koji stvara kartu. Nakon prethodnog programa koji učitava BPF programe, ovaj bi vam se trebao učiniti jednostavnim:
Ovdje definiramo skup parametara attr, u kojem kažemo “Trebam hash tablicu s ključevima i vrijednostima veličine sizeof(int), u koji mogu staviti najviše četiri elementa." Prilikom izrade BPF mapa, možete odrediti druge parametre, na primjer, na isti način kao u primjeru s programom, naveli smo naziv objekta kao "woo".
Ovo je sistemski poziv bpf(2) vratio nam je broj karte deskriptora 3 a zatim program očekivano čeka daljnje upute u pozivu sustava pause(2).
Sada pošaljimo naš program u pozadinu ili otvorimo drugi terminal i pogledajmo naš objekt pomoću uslužnog programa bpftool (našu kartu možemo razlikovati od ostalih po nazivu):
$ sudo bpftool map
...
114: hash name woo flags 0x0
key 4B value 4B max_entries 4 memlock 4096B
...
Broj 114 je globalni ID našeg objekta. Svaki program u sustavu može koristiti ovaj ID za otvaranje postojeće karte pomoću naredbe BPF_MAP_GET_FD_BY_ID sistemski poziv bpf.
Sada se možemo igrati s našom hash tablicom. Pogledajmo njegov sadržaj:
$ sudo bpftool map dump id 114
Found 0 elements
Prazan. Stavimo vrijednost u to hash[1] = 1:
$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0
Pogledajmo opet tablicu:
$ sudo bpftool map dump id 114
key: 01 00 00 00 value: 01 00 00 00
Found 1 element
hura! Uspjeli smo dodati jedan element. Imajte na umu da moramo raditi na razini bajta da bismo to učinili, jer bptftool ne zna koje su vrste vrijednosti u hash tablici. (Ovo znanje joj se može prenijeti pomoću BTF-a, ali više o tome sada.)
Kako točno bpftool čita i dodaje elemente? Pogledajmo ispod haube:
Prvo smo otvorili kartu prema njenom globalnom ID-u pomoću naredbe BPF_MAP_GET_FD_BY_ID и bpf(2) vratio nam je deskriptor 3. Daljnjim korištenjem naredbe BPF_MAP_GET_NEXT_KEY mimoilazeći smo pronašli prvi ključ u tablici NULL kao pokazivač na "prethodni" ključ. Ako imamo ključ, možemo BPF_MAP_LOOKUP_ELEMkoji vraća vrijednost pokazivaču value. Sljedeći korak je da pokušamo pronaći sljedeći element prosljeđivanjem pokazivača na trenutni ključ, ali naša tablica sadrži samo jedan element i naredbu BPF_MAP_GET_NEXT_KEY vraća ENOENT.
U redu, promijenimo vrijednost ključem 1, recimo da naša poslovna logika zahtijeva registraciju hash[1] = 2:
Očekivano, vrlo je jednostavno: naredba BPF_MAP_GET_FD_BY_ID otvara našu kartu po ID-u i naredbi BPF_MAP_UPDATE_ELEM prepisuje element.
Dakle, nakon što kreiramo hash tablicu iz jednog programa, možemo čitati i pisati njen sadržaj iz drugog. Imajte na umu da ako smo mi to mogli učiniti iz naredbenog retka, onda to može učiniti bilo koji drugi program u sustavu. Osim gore opisanih naredbi, za rad s kartama iz korisničkog prostora, Sljedeći:
BPF_MAP_LOOKUP_ELEM: pronađite vrijednost po ključu
BPF_MAP_UPDATE_ELEM: ažuriraj/stvori vrijednost
BPF_MAP_DELETE_ELEM: izvadite ključ
BPF_MAP_GET_NEXT_KEY: pronađite sljedeći (ili prvi) ključ
BPF_MAP_GET_NEXT_ID: omogućuje vam prolazak kroz sve postojeće karte, tako to funkcionira bpftool map
BPF_MAP_GET_FD_BY_ID: otvori postojeću kartu prema njezinom globalnom ID-u
BPF_MAP_LOOKUP_AND_DELETE_ELEM: atomski ažurira vrijednost objekta i vraća staru
BPF_MAP_FREEZE: učini kartu nepromjenjivom iz korisničkog prostora (ova se operacija ne može poništiti)
BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: masovne operacije. Na primjer, BPF_MAP_LOOKUP_AND_DELETE_BATCH - ovo je jedini pouzdan način za čitanje i resetiranje svih vrijednosti s karte
Ne rade sve ove naredbe za sve vrste karata, ali općenito rad s drugim vrstama karata iz korisničkog prostora izgleda potpuno isto kao i rad s hash tablicama.
Reda radi, završimo naše eksperimente s hash tablicom. Sjećate se da smo napravili tablicu koja može sadržavati do četiri ključa? Dodajmo još nekoliko elemenata:
$ 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
$ 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 +++
Sve je u redu: očekivano, tim BPF_MAP_UPDATE_ELEM pokušava stvoriti novi, peti, ključ, ali se ruši E2BIG.
Dakle, možemo kreirati i učitavati BPF programe, kao i kreirati karte i upravljati njima iz korisničkog prostora. Sada je logično pogledati kako možemo koristiti karte iz samih BPF programa. O tome bismo mogli govoriti jezikom teško čitljivih programa u strojnim makro kodovima, ali zapravo je došlo vrijeme da se pokaže kako se BPF programi zapravo pišu i održavaju - korištenjem libbpf.
(Za čitatelje koji su nezadovoljni nedostatkom primjera niske razine: detaljno ćemo analizirati programe koji koriste karte i pomoćne funkcije stvorene pomoću libbpf i reći vam što se događa na razini instrukcija. Za čitatelje koji su nezadovoljni jako puno, dodali smo primjer na odgovarajuće mjesto u članku.)
Pisanje BPF programa koristeći libbpf
Pisanje BPF programa pomoću strojnih kodova može biti zanimljivo samo prvi put, a onda nastupi sitost. U ovom trenutku morate obratiti pažnju na llvm, koji ima pozadinu za generiranje koda za BPF arhitekturu, kao i biblioteku libbpf, koji vam omogućuje pisanje korisničke strane BPF aplikacija i učitavanje koda BPF programa generiranih pomoću llvm/clang.
Zapravo, kao što ćemo vidjeti u ovom i sljedećim člancima, libbpf radi dosta posla bez njega (ili sličnih alata - iproute2, libbcc, libbpf-go, itd.) nemoguće je živjeti. Jedna od ubojitih karakteristika projekta libbpf je BPF CO-RE (Compile Once, Run Everywhere) - projekt koji vam omogućuje pisanje BPF programa koji su prenosivi s jednog kernela na drugi, s mogućnošću pokretanja na različitim API-jima (na primjer, kada se struktura kernela promijeni iz verzije do verzije). Kako biste mogli raditi s CO-RE, vaš kernel mora biti kompajliran s BTF podrškom (opisujemo kako to učiniti u odjeljku Razvojni alati. Možete provjeriti je li vaša jezgra izgrađena s BTF-om ili ne vrlo jednostavno - prisutnošću sljedeće datoteke:
Ova datoteka pohranjuje informacije o svim tipovima podataka koji se koriste u kernelu i koristi se u svim našim primjerima korištenja libbpf. Detaljno ćemo govoriti o CO-RE-u u sljedećem članku, ali u ovom - samo izgradite sebi kernel CONFIG_DEBUG_INFO_BTF.
knjižnica libbpf živi točno u imeniku tools/lib/bpf kernel i njegov razvoj se provodi putem mailing liste [email protected]. Međutim, održava se zasebno spremište za potrebe aplikacija koje žive izvan kernela https://github.com/libbpf/libbpf u kojoj je biblioteka kernela zrcaljena za pristup čitanju više-manje kakva jest.
U ovom odjeljku ćemo pogledati kako možete stvoriti projekt koji koristi libbpf, napišimo nekoliko (više ili manje besmislenih) testnih programa i detaljno analizirajmo kako sve to radi. To će nam omogućiti da u sljedećim odjeljcima lakše objasnimo kako točno BPF programi stupaju u interakciju s mapama, pomoćnicima kernela, BTF-om itd.
Obično projekti koriste libbpf dodati GitHub repozitorij kao git podmodul, učinit ćemo isto:
Naš sljedeći plan u ovom odjeljku je sljedeći: napisat ćemo BPF program poput BPF_PROG_TYPE_XDP, isto kao u prethodnom primjeru, ali u C-u, kompajliramo ga pomoću clang, i napišite pomoćni program koji će ga učitati u kernel. U sljedećim odjeljcima proširit ćemo mogućnosti i programa BPF i programa pomoćnika.
Primjer: stvaranje potpune aplikacije pomoću libbpf
Za početak koristimo datoteku /sys/kernel/btf/vmlinux, koji je gore spomenut, i stvorite njegov ekvivalent u obliku datoteke zaglavlja:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
Ova datoteka će pohraniti sve strukture podataka dostupne u našem kernelu, na primjer, ovako je IPv4 zaglavlje definirano u kernelu:
Iako se naš program pokazao vrlo jednostavnim, ipak moramo obratiti pozornost na mnoge detalje. Prvo, prva datoteka zaglavlja koju uključujemo je vmlinux.h, koji smo upravo generirali pomoću bpftool btf dump - sada ne trebamo instalirati kernel-headers paket da saznamo kako strukture kernela izgledaju. Sljedeća datoteka zaglavlja dolazi nam iz knjižnice libbpf. Sada nam treba samo za definiranje makronaredbe SEC, koji šalje znak u odgovarajući odjeljak ELF objektne datoteke. Naš program nalazi se u rubrici xdp/simple, gdje prije kose crte definiramo tip programa BPF - ovo je konvencija koja se koristi u libbpf, na temelju naziva odjeljka zamijenit će točnu vrstu pri pokretanju bpf(2). Sam program BPF-a je C - vrlo jednostavan i sastoji se od jedne linije return XDP_PASS. Na kraju, poseban dio "license" sadrži naziv licence.
Naš program možemo prevesti pomoću llvm/clang, verzija >= 10.0.0, ili još bolje, veća (pogledajte odjeljak Razvojni alati):
Među zanimljivim značajkama: ukazujemo na ciljnu arhitekturu -target bpf i put do zaglavlja libbpf, koju smo nedavno instalirali. Također, ne zaboravite na -O2, bez ove opcije možda vas čekaju iznenađenja u budućnosti. Pogledajmo naš kod, jesmo li uspjeli napisati program koji smo htjeli?
Da, uspjelo je! Sada imamo binarnu datoteku s programom i želimo stvoriti aplikaciju koja će je učitati u kernel. U tu svrhu knjižnica libbpf nudi nam dvije opcije - korištenje API-ja niže razine ili API-ja više razine. Ići ćemo drugim putem, jer želimo naučiti kako pisati, učitati i povezati BPF programe uz minimalan napor za njihovo kasnije proučavanje.
Prvo, moramo generirati "kostur" našeg programa iz njegove binarne datoteke pomoću istog uslužnog programa bpftool — švicarski nož BPF svijeta (što se može shvatiti doslovno, budući da je Daniel Borkman, jedan od kreatora i održavatelja BPF-a, Švicarac):
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
U spisu xdp-simple.skel.h sadrži binarni kod našeg programa i funkcije za upravljanje - učitavanje, prilaganje, brisanje našeg objekta. U našem jednostavnom slučaju ovo izgleda kao pretjerano, ali također funkcionira u slučaju kada objektna datoteka sadrži mnogo BPF programa i mapa i da bismo učitali ovaj divovski ELF samo trebamo generirati kostur i pozvati jednu ili dvije funkcije iz prilagođene aplikacije koju pišu Idemo dalje.
Strogo govoreći, naš program za učitavanje je trivijalan:
#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);
}
Ovdje struct xdp_simple_bpf definiran u datoteci xdp-simple.skel.h i opisuje našu objektnu datoteku:
Ovdje možemo vidjeti tragove API-ja niske razine: struktura struct bpf_program *simple и struct bpf_link *simple. Prva struktura posebno opisuje naš program, napisan u odjeljku xdp/simple, a drugi opisuje kako se program povezuje s izvorom događaja.
Funkcija xdp_simple_bpf__open_and_load, otvara ELF objekt, analizira ga, stvara sve strukture i podstrukture (osim programa, ELF sadrži i druge odjeljke - podatke, podatke samo za čitanje, informacije o ispravljanju pogrešaka, licencu itd.), a zatim ga učitava u kernel pomoću sustava poziv bpf, što možemo provjeriti prevođenjem i pokretanjem programa:
Pogledajmo sada korištenje našeg programa bpftool. Pronađimo njen 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)
i dump (koristimo skraćeni oblik naredbe bpftool prog dump xlated):
# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
0: (b7) r0 = 2
1: (95) exit
Nešto novo! Program je ispisao dijelove naše izvorne datoteke C. To je učinila knjižnica libbpf, koji je pronašao odjeljak za otklanjanje pogrešaka u binarnom obliku, preveo ga u BTF objekt, učitao u kernel pomoću BPF_BTF_LOAD, a zatim odredio rezultirajući deskriptor datoteke prilikom učitavanja programa s naredbom BPG_PROG_LOAD.
Pomoćnici kernela
BPF programi mogu pokretati "vanjske" funkcije - pomagače kernela. Ove pomoćne funkcije omogućuju BPF programima pristup strukturama jezgre, upravljanje mapama i također komunikaciju sa "stvarnim svijetom" - stvaranje perf događaja, kontrola hardvera (na primjer, preusmjeravanje paketa), itd.
Primjer: bpf_get_smp_processor_id
Unutar okvira paradigme “učenja primjerom”, razmotrimo jednu od pomoćnih funkcija, bpf_get_smp_processor_id(), određeni u spisu kernel/bpf/helpers.c. Vraća broj procesora na kojem se izvodi BPF program koji ga je pozvao. Ali nas ne zanima toliko njegova semantika koliko činjenica da njegova implementacija ima jedan redak:
Definicije BPF pomoćne funkcije slične su definicijama poziva sustava Linux. Ovdje je npr. definirana funkcija koja nema argumenata. (Funkcija koja uzima, recimo, tri argumenta definirana je pomoću makronaredbe BPF_CALL_3. Maksimalan broj argumenata je pet.) Međutim, ovo je samo prvi dio definicije. Drugi dio je definiranje strukture tipa struct bpf_func_proto, koji sadrži opis pomoćne funkcije koju verifikator razumije:
Da bi BPF programi određene vrste mogli koristiti ovu funkciju, moraju je registrirati, na primjer za vrstu BPF_PROG_TYPE_XDP funkcija je definirana u kernelu xdp_func_proto, koji na temelju ID-a pomoćne funkcije određuje podržava li XDP ovu funkciju ili ne. Naša funkcija je podupire:
Novi tipovi BPF programa su "definirani" u datoteci include/linux/bpf_types.h pomoću makronaredbe BPF_PROG_TYPE. Definirano pod navodnicima jer je to logična definicija, au terminima jezika C definicija čitavog niza konkretnih struktura pojavljuje se na drugim mjestima. Konkretno, u spisu kernel/bpf/verifier.c sve definicije iz datoteke bpf_types.h koriste se za stvaranje niza struktura bpf_verifier_ops[]:
To jest, za svaki tip BPF programa definiran je pokazivač na podatkovnu strukturu tipa struct bpf_verifier_ops, koji se inicijalizira s vrijednošću _name ## _verifier_ops, tj. xdp_verifier_ops za xdp. Struktura xdp_verifier_opsodređeno od strane u spisu net/core/filter.c kako slijedi:
Ovdje vidimo našu poznatu funkciju xdp_func_proto, koji će pokrenuti verifikator svaki put kada naiđe na izazov neka vrsta funkcije unutar BPF programa, vidi verifier.c.
Pogledajmo kako hipotetski BPF program koristi funkciju bpf_get_smp_processor_id. Da bismo to učinili, prepisujemo program iz našeg prethodnog odjeljka na sljedeći način:
to je, bpf_get_smp_processor_id je pokazivač funkcije čija je vrijednost 8, gdje je 8 vrijednost BPF_FUNC_get_smp_processor_id vrsta enum bpf_fun_id, koji nam je definiran u datoteci vmlinux.h (datoteka bpf_helper_defs.h u kernelu generira skripta, tako da su "magični" brojevi u redu). Ova funkcija ne uzima argumente i vraća vrijednost tipa __u32. Kada to pokrenemo u našem programu, clang generira instrukciju BPF_CALL "prava vrsta" Sastavimo program i pogledajmo odjeljak xdp/simple:
U prvom redu vidimo upute call, parametar IMM što je jednako 8, i SRC_REG - nula. Prema ABI sporazumu koji koristi verifikator, ovo je poziv pomoćnoj funkciji broj osam. Nakon što se pokrene, logika je jednostavna. Povratna vrijednost iz registra r0 kopirano u r1 a na linijama 2,3 pretvara se u tip u32 — brišu se gornja 32 bita. U redovima 4,5,6,7 vraćamo 2 (XDP_PASS) ili 1 (XDP_DROP) ovisno o tome je li pomoćna funkcija iz retka 0 vratila vrijednost nula ili ne.
Testirajmo se: učitajmo program i pogledajmo izlaz 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
U redu, verifikator je pronašao ispravan kernel-helper.
Primjer: prosljeđivanje argumenata i konačno pokretanje programa!
Sve pomoćne funkcije na razini izvođenja imaju prototip
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
Parametri pomoćnim funkcijama prosljeđuju se u registrima r1-r5, a vrijednost se vraća u registar r0. Ne postoje funkcije koje uzimaju više od pet argumenata i ne očekuje se da će podrška za njih biti dodana u budućnosti.
Pogledajmo novi pomagač kernela i kako BPF prosljeđuje parametre. Prepišimo xdp-simple.bpf.c kako slijedi (ostali redovi nisu promijenjeni):
SEC("xdp/simple")
int simple(void *ctx)
{
bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
return XDP_PASS;
}
Naš program ispisuje broj CPU-a na kojem radi. Sastavimo ga i pogledajmo kod:
U redovima 0-7 upisujemo niz running on CPU%un, a zatim na retku 8 pokrećemo poznati bpf_get_smp_processor_id. U redovima 9-12 pripremamo pomoćne argumente bpf_printk - registri r1, r2, r3. Zašto su tri, a ne dva? Jer bpf_printk - ovo je makro omotač oko pravog pomagača bpf_trace_printk, koji treba proslijediti veličinu niza formata.
Dodajmo sada nekoliko redaka u xdp-simple.ctako da se naš program povezuje sa sučeljem lo i stvarno je počelo!
Ovdje koristimo funkciju bpf_set_link_xdp_fd, koji povezuje BPF programe tipa XDP s mrežnim sučeljima. Čvrsto smo kodirali broj sučelja lo, što je uvijek 1. Funkciju pokrećemo dva puta da prvo odvojimo stari program ako je bio pripojen. Imajte na umu da nam sada ne treba izazov pause ili beskonačna petlja: naš program za učitavanje će izaći, ali BPF program neće biti ubijen jer je povezan s izvorom događaja. Nakon uspješnog preuzimanja i povezivanja, program će se pokrenuti za svaki mrežni paket koji stigne lo.
Skinimo program i pogledajmo sučelje 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
Program koji smo preuzeli ima ID 669 i vidimo isti ID na sučelju lo. Poslat ćemo par paketa na 127.0.0.1 (zahtjev + odgovor):
$ ping -c1 localhost
a sada pogledajmo sadržaj debug virtualne datoteke /sys/kernel/debug/tracing/trace_pipe, u kojem bpf_printk piše svoje poruke:
# 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
Uočena su dva paketa lo i obrađen na CPU0 - naš prvi punopravni besmisleni BPF program je radio!
Vrijedi napomenuti da bpf_printk Nije uzalud to što piše u debug datoteku: ovo nije najuspješniji pomoćnik za upotrebu u proizvodnji, ali naš je cilj bio pokazati nešto jednostavno.
Pristup kartama iz BPF programa
Primjer: korištenje karte iz programa BPF
U prethodnim odjeljcima smo naučili kako kreirati i koristiti karte iz korisničkog prostora, a sada pogledajmo dio jezgre. Počnimo, kao i obično, s primjerom. Napišimo ponovno naš program xdp-simple.bpf.c kako slijedi:
Na početku programa dodali smo definiciju karte woo: Ovo je niz od 8 elemenata koji pohranjuje vrijednosti poput u64 (u C-u bismo takav niz definirali kao u64 woo[8]). U programu "xdp/simple" dobivamo trenutni broj procesora u varijablu key a zatim pomoću pomoćne funkcije bpf_map_lookup_element dobivamo pokazivač na odgovarajući unos u nizu koji povećavamo za jedan. Prevedeno na ruski: izračunavamo statistiku o tome koji je CPU obradio dolazne pakete. Pokušajmo pokrenuti program:
Provjerimo je li spojena lo i pošaljite neke pakete:
$ 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
Gotovo svi procesi su obrađeni na CPU7. Ovo nam nije važno, glavno je da program radi i da razumijemo kako pristupiti kartama iz BPF programa - koristeći хелперов bpf_mp_*.
Mistični indeks
Dakle, možemo pristupiti karti iz BPF programa koristeći pozive poput
$ 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
Ali ako pogledamo već učitani program, vidimo pokazivač na ispravnu kartu (linija 4):
Stoga možemo zaključiti da je u trenutku pokretanja našeg programa za učitavanje poveznica na &woo je zamijenjen nečim s knjižnicom libbpf. Prvo ćemo pogledati izlaz strace:
To vidimo libbpf stvorio kartu woo a zatim preuzeli naš program simple. Pogledajmo pobliže kako učitavamo program:
poziv xdp_simple_bpf__open_and_load iz datoteke xdp-simple.skel.h
koji uzrokuje xdp_simple_bpf__load iz datoteke xdp-simple.skel.h
koji uzrokuje bpf_object__load_skeleton iz datoteke libbpf/src/libbpf.c
koji uzrokuje bpf_object__load_xattr od libbpf/src/libbpf.c
Posljednja funkcija će, između ostalog, pozvati bpf_object__create_maps, koji stvara ili otvara postojeće karte, pretvarajući ih u deskriptore datoteka. (Ovdje vidimo BPF_MAP_CREATE u izlazu strace.) Zatim se poziva funkcija bpf_object__relocate a ona je ta koja nas zanima, jer se sjećamo što smo vidjeli woo u tablici preseljenja. Istražujući ga, na kraju se nađemo u funkciji bpf_program__relocate, koji bavi se premještanjem karata:
case RELO_LD64:
insn[0].src_reg = BPF_PSEUDO_MAP_FD;
insn[0].imm = obj->maps[relo->map_idx].fd;
break;
i zamijenite izvorni registar u njemu sa BPF_PSEUDO_MAP_FD, a prvi IMM u deskriptor datoteke naše karte i, ako je jednak, na primjer, 0xdeadbeef, tada ćemo kao rezultat dobiti uputu
18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll
Ovo je način na koji se informacije karte prenose u određeni učitani BPF program. U ovom slučaju, karta se može izraditi pomoću BPF_MAP_CREATE, a otvorio ID koristeći BPF_MAP_GET_FD_BY_ID.
Ukupno, kada se koristi libbpf algoritam je sljedeći:
tijekom kompilacije, zapisi se stvaraju u tablici premještanja za veze na karte
libbpf otvara knjigu ELF objekata, pronalazi sve korištene mape i stvara deskriptore datoteka za njih
deskriptori datoteka se učitavaju u kernel kao dio instrukcija LD64
Kao što možete zamisliti, ima još toga i morat ćemo pogledati u srž. Srećom, imamo trag - zapisali smo značenje BPF_PSEUDO_MAP_FD u izvorni registar i možemo ga zakopati, što će nas odvesti do svetinje svih svetih - kernel/bpf/verifier.c, gdje funkcija s karakterističnim nazivom zamjenjuje deskriptor datoteke adresom strukture tipa struct bpf_map:
(puni kod se može pronaći по ссылке). Dakle, možemo proširiti naš algoritam:
prilikom učitavanja programa verifikator provjerava ispravnost korištenja karte i upisuje adresu odgovarajuće strukture struct bpf_map
Prilikom preuzimanja ELF binarne datoteke pomoću libbpf Ima još puno toga, ali o tome ćemo raspravljati u drugim člancima.
Učitavanje programa i mapa bez libbpf-a
Kao što je obećano, ovdje je primjer za čitatelje koji žele znati kako stvoriti i učitati program koji koristi karte, bez pomoći libbpf. Ovo može biti korisno kada radite u okruženju za koje ne možete graditi ovisnosti, ili štedjeti svaki bit, ili pisati program kao ply, koji generira BPF binarni kod u hodu.
Da bismo lakše pratili logiku, prepisat ćemo naš primjer za ove potrebe xdp-simple. Cjeloviti i malo prošireni kod programa o kojem se govori u ovom primjeru može se pronaći ovdje suština.
Logika naše aplikacije je sljedeća:
izraditi kartu tipa BPF_MAP_TYPE_ARRAY pomoću naredbe BPF_MAP_CREATE,
stvoriti program koji koristi ovu kartu,
spojite program na sučelje lo,
što se prevodi na ljudski kao
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);
}
Ovdje map_create stvara mapu na isti način kao što smo učinili u prvom primjeru o pozivu sustava bpf - “kernel, molim te napravi mi novu mapu u obliku niza od 8 elemenata poput __u64 i vrati mi deskriptor datoteke":
Zeznuti dio prog_load je definicija našeg BPF programa kao niza struktura struct bpf_insn insns[]. Ali budući da koristimo program koji imamo u C-u, možemo malo varati:
Ukupno trebamo napisati 14 uputa u obliku struktura poput struct bpf_insn (savjet: uzmite deponij odozgo, ponovno pročitajte odjeljak s uputama, otvorite linux/bpf.h и linux/bpf_common.h i pokušati utvrditi struct bpf_insn insns[] samostalno):
Vježba za one koji ovo nisu sami napisali - nađite map_fd.
U našem programu ostao je još jedan neobjavljen dio - xdp_attach. Nažalost, programi poput XDP-a ne mogu se povezati pomoću sistemskog poziva bpf. Ljudi koji su stvorili BPF i XDP bili su iz mrežne Linux zajednice, što znači da su koristili onaj koji im je najpoznatiji (ali ne za normalan ljudi) sučelje za interakciju s jezgrom: netlink utičnice, vidi također RFC3549. Najjednostavniji način implementacije xdp_attach kopira kod iz libbpf, naime, iz datoteke netlink.c, što smo i učinili, malo skrativši:
Dobrodošli u svijet netlink utičnica
Otvorite vrstu netlink utičnice 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;
}
Čitamo iz ove utičnice:
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;
}
Konačno, ovdje je naša funkcija koja otvara utičnicu i šalje joj posebnu poruku koja sadrži deskriptor datoteke:
$ 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
Hura, sve radi. Imajte na umu, usput, da je naša karta ponovno prikazana u obliku bajtova. To je zbog činjenice da, za razliku od libbpf nismo učitali informacije o vrsti (BTF). Ali o tome ćemo više sljedeći put.
Razvojni alati
U ovom ćemo odjeljku pogledati minimalni alat za razvojne programere BPF-a.
Općenito govoreći, ne trebate ništa posebno za razvoj BPF programa - BPF radi na bilo kojoj pristojnoj distribucijskoj jezgri, a programi su izgrađeni korištenjem clang, koji se može isporučiti iz paketa. Međutim, zbog činjenice da je BPF u razvoju, kernel i alati se stalno mijenjaju, ako ne želite pisati BPF programe koristeći staromodne metode iz 2019., tada ćete morati kompajlirati
llvm/clang
pahole
svoju jezgru
bpftool
(Za referencu, ovaj odjeljak i svi primjeri u članku pokrenuti su na Debianu 10.)
llvm/zveket
BPF je prijateljski nastrojen s LLVM-om i, iako se nedavno programi za BPF mogu kompajlirati pomoću gcc-a, sav trenutni razvoj se provodi za LLVM. Stoga ćemo prije svega izgraditi trenutnu verziju clang iz gita:
$ 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
... много времени спустя
$
Sada možemo provjeriti je li sve ispravno spojeno:
(Upute za sastavljanje clang uzeto od mene iz bpf_devel_QA.)
Nećemo instalirati programe koje smo upravo izradili, već ih samo dodati PATH, na primjer:
export PATH="`pwd`/bin:$PATH"
(Ovo se može dodati .bashrc ili u zasebnu datoteku. Osobno dodajem ovakve stvari u ~/bin/activate-llvm.sh a kad je potrebno to i činim . activate-llvm.sh.)
Pahole i BTF
Korisnost pahole koristi se prilikom izgradnje kernela za stvaranje informacija o ispravljanju pogrešaka u BTF formatu. U ovom članku nećemo ulaziti u detalje o pojedinostima BTF tehnologije, osim činjenice da je praktična i da je želimo koristiti. Dakle, ako ćete graditi svoj kernel, prvo napravite pahole (bez pahole nećete moći izgraditi kernel s opcijom 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
Kerneli za eksperimentiranje s BPF-om
Kada istražujem mogućnosti BPF-a, želim sastaviti vlastitu jezgru. Ovo, općenito govoreći, nije potrebno, budući da ćete moći kompajlirati i učitati BPF programe na distribucijskom kernelu, međutim, posjedovanje vlastitog kernela omogućuje vam korištenje najnovijih BPF značajki, koje će se pojaviti u vašoj distribuciji u najboljem slučaju za nekoliko mjeseci , ili, kao u slučaju nekih alata za otklanjanje pogrešaka, neće uopće biti pakirani u doglednoj budućnosti. Također, njegova vlastita jezgra čini da je važno eksperimentirati s kodom.
Za izgradnju kernela potrebna vam je, prvo, sama jezgra, a zatim konfiguracijska datoteka kernela. Za eksperimentiranje s BPF-om možemo koristiti uobičajeni vanilija kernel ili jedan od razvojnih kernela. Povijesno gledano, razvoj BPF-a odvija se unutar Linux mrežne zajednice i stoga sve promjene prije ili kasnije prolaze kroz Davida Millera, Linux mrežnog održavatelja. Ovisno o njihovoj prirodi - izmjene ili nove značajke - mrežne promjene spadaju u jednu od dvije jezgre - net ili net-next. Promjene za BPF raspodjeljuju se na isti način između bpf и bpf-next, koji se zatim objedinjuju u net i net-next. Za više detalja pogledajte bpf_devel_QA и netdev-FAQ. Dakle, odaberite kernel na temelju vašeg ukusa i potreba stabilnosti sustava na kojem testirate (*-next kerneli su najnestabilniji od navedenih).
Izvan je opsega ovog članka govoriti o tome kako upravljati konfiguracijskim datotekama jezgre - pretpostavlja se da ili već znate kako to učiniti, ili spreman za učenje na svome. Međutim, sljedeće upute trebale bi biti više-manje dovoljne da vam daju radni sustav s omogućenim BPF-om.
Preuzmite jedan od gore navedenih kernela:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next
Izgradite minimalnu radnu konfiguraciju jezgre:
$ cp /boot/config-`uname -r` .config
$ make localmodconfig
Omogući BPF opcije u datoteci .config po vlastitom izboru (najvjerojatnije CONFIG_BPF već će biti omogućen budući da ga koristi systemd). Ovdje je popis opcija iz kernela korištenih za ovaj članak:
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
Zatim možemo jednostavno sastaviti i instalirati module i kernel (usput, možete sastaviti kernel pomoću novosastavljenog clangdodavanjem CC=clang):
$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install
i ponovno pokrenite s novim kernelom (koristim za ovo kexec iz paketa 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
Najčešće korišteni uslužni program u članku bit će uslužni program bpftool, koji se isporučuje kao dio Linux kernela. Napisali su ga i održavaju BPF programeri za BPF programere i može se koristiti za upravljanje svim vrstama BPF objekata - učitavanje programa, stvaranje i uređivanje karata, istraživanje života BPF ekosustava itd. Može se pronaći dokumentacija u obliku izvornih kodova za stranice priručnika u jezgri ili već sastavljeno, na mreži.
U vrijeme pisanja ovog teksta bpftool dolazi spreman samo za RHEL, Fedora i Ubuntu (pogledajte, na primjer, ova nit, koji priča nedovršenu priču o pakiranju bpftool u Debianu). Ali ako ste već izgradili svoj kernel, onda izgradite bpftool lako kao pita:
$ 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 ]
$
(ovdje ${linux} - ovo je vaš kernel direktorij.) Nakon izvršavanja ovih naredbi bpftool bit će prikupljeni u imeniku ${linux}/tools/bpf/bpftool i može se dodati putanji (prije svega korisniku root) ili samo kopirajte na /usr/local/sbin.
Skupljati bpftool najbolje je koristiti ovo drugo clang, sastavljen na gore opisani način, te provjerite je li pravilno sastavljen - pomoću npr. naredbe
$ 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
...
koji će pokazati koje su BPF značajke omogućene u vašem kernelu.
Usput, prethodna naredba može se pokrenuti kao
# bpftool f p k
To se radi po analogiji s uslužnim programima iz paketa iproute2, gdje možemo npr. reći ip a s eth0 umjesto ip addr show dev eth0.
Zaključak
BPF vam omogućuje da potkujete buhu kako biste učinkovito izmjerili i promijenili funkcionalnost jezgre u hodu. Sustav se pokazao vrlo uspješnim, u najboljim tradicijama UNIX-a: jednostavan mehanizam koji vam omogućuje (re)programiranje kernela omogućio je velikom broju ljudi i organizacija da eksperimentiraju. I, iako su eksperimenti, kao i razvoj same BPF infrastrukture, daleko od završetka, sustav već ima stabilan ABI koji vam omogućuje izgradnju pouzdane i, što je najvažnije, učinkovite poslovne logike.
Želio bih napomenuti da je, po mom mišljenju, tehnologija postala toliko popularna jer, s jedne strane, može za reprodukciju (arhitektura stroja može se razumjeti više-manje u jednoj večeri), a s druge strane, riješiti probleme koji se nisu mogli (lijepo) riješiti prije njegove pojave. Ove dvije komponente zajedno tjeraju ljude na eksperimentiranje i sanjarenje, što dovodi do pojave sve više i više inovativnih rješenja.
Ovaj članak, iako nije osobito kratak, samo je uvod u svijet BPF-a i ne opisuje "napredne" značajke i važne dijelove arhitekture. Plan za dalje je otprilike ovakav: sljedeći članak će biti pregled tipova BPF programa (postoji 5.8 tipova programa podržanih u kernelu 30), zatim ćemo konačno pogledati kako napisati prave BPF aplikacije koristeći programe za praćenje kernela kao primjer, onda je vrijeme za dublji tečaj o BPF arhitekturi, nakon čega slijede primjeri BPF umrežavanja i sigurnosnih aplikacija.
Referentni vodič za BPF i XDP — dokumentacija o BPF-u od ciliuma, točnije od Daniela Borkmana, jednog od kreatora i održavatelja BPF-a. Ovo je jedan od prvih ozbiljnijih opisa, koji se od ostalih razlikuje po tome što Danijel točno zna o čemu piše i tu nema grešaka. Konkretno, ovaj dokument opisuje kako raditi s BPF programima tipa XDP i TC pomoću dobro poznatog uslužnog programa ip iz paketa iproute2.
Dokumentacija/umrežavanje/filter.txt — izvorna datoteka s dokumentacijom za klasični, a potom prošireni BPF. Dobro štivo ako želite proniknuti u asemblerski jezik i tehničke detalje arhitekture.
Blog o BPF-u s Facebooka. Ažurira se rijetko, ali prikladno, kao što tamo pišu Alexei Starovoitov (autor eBPF-a) i Andrii Nakryiko - (održavatelj) libbpf).
Tajne bpftoola. Zabavna twitter nit Quentina Monneta s primjerima i tajnama korištenja bpftoola.