BPF per i picculi, prima parte: BPF allargatu

In u principiu ci era una tecnulugia è si chjamava BPF. L'avemu guardatu precedente, Vecchiu Testamentu, articulu in questa seria. In u 2013, grazia à l'sforzi di Alexei Starovoitov è Daniel Borkman, hè statu sviluppatu è inclusu in u core Linux Una versione migliurata ottimizzata per e macchine muderne à 64 bit. Sta nova tecnulugia hè stata chjamata brevemente BPF Internal, poi ribattezzata BPF Extended, è avà, parechji anni dopu, tutti a chjamanu semplicemente BPF.

In fondu, BPF permette à u codice arbitrariu furnitu da l'utente di eseguisce in u spaziu di u kernel. Linux È a nova architettura hè stata cusì riesciuta chì ci vuleria una decina d'articuli per discrive tutte e so applicazioni. (L'unica cosa chì i sviluppatori ùn anu micca fattu, cum'è si pò vede in u graficu di efficienza quì sottu, hè stata di creà un logò decente.)

Questu articulu descrive a struttura di a macchina virtuale BPF, l'interfacce di u kernel per travaglià cù BPF, i strumenti di sviluppu, è ancu una breve, assai brevi panoramica di e capacità esistenti, i.e. tuttu ciò chì avemu bisognu in u futuru per un studiu più profundo di l'applicazioni pratiche di BPF.
BPF per i picculi, prima parte: BPF allargatu

Riassuntu di l'articulu

Introduzione à l'architettura BPF. Prima, avemu da piglià una vista d'uccello di l'architettura BPF è descrive i cumpunenti principali.

Registri è sistema di cumandamentu di a macchina virtuale BPF. Avendu digià una idea di l'architettura cum'è un sanu, descriveremu a struttura di a macchina virtuale BPF.

Ciclu di vita di l'uggetti BPF, sistema di file bpffs. In questa sezione, avemu da piglià un ochju più vicinu à u ciclu di vita di l'uggetti BPF - prugrammi è carte.

Gestisce l'uggetti utilizendu a chjama di u sistema bpf. Cù una certa comprensione di u sistema digià in u locu, infine guardemu cumu creà è manipulà l'uggetti da u spaziu di l'utilizatori utilizendu una chjamata di sistema speciale - bpf(2).

Пишем программы BPF с помощью libbpf. Di sicuru, pudete scrive prugrammi cù una chjama di sistema. Ma hè difficiule. Per un scenariu più realistu, i programatori nucleari anu sviluppatu una biblioteca libbpf. Creemu un scheletru di applicazione BPF di basa chì useremu in l'esempii successivi.

Aiutanti di u kernel. Quì ampararemu cumu i prugrammi BPF ponu accede à e funzioni di l'aiutu di u kernel - un strumentu chì, cù e carte, allarga fundamentalmente e capacità di u novu BPF cumparatu cù u classicu.

Accessu à e carte da i prugrammi BPF. À questu puntu, sapemu abbastanza per capisce esattamente cumu pudemu creà prugrammi chì utilizanu carte. È andemu ancu à piglià un ochju rapidu in u grande è putente verificatore.

Strumenti di sviluppu. Sezione d'aiutu nantu à cumu assemble l'utilità è u kernel necessarii per l'esperimenti.

A cunclusione. À a fine di l'articulu, quelli chì leghjenu finu à quì truveranu parolle motivanti è una breve descrizzione di ciò chì succede in l'articuli seguenti. Avemu ancu liste una quantità di ligami per l'autostudiu per quelli chì ùn anu micca u desideriu o a capacità d'aspittà per a continuazione.

Introduzione à l'architettura BPF

Prima di principià à cunsiderà l'architettura BPF, avemu da riferite una ultima volta (oh). BPF classicu, chì hè statu sviluppatu cum'è una risposta à l'avventu di e macchine RISC è risolviu u prublema di filtrazione di pacchetti efficiente. L'architettura hè stata cusì riescita chì, essendu nata in l'anni novanta in Berkeley UNIX, hè stata portata à a maiò parte di i sistemi operativi esistenti, sopravvive à l'anni 20 è sempre trova novi applicazioni.

U novu BPF hè statu sviluppatu cum'è una risposta à l'ubiquità di e macchine 64-bit, i servizii di nuvola è l'aumentu bisognu di strumenti per creà SDN (Sfurtuna-dfinitu nrete). Sviluppatu da l'ingegneri di a rete principale cum'è un rimpiazzamentu miglioratu per u BPF classicu, u novu BPF hà trovu applicazione in u difficiule compitu di tracciamentu solu sei mesi dopu. Linux sistemi, è avà, sei anni dopu à a so apparizione, averiamu bisognu di un articulu sanu novu solu per elencà i sfarenti tipi di prugrammi.

Foto divertenti

In u so core, BPF hè una macchina virtuale sandbox chì vi permette di eseguisce codice "arbitrariu" in u spaziu kernel senza compromette a sicurità. I prugrammi BPF sò creati in u spaziu di l'utilizatori, caricati in u kernel, è cunnessi à qualchì fonte di l'avvenimentu. Un avvenimentu puderia esse, per esempiu, a consegna di un pacchettu à una interfaccia di rete, u lanciamentu di qualchì funzione di kernel, etc. In u casu di un pacchettu, u prugramma BPF averà accessu à i dati è i metadati di u pacchettu (per leghje è, possibbilmente, scrive, secondu u tipu di prugramma in u casu di eseguisce una funzione di kernel, l'argumenti di). a funzione, cumpresi puntatori à a memoria di u kernel, etc.

Fighjemu un ochju più vicinu à stu prucessu. Per principià, parlemu di a prima diferenza da u BPF classicu, i prugrammi per quale sò stati scritti in assembler. In a nova versione, l'architettura hè stata allargata in modu chì i prugrammi puderanu esse scritti in lingue d'altu livellu, primarmenti, sicuru, in C. Per questu, hè statu sviluppatu un backend per llvm, chì permette di generà bytecode per l'architettura BPF.

BPF per i picculi, prima parte: BPF allargatu

L'architettura BPF hè stata cuncepita, in parte, per eseguisce in modu efficiente nantu à e macchine muderne. Per fà stu travagliu in pratica, u bytecode BPF, una volta caricatu in u kernel, hè traduttu in codice nativu utilizendu un cumpunente chjamatu compilatore JIT (JUst In Ttempu). Dopu, se vi ricordate, in u BPF classicu u prugramma hè stata caricata in u kernel è attaccata à a fonte di l'avvenimentu atomicamente - in u cuntestu di una sola chjama di u sistema. In a nova architettura, questu succede in duie tappe - prima, u codice hè caricatu in u kernel cù una chjama di sistema. bpf(2)è dopu, più tardi, attraversu altri miccanismi chì varienu sicondu u tipu di prugramma, u prugramma attache à a fonte di l'avvenimentu.

Quì u lettore pò avè una quistione: era pussibule? Cumu hè garantita a sicurità di l'esekzione di tali codice? A sicurità di l'esecuzione hè garantita per noi da u stadiu di carica di prugrammi BPF chjamati verificatore (in inglese sta tappa hè chjamata verificatore è continueraghju à aduprà a parolla inglese):

BPF per i picculi, prima parte: BPF allargatu

Verifier hè un analizzatore staticu chì assicura chì un prugramma ùn disturba micca u funziunamentu normale di u kernel. Questu, per via, ùn significa micca chì u prugramma ùn pò micca interferiscenu cù u funziunamentu di u sistema - i prugrammi BPF, secondu u tipu, ponu leghje è riscrive e sezioni di memoria di u kernel, rinvià i valori di funzioni, trim, append, rewrite. è ancu invià i pacchetti di rete. Verifier guarantisci chì eseguisce un prugramma BPF ùn falla micca u kernel è chì un prugramma chì, secondu e regule, hà accessu à scrittura, per esempiu, i dati di un pacchettu in uscita, ùn serà micca capaci di sovrascrive a memoria di u kernel fora di u pacchettu. Fighjemu u verificatore in un pocu più di dettu in a sezione currispundente, dopu avè cunnisciutu tutti l'altri cumpunenti di BPF.

Allora chì avemu amparatu finu à avà? L'utilizatore scrive un prugramma in C, carica in u kernel cù una chjama di sistema bpf(2), induve hè verificatu da un verificatore è traduttu in bytecode nativu. Allora u stessu o un altru utilizatore cunnetta u prugramma à a fonte di l'avvenimentu è cumencia à eseguisce. Separate boot è cunnessione hè necessariu per parechje ragioni. Prima, l'esecuzione di un verificatore hè relativamente caru è scarichendu u stessu prugramma parechje volte perdemu u tempu di l'urdinatore. Siconda, esattamente cumu un prugramma hè cunnessu dipende da u so tipu, è una interfaccia "universale" sviluppata un annu fà pò esse micca adattata per novi tipi di prugrammi. (Ancu se avà chì l'architettura diventa più matura, ci hè una idea di unificà sta interfaccia à u livellu libbpf.)

U lettore attentu pò nutà chì ùn avemu micca finitu cù l'imaghjini. In verità, tuttu ciò chì sopra ùn spiega micca perchè BPF cambia fundamentalmente l'imagine cumparatu cù BPF classic. Dui innovazioni chì allarganu significativamente u scopu di l'applicabilità sò l'abilità di utilizà a memoria sparta è e funzioni di aiutu di u kernel. In BPF, a memoria sparta hè implementata cù e mape chjamate - strutture di dati spartuti cù una API specifica. Probabilmente anu avutu stu nome perchè u primu tipu di mappa chì apparisce era una tavola hash. Allora apparsu array, tabelle di hash locale (per CPU) è array lucali, arburi di ricerca, carte chì cuntenenu punters à i prugrammi BPF è assai di più. Ciò chì hè interessante per noi avà hè chì i prugrammi BPF anu avà a capacità di persiste u statu trà e chjama è di sparte cù altri prugrammi è cù u spaziu di l'utilizatori.

Maps hè accessu da i prucessi di l'utilizatori cù una chjama di sistema bpf(2), è da i prugrammi BPF in esecuzione in u kernel chì utilizanu funzioni d'aiutu. Inoltre, l'aiutanti esistenu micca solu per travaglià cù carte, ma ancu per accede à altre capacità di u kernel. Per esempiu, i prugrammi BPF ponu utilizà funzioni d'aiutu per trasmette pacchetti à altre interfacce, generà avvenimenti perf, accede à strutture di kernel, è cusì.

BPF per i picculi, prima parte: BPF allargatu

In riassuntu, BPF furnisce a capacità di carricà arbitrariu, vale à dì, verificatu-testatu, codice d'utilizatore in u spaziu di u kernel. Stu codice pò salvà u statu trà e chjama è scambià dati cù u spaziu di l'utilizatori, è hà ancu accessu à i sottosistemi di kernel permessi da stu tipu di prugramma.

Questu hè digià simile à e capacità furnite da i moduli di u kernel, cumparatu cù quale BPF hà qualchì vantaghju (di sicuru, pudete solu paragunà applicazioni simili, per esempiu, traccia di sistema - ùn pudete micca scrive un driver arbitrariu cù BPF). Pudete nutà un sogliu di entrata più bassu (alcune utilità chì utilizanu BPF ùn esigenu micca chì l'utilizatore hà capacità di prugrammazione di kernel, o cumpetenze di prugrammazione in generale), sicurezza di runtime (alza a manu in i cumenti per quelli chì ùn anu micca rottu u sistema quandu scrivite). o moduli di teste), l'atomicità - ci hè un downtime quandu si ricaricà i moduli, è u subsistema BPF assicura chì ùn mancanu avvenimenti (per esse ghjustu, questu ùn hè micca veru per tutti i tipi di prugrammi BPF).

A prisenza di tali capacità rende BPF un strumentu universale per espansione u kernel, chì hè cunfirmatu in a pratica: più è più novi tipi di prugrammi sò aghjuntu à BPF, più è più grande cumpagnie utilizanu BPF nantu à i servitori di cumbattimentu 24 × 7, più è più. startups custruiscenu a so attività nantu à e soluzioni basate nantu à quale sò basati in BPF. BPF hè utilizatu in ogni locu: in a prutezzione di l'attacchi DDoS, a creazione di SDN (per esempiu, l'implementazione di rete per kubernetes), cum'è u principale strumentu di traccia di u sistema è u cullettore di statistiche, in sistemi di rilevazione di intrusioni è sistemi sandbox, etc.

Finitemu a parte generale di l'articulu quì è fighjemu a macchina virtuale è l'ecosistema BPF in più detail.

Digressione : utilità

Per esse in gradu di eseguisce l'esempii in e sezioni seguenti, pudete bisognu di una quantità di utilità, almenu llvm/clang cù supportu bpf è bpftool. In a sezione Strumenti di sviluppu Pudete leghje l'istruzzioni per assemblà l'utilità, è ancu u vostru kernel. Questa sezione hè posta sottu per ùn disturbà l'armunia di a nostra presentazione.

BPF Virtual Machine Registers and Instruction System

L'architettura è u sistema di cumandamentu di BPF sò stati sviluppati in cunsiderà u fattu chì i prugrammi seranu scritti in lingua C è, dopu a carica in u kernel, traduttu in codice nativu. Per quessa, u numeru di registri è l'inseme di cumandamenti sò stati scelti cù un ochju à l'intersezzione, in u sensu matematicu, di e capacità di i machini muderni. Inoltre, diverse restrizioni sò state imposte à i prugrammi, per esempiu, finu à pocu tempu ùn era micca pussibule di scrive loops è subrutines, è u numeru di struzzioni era limitatu à 4096 (oghji i prugrammi privilegiati ponu carricà finu à un milione di instructions).

BPF hà undici registri 64-bit accessibile da l'utilizatori r0-r10 è un contatore di prugramma. Registrate r10 cuntene un puntatore di frame è hè di sola lettura. I prugrammi anu accessu à una pila di 512 byte in runtime è una quantità illimitata di memoria sparta in forma di carte.

I prugrammi BPF sò permessi di eseguisce un settore specificu di assistenti di u kernel di u prugramma è, più recentemente, funzioni regularmente. Ogni funzione chjamata pò piglià sin'à cinque argumenti, passati in registri r1-r5, è u valore di ritornu hè passatu à r0. Hè garantitu chì dopu à vultà da a funzione, u cuntenutu di i registri r6-r9 Ùn cambierà micca.

Per una traduzzione efficace di u prugramma, i registri r0-r11 per tutte l'architetture supportate sò mappati unicu à i registri veri, tenendu in contu e caratteristiche ABI di l'architettura attuale. Per esempiu, per x86_64 registri r1-r5, usati per passà i paràmetri di a funzione, sò visualizati rdi, rsi, rdx, rcx, r8, chì sò usati per passà i paràmetri à e funzioni x86_64. Per esempiu, u codice à manca si traduce in u codice à a diritta cusì:

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

Iscrizzione r0 ancu usatu per rinvià u risultatu di l'esekzione di u prugramma, è in u registru r1 u prugramma hè passatu un puntatore à u cuntestu - secondu u tipu di prugramma, questu puderia esse, per esempiu, una struttura struct xdp_md (per XDP) o struttura struct __sk_buff (per diversi prugrammi di rete) o struttura struct pt_regs (per diversi tipi di prugrammi di traccia), etc.

Dunque, avemu avutu un inseme di registri, assistenti di kernel, una pila, un punteru di cuntestu è memoria sparta in forma di carte. Ùn hè micca chì tuttu questu hè assolutamente necessariu in u viaghju, ma ...

Continuemu a descrizzione è parlemu di u sistema di cumandamentu per travaglià cù questi ogetti. tutti (Quasi tutti) L'istruzzioni BPF anu una dimensione fissa di 64 bit. Se guardate una struzzione nantu à una macchina Big Endian 64-bit vi vede

BPF per i picculi, prima parte: BPF allargatu

Code - questu hè a codificazione di l'istruzzioni, Dst/Src sò i codificazioni di u receptore è di a fonte, rispettivamente, Off - Indentazione firmata a 16 bit, è Imm hè un integer firmatu di 32 bit utilizatu in certi struzzioni (simile à a constante cBPF K). Encoding Code hà unu di dui tipi:

BPF per i picculi, prima parte: BPF allargatu

E classi d'istruzione 0, 1, 2, 3 definiscenu cumandamenti per travaglià cù a memoria. Iddi sò chjamati, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, rispettivamente. Classi 4, 7 (BPF_ALU, BPF_ALU64) costituiscono un insieme di istruzioni ALU. Classi 5, 6 (BPF_JMP, BPF_JMP32) cuntenenu struzzioni di salto.

U pianu ulteriore per studià u sistema di struzzioni BPF hè u seguitu: invece di elencu meticulosamente tutte l'istruzzioni è i so paràmetri, guardemu un paru di esempi in questa sezione è da elli diventerà chjaru cumu funziona l'istruzzioni in realtà è cumu si disassemble manualmente qualsiasi file binariu per BPF. Per cunsulidà u materiale più tardi in l'articulu, avemu ancu scuntrà cù struzzioni individuali in e rùbbriche circa Verifier, compilatore JIT, traduzzione di BPF classicu, è ancu quandu studia carte, funzioni di chjama, etc.

Quandu avemu parlatu di struzzioni individuali, avemu da riferite à i schedari core bpf.h и bpf_common.h, chì definiscenu i codici numerichi di l'istruzzioni BPF. Quandu studia l'architettura nantu à u vostru propiu è / o parsing binari, pudete truvà semantica in i seguenti fonti, ordinati in ordine di cumplessità: Specifica eBPF non ufficiale, Guida di Riferimentu BPF è XDP, Instruction Set, Documentazione/networking/filter.txt è, benintesa, in i codici surghjenti Linux — verificatore, JIT, interprete BPF.

Esempiu: disassemble BPF in a vostra testa

Fighjemu un esempiu in quale compilemu un prugramma readelf-example.c è fighjate à u binariu risultatu. Revelemu u cuntenutu uriginale readelf-example.c quì sottu, dopu avè restauratu a so logica da i codici binari:

$ 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 ................

Prima colonna in output readelf hè un indentazione è u nostru prugramma hè cusì custituitu da quattru cumandamenti:

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

I codici di cumanda sò uguali b7, 15, b7 и 95. Ricurdativi chì i trè bits menu significati sò a classa d'istruzzioni. In u nostru casu, u quartu bit di tutte l'istruzzioni hè viotu, cusì e classi d'istruzzioni sò 7, 5, 7, 5, rispettivamente BPF_ALU64, è 5 hè BPF_JMP. Per e duie classi, u formatu d'istruzzioni hè u listessu (vede sopra) è pudemu riscrive u nostru prugramma cusì (in u stessu tempu riscriveremu e culonne rimanenti in forma umana):

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

Operazione b класса ALU64 Is BPF_MOV. Assigna un valore à u registru di destinazione. Se u bit hè stabilitu s (fonte), allura u valore hè pigliatu da u registru fonte, è se, cum'è in u nostru casu, ùn hè micca stabilitu, allura u valore hè pigliatu da u campu. Imm. Allora in a prima è a terza istruzzioni facemu l'operazione r0 = Imm. Inoltre, l'operazione JMP class 1 hè BPF_JEQ (saltà se uguale). In u nostru casu, dapoi u bit S hè zero, compara u valore di u registru fonte cù u campu Imm. Se i valori coincidenu, allora a transizione si faci PC + Offinduve PC, cum'è solitu, cuntene l'indirizzu di l'istruzzioni dopu. Infine, JMP Class 9 Operation hè BPF_EXIT. Questa struzzione termina u prugramma, torna à u kernel r0. Aghjunghjemu una nova colonna à a nostra tavula:

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

Pudemu riscrive questu in una forma più còmuda:

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

Se ricurdate ciò chì hè in u registru r1 u prugramma hè passatu un puntatore à u cuntestu da u kernel, è in u registru r0 u valore hè tornatu à u kernel, allora pudemu vede chì se l'indicatore à u cuntestu hè zero, allora vultemu 1, è altrimente - 2. Cuntrollamu chì avemu ragiò fighjendu a fonte:

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

Iè, hè un prugramma senza significatu, ma si traduce in quattru struzzioni simplici.

Esempiu d'eccezzioni: istruzzioni di 16 byte

Avemu menzionatu prima chì alcune struzzioni piglianu più di 64 bits. Questu hè applicatu, per esempiu, à l'istruzzioni lddw (Codice = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - carica una doppia parolla da i campi in u registru Imm. U fattu hè chì Imm hà una dimensione di 32, è una doppia parolla hè 64 bits, cusì carica un valore immediatu di 64 bit in un registru in una struzzione di 64 bit ùn funziona micca. Per fà questu, dui struzzioni adiacenti sò usati per almacenà a seconda parte di u valore 64-bit in u campu Imm. Un esempiu:

$ 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                   ........

Ci hè solu dui struzzioni in un prugramma binariu:

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

Ci scuntremu di novu cù istruzzioni lddw, quandu parlemu di trasferimenti è travagliendu cù carte.

Esempiu: smontà BPF cù arnesi standard

Dunque, avemu amparatu à leghje i codici binari BPF è sò pronti à analizà ogni struzzione se ne necessariu. Tuttavia, vale a pena dì chì in a pratica hè più còmuda è più veloce di disassemble i prugrammi cù l'arnesi standard, per esempiu:

$ llvm-objdump -d x64.o

Disassembly of section .text:

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

Ciclu di vita di l'uggetti BPF, sistema di fugliale bpffs

(Aghju prima amparatu alcuni di i dettagli descritti in questa sottosezione da postu Alexei Starovoitov in Blog BPF.)

L'oggetti BPF - prugrammi è carte - sò creati da u spaziu di l'utilizatori cù cumandamenti BPF_PROG_LOAD и BPF_MAP_CREATE chjama di sistema bpf(2), Parleremu esattamente cumu questu succede in a sezione dopu. Questu crea strutture di dati di u kernel è per ognunu di elli refcount (conte di riferimentu) hè stabilitu à unu, è un descrittore di file chì punta à l'ughjettu hè tornatu à l'utilizatore. Dopu chì u manicu hè chjusu refcount l'ughjettu hè ridutta da unu, è quandu ghjunghje à zero, l'ughjettu hè distruttu.

Se u prugramma usa carte, allora refcount sti carte sò aumentati da unu dopu à carica u prugramma, i.e. i so descriptori di schedari ponu esse chjusi da u prucessu di l'utilizatori è sempre refcount ùn diventerà micca zero:

BPF per i picculi, prima parte: BPF allargatu

Dopu avè caricatu cù successu un prugramma, di solitu l'attachemu à un tipu di generatore di eventi. Per esempiu, pudemu mette nantu à una interfaccia di rete per processà i pacchetti entranti o cunnette à alcuni tracepoint in u core. À questu puntu, u contatore di riferimentu cresce ancu da unu è pudemu chjude u descriptore di u schedariu in u prugramma di caricatore.

Chì succede si avà chjude u bootloader? Dipende da u tipu di generatore di eventi (ganciu). Tutti i ganci di a rete esisteranu dopu chì u caricatore cumpleta, questi sò i cosi-chiamati ganci globali. E, per esempiu, i prugrammi di traccia seranu liberati dopu chì u prucessu chì li hà creatu finisci (è per quessa sò chjamati lucali, da "lucale à u prucessu"). Tecnicamente, i ganci lucali anu sempre un descrittore di file currispondente in u spaziu di l'utilizatori è per quessa chjode quandu u prucessu hè chjusu, ma i ganci glubale ùn anu micca. In a figura seguente, usendu cruci rossi, pruvate di vede cumu a terminazione di u prugramma di caricatore afecta a vita di l'uggetti in u casu di ganci lucali è glubale.

BPF per i picculi, prima parte: BPF allargatu

Perchè ci hè una distinzione trà i ganci lucali è glubale? L'esecuzione di certi tipi di prugrammi di rete hè sensu senza un spaziu d'utilizatore, per esempiu, imagine a prutezzione DDoS - u bootloader scrive e regule è cunnetta u prugramma BPF à l'interfaccia di a rete, dopu chì u bootloader pò andà è tumbà. Per d 'altra banda, imaginate un prugramma di traccia di debugging chì avete scrittu annantu à i vostri ghjinochje in deci minuti - quandu hè finitu, vi piacerebbe chì ùn ci sia micca basura in u sistema, è i ganci lucali vi assicurà chì.

Per d 'altra banda, imaginate chì vulete cunnette à un tracepoint in u kernel è raccoglie statistiche annantu à parechji anni. In questu casu, vulete compie a parte di l'utilizatori è torna à e statistiche da u tempu à u tempu. U sistema di fugliale bpf furnisce questa opportunità. Hè un sistema di pseudo-file solu in memoria chì permette a creazione di fugliali chì riferite l'uggetti BPF è cusì aumentanu. refcount ogetti. Dopu questu, u caricatore pò esce, è l'uggetti chì hà creatu restanu vivu.

BPF per i picculi, prima parte: BPF allargatu

A creazione di fugliali in bpffs chì riferimentu à l'uggetti BPF hè chjamatu "pinning" (cum'è in a frasa seguente: "prucessu pò pin un prugramma BPF o mappa"). A creazione di l'uggetti di u schedariu per l'uggetti BPF hè sensu micca solu per allargà a vita di l'uggetti lucali, ma ancu per l'usabilità di l'uggetti glubale - vultendu à l'esempiu cù u prugramma di prutezzione globale DDoS, vulemu esse capace di vene à fighjà statistiche. di tantu in tantu.

U sistema di schedari BPF hè generalmente muntatu in /sys/fs/bpf, ma pò ancu esse muntatu in u locu, per esempiu, cusì:

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

I nomi di u sistema di schedari sò creati cù u cumandimu BPF_OBJ_PIN Chjama di sistema BPF. Per illustrà, pigliemu un prugramma, compilelu, carichemu, è pinlemu bpffs. U nostru prugramma ùn face nunda d'utile, avemu solu presentà u codice per pudè ripruduce l'esempiu:

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

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

Cumpilemu stu prugramma è creanu una copia lucale di u sistema di schedari bpffs:

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

Avà andemu à scaricà u nostru prugramma cù l'utilità bpftool è fighjate à e chjama di u sistema accumpagnatu bpf(2) (alcune linee irrilevanti eliminate da l'output strace):

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

Quì avemu caricatu u prugrammu usendu BPF_PROG_LOAD, hà ricevutu un descrittore di u schedariu da u kernel 3 è usendu u cumandamentu BPF_OBJ_PIN pinned this file descriptor as a file "bpf-mountpoint/test". Dopu questu, u prugramma bootloader bpftool hà finitu di travaglià, ma u nostru prugramma hè stata in u kernel, ancu s'ellu ùn l'avete micca attaccatu à alcuna interfaccia di rete:

$ 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

Pudemu sguassà l'ughjettu di u schedariu normalment unlink(2) è dopu chì u prugramma currispondente serà sguassatu:

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

Sguassà l'uggetti

Parlendu di sguassà l'uggetti, hè necessariu di chjarificà chì dopu chì avemu disconnected u prugramma da u ganciu (generatore di l'eventi), micca un novu avvenimentu ùn attivarà u so lanciamentu, in ogni modu, tutte e istanze attuali di u prugramma saranu cumplette in l'ordine normale. .

Certi tipi di prugrammi BPF permettenu di rimpiazzà u prugramma nantu à a mosca, i.e. furnisce l'atomicità di sequenza replace = detach old program, attach new program. In questu casu, tutti i casi attivi di a versione antica di u prugramma finiscinu u so travagliu, è i novi gestori di l'avvenimenti seranu creati da u novu prugramma, è "atomicità" quì significa chì ùn manca micca un avvenimentu unicu.

Attaccà i prugrammi à e fonti di l'avvenimenti

In questu articulu, ùn descriveremu micca separatamente i prugrammi di cunnessione à e fonti di l'avvenimenti, postu chì hè sensu di studià questu in u cuntestu di un tipu specificu di prugramma. Cm. esempiu sottu, in quale mostramu cumu i prugrammi cum'è XDP sò cunnessi.

Manipulendu l'uggetti Utilizendu a Chjama di Sistema bpf

prugrammi BPF

Tutti l'uggetti BPF sò creati è gestiti da u spaziu di l'utilizatori cù una chjama di sistema bpf, avè u prototipu seguente:

#include <linux/bpf.h>

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

Eccu a squadra cmd hè unu di i valori di tipu enum bpf_cmd, attr - un punteru à i paràmetri per un prugramma specificu è size - dimensione di l'ughjettu secondu u puntatore, i.e. di solitu questu sizeof(*attr). In u kernel 5.8 a chjama di u sistema bpf supporta 34 cumandamenti diffirenti, è definizione union bpf_attr occupa 200 linee. Ma ùn duvemu micca esse intimidatu da questu, postu chì avemu da esse familiarizatu cù i cumandamenti è i paràmetri in u cursu di parechji articuli.

Cuminciamu cù a squadra BPF_PROG_LOAD, chì crea prugrammi BPF - piglia un set di struzzioni BPF è u carica in u kernel. À u mumentu di a carica, u verificatore hè lanciatu, è dopu u compilatore JIT è, dopu à l'esekzione successu, u descriptore di u schedariu di u prugramma hè tornatu à l'utilizatore. Avemu vistu ciò chì succede à ellu dopu in a sezione precedente circa u ciclu di vita di l'uggetti BPF.

Scrivemu avà un prugramma persunalizatu chì caricarà un prugramma BPF simplice, ma prima avemu bisognu di decide chì tipu di prugramma vulemu carcà - avemu da selezziunà. Type è in u quadru di stu tipu, scrivite un prugramma chì passà a prova di verificatore. Tuttavia, in ordine micca à cumplicà u prucessu, quì hè una suluzione ready-made: avemu da piglià un prugrammu cum'è BPF_PROG_TYPE_XDP, chì restituverà u valore XDP_PASS (saltà tutti i pacchetti). In l'assembler BPF pare assai simplice:

r0 = 2
exit

Dopu avemu decisu chì caricheremu, pudemu dì cumu a faremu:

#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();
}

Avvenimenti interessanti in un prugramma cumincianu cù a definizione di un array insns - u nostru prugramma BPF in codice machine. In questu casu, ogni struzzione di u prugramma BPF hè imballata in a struttura bpf_insn. Primu elementu insns rispetta l'istruzzioni r0 = 2, u sicondu - exit.

Ritirata. U kernel definisce macros più convenienti per scrive codici di macchina, è aduprà u file header di u kernel tools/include/linux/filter.h pudemu scrive

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

Ma postu chì scrive prugrammi BPF in codice nativu hè solu necessariu per scrive testi in u kernel è articuli nantu à BPF, l'absenza di sti macros ùn hè micca veramente complicatu a vita di u sviluppatore.

Dopu avè definitu u prugramma BPF, andemu à caricallu in u kernel. U nostru settore minimalista di parametri attr include u tipu di prugramma, u settore è u numeru di struzzioni, a licenza necessaria è u nome "woo", chì avemu aduprà à truvà u nostru prugrammu nant'à u sistema dopu à scaricà. U prugramma, cum'è prumessu, hè carricu in u sistema cù una chjama di sistema bpf.

À a fine di u prugramma avemu finitu in un ciclu infinitu chì simula a carica. Senza ellu, u prugramma serà uccisu da u kernel quandu u descrittore di u schedariu chì a chjama di u sistema ci hà tornatu hè chjusu. bpf, è ùn avemu micca vistu in u sistema.

Ebbè, simu pronti per a prova. Assemblemu è eseguisce u prugramma sottu straceper verificà chì tuttu funziona cum'è deve:

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

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

tuttu va bè, bpf(2) hà riturnatu u manicu 3 à noi è andemu in un ciclu infinitu cù pause(). Pruvemu di truvà u nostru prugramma in u sistema. Per fà questu, andemu à un altru terminal è aduprà l'utilità 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)

Avemu vistu chì ci hè un prugrammu carricu nant'à u sistema woo u so ID globale hè 390 è hè attualmente in corso simple-prog ci hè un descrittore di u schedariu apertu chì punta à u prugramma (è se simple-prog finirà u travagliu, allora woo sparirà). Comu aspittatu, u prugramma woo pigghia 16 bytes - dui instructions - di codici binari in l'architettura BPF, ma in a so forma nativa (x86_64) hè digià 40 bytes. Fighjemu u nostru prugramma in a so forma originale:

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

senza sorprese. Avà fighjemu u codice generatu da u compilatore JIT:

# 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

micca assai efficace per exit(2), Ma in ghjustizia, u nostru prugramma hè troppu simplice, è per i prugrammi micca triviali, u prulogo è l'epilogo aghjuntu da u compilatore JIT sò, sicuru, necessariu.

Maps

I prugrammi BPF ponu utilizà zoni di memoria strutturata chì sò accessibili à l'altri prugrammi BPF è à i prugrammi in u spaziu di l'utilizatori. Questi ogetti sò chjamati mape è in questa sezione vi mustraremu cumu manipulà cù una chjama di sistema bpf.

Diciamu subitu chì e capacità di e carte ùn sò micca limitate solu à l'accessu à a memoria sparta. Ci sò carte speciali chì cuntenenu, per esempiu, punters à prugrammi BPF o punters à interfacce di rete, carte per travaglià cù avvenimenti perf, etc. Ùn ne parlemu micca quì, per ùn cunfundà u lettore. In più di questu, ignoremu i prublemi di sincronizazione, postu chì questu ùn hè micca impurtante per i nostri esempi. Una lista cumpleta di i tipi di carte dispunibuli pò esse truvata in <linux/bpf.h>, è in questa rùbbrica avemu da piglià cum'è un esempiu u primu tipu storicamente, a table hash BPF_MAP_TYPE_HASH.

Se crea una tavola hash in, dì, C ++, dite unordered_map<int,long> woo, chì in russo significa "Aghju bisognu di una tavola woo taglia illimitata, chì e chjave sò di tipu int, è i valori sò u tipu long" Per creà una tavola hash BPF, avemu bisognu di fà assai a listessa cosa, salvu chì avemu da specificà a dimensione massima di a tavola, è invece di specificà i tipi di chjave è i valori, avemu bisognu di specificà e so dimensioni in byte. . Per creà mape utilizate u cumandamentu BPF_MAP_CREATE chjama di sistema bpf. Fighjemu un prugramma più o menu minimu chì crea una mappa. Dopu à u prugramma precedente chì carica i prugrammi BPF, questu vi pare simplice:

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

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

    for ( ;; )
        pause();
}

Quì avemu definitu un set di paràmetri attr, in quale dicemu "Aghju bisognu di una tavola hash cù chjavi è valori di dimensione sizeof(int), in quale possu mette un massimu di quattru elementi ". Quandu creanu carte BPF, pudete specificà altri paràmetri, per esempiu, in u listessu modu cum'è in l'esempiu cù u prugramma, avemu specificatu u nome di l'ughjettu cum'è "woo".

Cumpilemu è eseguisce u prugramma:

$ 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(

Eccu a chjama di u sistema bpf(2) ci hà tornatu u numeru di mappa di descrittore 3 è dopu u prugramma, cum'è previstu, aspetta per più struzzioni in a chjama di u sistema pause(2).

Avà mandemu u nostru prugramma à u fondu o apre un altru terminal è fighjemu u nostru ughjettu cù l'utilità bpftool (pudemu distingue a nostra mappa da l'altri per u so nome):

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

U numeru 114 hè l'ID globale di u nostru ughjettu. Qualchese prugramma nantu à u sistema pò aduprà stu ID per apre una mappa esistente cù u cumandimu BPF_MAP_GET_FD_BY_ID chjama di sistema bpf.

Avà pudemu ghjucà cù a nostra table hash. Fighjemu u so cuntenutu:

$ sudo bpftool map dump id 114
Found 0 elements

Viotu. Mettimu un valore in questu hash[1] = 1:

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

Fighjemu dinò a tavula:

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

Eura! Avemu riesciutu à aghjunghje un elementu. Nota chì avemu da travaglià à u nivellu di byte per fà questu, postu chì bptftool ùn sà micca chì tipu sò i valori in a tavola hash. (Questa cunniscenza pò esse trasferita à ella usendu BTF, ma più nantu à questu avà.)

Cumu esattamente bpftool leghje è aghjunghje elementi? Fighjemu un ochju sottu à u cappucciu:

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

Prima avemu apertu a mappa da u so ID globale cù u cumandimu BPF_MAP_GET_FD_BY_ID и bpf(2) hà tornatu u descrittore 3 à noi BPF_MAP_GET_NEXT_KEY avemu trovu a prima chjave in a tavula passendu NULL cum'è un punteru à a chjave "precedente". Se avemu a chjave, pudemu fà BPF_MAP_LOOKUP_ELEMchì torna un valore à un puntatore value. U prossimu passu hè di pruvà à truvà l'elementu prossimu passendu un puntatore à a chjave attuale, ma a nostra tavula cuntene solu un elementu è u cumandamentu. BPF_MAP_GET_NEXT_KEY torna ENOENT.

Va bè, cambiemu u valore da a chjave 1, dicemu chì a nostra logica cummerciale richiede a registrazione hash[1] = 2:

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

Comu s'aspittava, hè assai sèmplice: u cumandamentu BPF_MAP_GET_FD_BY_ID apre a nostra mappa per ID, è u cumandamentu BPF_MAP_UPDATE_ELEM sovrascrive l'elementu.

Allora, dopu avè creatu una table hash da un prugramma, pudemu leghje è scrive u so cuntenutu da un altru. Nota chì s'è no puderemu fà questu da a linea di cumanda, allora qualsiasi altru prugramma in u sistema pò fà. In più di i cumandamenti descritti sopra, per travaglià cù carte da u spaziu di l'utilizatori, i seguenti:

  • BPF_MAP_LOOKUP_ELEM: truvà u valore per chjave
  • BPF_MAP_UPDATE_ELEM: aghjurnà / crea valore
  • BPF_MAP_DELETE_ELEM: caccià a chjave
  • BPF_MAP_GET_NEXT_KEY: truvà a chjave dopu (o prima).
  • BPF_MAP_GET_NEXT_ID: permette di passà per tutte e carte esistenti, hè cusì chì funziona bpftool map
  • BPF_MAP_GET_FD_BY_ID: apre una mappa esistente cù u so ID globale
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: aghjurnà atomicamente u valore di un ughjettu è rinvià u vechju
  • BPF_MAP_FREEZE: rende a mappa immutable da u spaziu d'utilizatore (sta operazione ùn pò esse annullata)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: operazioni di massa. Per esempiu, BPF_MAP_LOOKUP_AND_DELETE_BATCH - questu hè l'unicu modu affidabile per leghje è resettate tutti i valori da a mappa

Ùn sò micca tutti questi cumandamenti funzionanu per tutti i tipi di carte, ma in generale u travagliu cù altre tippi di carte da u spaziu di l'utilizatori pare esattamente u listessu cum'è travaglià cù e tavule hash.

Per l'ordine, finiscemu i nostri esperimenti di tavulinu di hash. Ricurdativi chì avemu creatu una tavula chì pò cuntene sin'à quattru chjave? Aghjunghjemu uni pochi di elementi più:

$ 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

Finu à quì bè:

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

Pruvemu di aghjunghje unu di più:

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

Comu aspittatu, ùn avemu micca successu. Fighjemu l'errore in più detail:

$ 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 +++

Tuttu hè bè: cum'è s'aspittava, a squadra BPF_MAP_UPDATE_ELEM prova à creà una nova, quinta, chjave, ma crashes E2BIG.

Cusì, pudemu creà è carricà i prugrammi BPF, è ancu creà è gestisce carte da u spaziu di l'utilizatori. Avà hè logicu per vede cumu pudemu usà carte da i prugrammi BPF stessi. Pudemu parlà di questu in a lingua di i prugrammi difficiuli di leghje in i codici macro di macchina, ma in fattu hè ghjuntu u tempu di dimustrà cumu i prugrammi BPF sò veramente scritti è mantenuti - usendu libbpf.

(Per i lettori chì ùn sò micca soddisfatti di a mancanza di un esempiu di livellu bassu: analizzeremu in dettaglio i prugrammi chì utilizanu carte è funzioni d'aiutu create cù libbpf è vi dicu ciò chì succede à u livellu d'istruzione. Per i lettori chì sò insatisfatti assai, avemu aghjustatu esempiu in u locu adattatu in l'articulu).

Scrizzione di prugrammi BPF cù libbpf

Scrive i prugrammi BPF cù i codici di a macchina pò esse interessante solu a prima volta, è poi si mette in sazietà. À questu mumentu avete bisognu di turnà a vostra attenzione llvm, chì hà un backend per generà codice per l'architettura BPF, è ancu una biblioteca libbpf, chì vi permette di scrive u latu di l'utilizatori di l'applicazioni BPF è carica u codice di i prugrammi BPF generati usendu llvm/clang.

In fatti, cum'è avemu da vede in questu è articuli successivi, libbpf faci assai travagliu senza ellu (o strumenti simili - iproute2, libbcc, libbpf-go, etc.) hè impussibile di campà. Una di e caratteristiche killer di u prugettu libbpf hè BPF CO-RE (Compile Once, Run Everywhere) - un prughjettu chì permette di scrive prugrammi BPF chì sò portable da un kernel à l'altru, cù a capacità di eseguisce in diverse API (per esempiu, quandu a struttura di u kernel cambia da a versione). à a versione). Per pudè travaglià cù CO-RE, u vostru kernel deve esse cumpilatu cù supportu BTF (descrivimu cumu fà questu in a sezione Strumenti di sviluppu. Pudete verificà se u vostru kernel hè custruitu cù BTF o micca assai simplice - da a presenza di u schedariu seguente:

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

Stu schedariu guarda infurmazione nantu à tutti i tipi di dati utilizati in u kernel è hè utilizatu in tutti i nostri esempi di usu libbpf. Parleremu in dettagliu di CO-RE in u prossimu articulu, ma in questu - basta à custruisce un kernel cun CONFIG_DEBUG_INFO_BTF.

affairs libbpf campa ghjustu in u cartulare tools/lib/bpf kernel è u so sviluppu hè realizatu attraversu a lista di mailing bpf@vger.kernel.org. Tuttavia, un repository separatu hè mantinutu per i bisogni di l'applicazioni chì vivenu fora di u kernel https://github.com/libbpf/libbpf in quale a libreria di u kernel hè riflessa per l'accessu di lettura più o menu cum'è hè.

In questa rùbbrica, avemu da vede cumu pudete creà un prughjettu chì usa libbpf, scrivimu parechji prugrammi di teste (più o menu senza significatu) è analizzemu in dettu cumu tuttu funziona. Questu ci permetterà di spiegà più facilmente in e sezioni seguenti esattamente cumu i prugrammi BPF interagiscenu cù carte, assistenti di kernel, BTF, etc.

Tipicamente prughjetti chì utilizanu libbpf aghjunghje un repository GitHub cum'è un submodulu git, faremu u listessu:

$ 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.

In traccia d'andà à libbpf assai sèmplice:

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

U nostru prossimu pianu in questa sezione hè u seguitu: scriveremu un prugramma BPF cum'è BPF_PROG_TYPE_XDP, u listessu cum'è in l'esempiu precedente, ma in C, l'avemu compilatu usendu clang, è scrivite un prugramma d'aiutu chì a carica in u kernel. In e seguenti sezioni, espansione e capacità di u prugramma BPF è di u prugramma assistente.

Esempiu: creazione di una applicazione cumpleta cù libbpf

Per principià, avemu aduprà u schedariu /sys/kernel/btf/vmlinux, chì hè statu mintuatu sopra, è creanu u so equivalente in forma di un file header:

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

Stu schedariu guardà tutte e strutture di dati dispunibuli in u nostru kernel, per esempiu, questu hè cumu l'intestazione IPv4 hè definitu in u kernel:

$ 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;
};

Avà scriveremu u nostru prugramma BPF in C:

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

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

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

Ancu s'è u nostru prugramma hè diventatu assai simplice, avemu sempre bisognu di attentu à parechji ditagli. Prima, u primu file header chì includemu hè vmlinux.h, chì avemu ghjustu generatu usendu bpftool btf dump - avà ùn avemu micca bisognu di stallà u pacchettu kernel-headers per sapè cumu si sò e strutture di u kernel. U seguente file di intestazione vene à noi da a biblioteca libbpf. Avà avemu solu bisognu di definisce a macro SEC, chì manda u caratteru à a sezione appropritata di u schedariu d'ughjettu ELF. U nostru prugramma hè cuntinutu in a rùbbrica xdp/simple, induve prima di u slash definiscemu u tipu di prugramma BPF - questu hè a cunvenzione utilizata in libbpf, basatu annantu à u nome di a sezione, sustituverà u tipu currettu à l'iniziu bpf(2). U prugramma BPF stessu hè C - assai simplice è custituitu da una linea return XDP_PASS. Infine, una sezione separata "license" cuntene u nome di a licenza.

Pudemu cumpilà u nostru prugramma cù llvm/clang, versione>= 10.0.0, o megliu ancu, più grande (vede a sezione Strumenti di sviluppu):

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

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

Trà e caratteristiche interessanti: indichemu l'architettura di destinazione -target bpf è u percorsu à l'intestazione libbpf, chì avemu stallatu recentemente. Inoltre, ùn vi scurdate micca -O2, senza questa opzione pudete esse in sorpresa in u futuru. Fighjemu u nostru codice, avemu riesciutu à scrive u prugramma chì vulemu ?

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

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

Disassembly of section xdp/simple:

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

Iè, hà travagliatu! Avà, avemu un schedariu binariu cù u prugramma, è vulemu creà una applicazione chì hà da carricà in u kernel. Per questu scopu, a biblioteca libbpf ci offre duie opzioni - utilizate una API di livellu più bassu o una API di livellu più altu. Andemu in a seconda strada, postu chì vulemu amparà à scrive, carica è cunnetta i prugrammi BPF cù u minimu sforzu per u so studiu sussegwente.

Prima, avemu bisognu di generà u "scheletru" di u nostru prugramma da u so binariu utilizendu a stessa utilità bpftool - u cuteddu svizzeru di u mondu BPF (chì pò esse pigliatu literalmente, postu chì Daniel Borkman, unu di i creatori è mantenitori di BPF, hè svizzeru):

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

In u schedariu xdp-simple.skel.h cuntene u codice binariu di u nostru prugramma è e funzioni per a gestione - carica, attache, sguassà u nostru ughjettu. In u nostru casu simplice questu s'assumiglia eccessivamente, ma funziona ancu in u casu induve u schedariu di l'ughjettu cuntene assai prugrammi BPF è carte è per carricà stu ELF giant avemu solu bisognu di generà u scheletru è chjamà una o duie funzioni da l'applicazione persunalizata. scrivenu Andemu avà.

In modu strettu, u nostru prugramma di caricatore hè triviale:

#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);
}

struct xdp_simple_bpf definitu in u schedariu xdp-simple.skel.h è descrive u nostru schedariu d'ughjettu:

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;
};

Pudemu vede tracce di una API di livellu bassu quì: a struttura struct bpf_program *simple и struct bpf_link *simple. A prima struttura descrive specificamente u nostru prugramma, scrittu in a rùbbrica xdp/simple, è u sicondu descrive cumu u prugramma cunnetta à a fonte di l'avvenimentu.

funziunava xdp_simple_bpf__open_and_load, apre un oggettu ELF, analizà, crea tutte e strutture è sottostrutture (oltre à u prugramma, ELF cuntene ancu altre sezioni - dati, dati di lettura, infurmazione di debugging, licenza, etc.), è poi carica in u kernel cù un sistema. chjama bpf, chì pudemu verificà cumpilendu è eseguendu u prugramma:

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

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

Fighjemu avà u nostru prugramma cù l'usu bpftool. Truvemu a so 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)

è dump (avemu aduprà una forma accurtata di u cumandamentu bpftool prog dump xlated):

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

Qualcosa di novu! U prugramma hà stampatu pezzi di u nostru schedariu di fonte C Questu hè statu fattu da a biblioteca libbpf, chì hà truvatu a sezione di debug in u binariu, hà compilatu in un oggettu BTF, hà caricatu in u kernel usendu BPF_BTF_LOAD, è dopu specificatu u descriptore di u schedariu resultanti quandu carica u prugramma cù u cumandimu BPG_PROG_LOAD.

Aiutanti di u kernel

I prugrammi BPF ponu eseguisce funzioni "esterne" - kernel helpers. Queste funzioni d'aiutu permettenu à i prugrammi BPF di accede à e strutture di u kernel, di gestisce e mape, è ancu di cumunicà cù u "mondu reale" - creà eventi perf, cuntrollà hardware (per esempiu, redirect packets), etc.

Esempiu: bpf_get_smp_processor_id

In u quadru di u paradigma di "amparare per esempiu", cunsideremu una di e funzioni d'aiutu, bpf_get_smp_processor_id(), certa in u schedariu kernel/bpf/helpers.c. Ritorna u numeru di u processatore nantu à quale u prugramma BPF chì hà chjamatu hè in esecuzione. Ma ùn avemu micca interessatu in a so semantica cum'è in u fattu chì a so implementazione piglia una linea:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

E definizioni di e funzioni d'aiutu BPF sò simili à e definizioni di chjamate di sistema. LinuxQuì, per esempiu, hè definita una funzione chì ùn hà micca argumenti. (Una funzione chì piglia, per esempiu, trè argumenti hè definita cù una macro BPF_CALL_3. U numaru massimu di argumenti hè cinque.) Tuttavia, questu hè solu a prima parte di a definizione. A seconda parte hè di definisce a struttura di u tipu struct bpf_func_proto, chì cuntene una descrizzione di a funzione helper chì u verificatore capisce:

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

Registrazione di Funzioni Helper

Per chì i prugrammi BPF di un tipu particulari utilizanu sta funzione, anu da esse registratu, per esempiu per u tipu BPF_PROG_TYPE_XDP una funzione hè definita in u kernel xdp_func_proto, chì determina da l'ID di funzione helper se XDP supporta sta funzione o micca. A nostra funzione hè sustegnu:

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;
    ...
    }
}

I novi tipi di prugramma BPF sò "definiti" in u schedariu include/linux/bpf_types.h usendu una macro BPF_PROG_TYPE. Definitu in virgulette perchè hè una definizione logica, è in termini di lingua C a definizione di un inseme sanu di strutture concrete si trova in altri lochi. In particulare, in u schedariu kernel/bpf/verifier.c tutte e definizioni da u schedariu bpf_types.h sò usati per creà una varietà di strutture bpf_verifier_ops[]:

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

Questu hè, per ogni tipu di prugramma BPF, un punteru à una struttura di dati di u tipu hè definitu struct bpf_verifier_ops, chì hè inizializatu cù u valore _name ## _verifier_ops, vale à dì, xdp_verifier_ops di xdp. Struttura xdp_verifier_ops determinatu in u schedariu net/core/filter.c a siguenti:

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,
};

Quì vedemu a nostra funzione familiar xdp_func_proto, chì eseguirà u verificatore ogni volta chì scontra una sfida qualchì tipu funzioni in un prugramma BPF, vede verifier.c.

Fighjemu cumu un prugramma ipoteticu BPF usa a funzione bpf_get_smp_processor_id. Per fà questu, riscrivemu u prugramma da a nostra sezione precedente cum'è seguente:

#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";

Simbulu bpf_get_smp_processor_id determinatu в <bpf/bpf_helper_defs.h> librarii libbpf quantu

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

hè, bpf_get_smp_processor_id hè un puntatore di funzione chì u valore hè 8, induve 8 hè u valore BPF_FUNC_get_smp_processor_id tipu enum bpf_fun_id, chì hè definitu per noi in u schedariu vmlinux.h (file bpf_helper_defs.h in u kernel hè generatu da un script, cusì i numeri "magichi" sò ok). Questa funzione ùn piglia micca argumenti è torna un valore di tipu __u32. Quandu l'avemu in u nostru prugramma, clang genera una struzzione BPF_CALL "u tipu ghjustu" Cumpilemu u prugramma è fighjemu a rùbbrica xdp/simple:

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

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

Disassembly of section xdp/simple:

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

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

In a prima linea vedemu struzzioni call, paràmetru IMM chì hè uguali à 8, è SRC_REG - zeru. Sicondu l'accordu ABI utilizatu da u verificatore, questu hè una chjama à a funzione d'aiutu numeru ottu. Una volta hè lanciata, a logica hè simplice. Ritorna u valore da u registru r0 copiatu à r1 è nantu à i linii 2,3 hè cunvertitu à tipu u32 - i 32 bit superiori sò sbulicati. Nant'à e linii 4,5,6,7 vultemu 2 (XDP_PASS) o 1 (XDP_DROP) secondu chì a funzione helper da a linea 0 hà restituutu un valore zero o micca zero.

Testemu noi stessi: carica u prugramma è fighjate u risultatu 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

Ok, verificatore hà trovu l'aiutu di kernel currettu.

Esempiu: passendu argumenti è infine eseguisce u prugramma!

Tutte e funzioni d'aiutu à u livellu di run-level anu un prototipu

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

I paràmetri à e funzioni d'aiutu sò passati in i registri r1-r5, è u valore hè tornatu in u registru r0. Ùn ci sò micca funzioni chì pigghianu più di cinque argumenti, è u sustegnu per elli ùn hè micca previstu per esse aghjuntu in u futuru.

Fighjemu un ochju à u novu helper di u kernel è cumu BPF passa i paràmetri. Riscrivemu xdp-simple.bpf.c cum'è seguitu (u restu di e linee ùn anu micca cambiatu):

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

U nostru prugramma stampa u numeru di CPU nantu à quale hè in esecuzione. Cumpilemu è fighjemu u codice:

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

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

In i linii 0-7 scrivimu a stringa running on CPU%un, è dopu nantu à a linea 8 eseguimu quellu familiar bpf_get_smp_processor_id. Nant'à i linii 9-12 avemu preparatu l'argumenti di l'aiutu bpf_printk - i registri r1, r2, r3. Perchè ci sò trè è micca dui ? Perchè bpf_printkquestu hè un macro wrapper intornu à u veru aiutu bpf_trace_printk, chì deve passà a dimensione di a stringa di furmatu.

Avà aghjunghje un paru di linii xdp-simple.ccusì chì u nostru prugrammu cullega à l 'interfaccia lo è veramente cuminciatu!

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

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

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

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

cleanup:
    xdp_simple_bpf__destroy(obj);
}

Quì avemu aduprà a funzione bpf_set_link_xdp_fd, chì cunnetta i prugrammi BPF di tipu XDP à l'interfaccia di rete. Avemu codificatu u numeru di l'interfaccia lo, chì hè sempre 1. Eseguimu a funzione duie volte per prima staccate u vechju prugramma s'ellu era attaccatu. Avvisate chì avà ùn avemu micca bisognu di sfida pause o un ciclu infinitu: u nostru prugramma di caricatore esce, ma u prugramma BPF ùn serà micca distruttu postu chì hè cunnessu à a fonte di l'avvenimentu. Dopu a scaricamentu successu è a cunnessione, u prugramma serà lanciatu per ogni pacchettu di rete chì arriva lo.

Andemu à scaricà u prugrammu è fighjulà l'interfaccia 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

U prugramma chì avemu telecaricatu hà ID 669 è vedemu u listessu ID in l'interfaccia lo. Manderemu un paru di pacchetti à 127.0.0.1 (richiesta + risposta):

$ ping -c1 localhost

è avà fighjemu u cuntenutu di u schedariu virtuale di debug /sys/kernel/debug/tracing/trace_pipe, in quale bpf_printk scrive i so missaghji:

# 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

Dui pacchetti sò stati macchiati lo è processatu nantu à CPU0 - u nostru primu prugramma BPF senza significatu cumpletu hà travagliatu!

Vale a pena nutà chì bpf_printk Ùn hè micca per nunda chì scrive à u schedariu di debug: questu ùn hè micca l'aiutu più successu per l'usu in a produzzione, ma u nostru scopu era di mustrà qualcosa simplice.

Accessu à e carte da i prugrammi BPF

Esempiu: utilizendu una mappa da u prugramma BPF

In e rùbbriche precedenti avemu amparatu cumu creà è aduprà mape da u spaziu di l'utilizatori, è avà fighjemu a parte di u kernel. Cuminciamu, cum'è di solitu, cù un esempiu. Riscrivemu u nostru prugramma xdp-simple.bpf.c a siguenti:

#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";

À u principiu di u prugramma avemu aghjustatu una definizione di mappa woo: Questu hè un array di 8 elementi chì guarda i valori cum'è u64 (in C avemu da definisce un tali array cum'è u64 woo[8]). In un prugramma "xdp/simple" avemu u numeru di prucessore attuale in una variàbile key è dopu aduprà a funzione helper bpf_map_lookup_element avemu un punteru à l'entrata currispundenti in u array, chì avemu aumentatu da unu. Tradottu in Russu: calculemu statistiche nantu à quale CPU hà processatu i pacchetti entranti. Pruvemu di eseguisce u prugramma:

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

Cuntrollamu ch'ella hè cunnessa lo è mandate qualchi pacchetti:

$ 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

Avà fighjemu u cuntenutu di l'array:

$ 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 }
]

Quasi tutti i prucessi sò stati processati nantu à CPU7. Questu ùn hè micca impurtante per noi, u principale hè chì u prugramma funziona è capiscenu cumu accede à e carte da i prugrammi BPF - usendu хелперов bpf_mp_*.

Indice misticu

Cusì, pudemu accede à a mappa da u prugramma BPF usendu chjama cum'è

val = bpf_map_lookup_elem(&woo, &key);

induve a funzione helper s'assumiglia

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

ma passemu un puntatore &woo à una struttura senza nome struct { ... }...

Se fighjemu u prugramma assembler, vedemu chì u valore &woo ùn hè micca veramente definitu (linea 4):

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

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

Disassembly of section xdp/simple:

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

è hè contenuta in traslocazioni:

$ 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

Ma se fighjemu u prugramma digià caricatu, vedemu un punteru à a mappa curretta (linea 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]
...

Cusì, pudemu cuncludi chì à u mumentu di lanciari u nostru prugramma di caricatore, u ligame per &woo hè statu rimpiazzatu da qualcosa cù una biblioteca libbpf. Prima, fighjemu a pruduzzioni strace:

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

Avemu vede chì libbpf criatu una mappa woo è dopu scaricatu u nostru prugramma simple. Fighjemu un ochju più vicinu à cumu carchemu u prugramma:

  • chjama xdp_simple_bpf__open_and_load da u schedariu xdp-simple.skel.h
  • chì provoca xdp_simple_bpf__load da u schedariu xdp-simple.skel.h
  • chì provoca bpf_object__load_skeleton da u schedariu libbpf/src/libbpf.c
  • chì provoca bpf_object__load_xattr из libbpf/src/libbpf.c

L'ultima funzione, frà altre cose, chjamarà bpf_object__create_maps, chì crea o apre e carte esistenti, trasfurmendu in descriptori di file. (Questu hè induve vedemu BPF_MAP_CREATE in l'output strace.) In seguitu a funzione hè chjamata bpf_object__relocate è hè ella chì ci interessa, postu chì ricurdamu di ciò chì avemu vistu woo in a tavola di trasferimentu. Esplorendu, eventualmente ci truvemu in a funzione bpf_program__relocate, chì tratta di trasferimenti di carte:

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

Allora pigliamu e nostre struzzioni

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

è rimpiazzà u registru fonte in questu cù BPF_PSEUDO_MAP_FD, è u primu IMM à u descrittore di u schedariu di a nostra mappa è, s'ellu hè uguali à, per esempiu, 0xdeadbeef, tandu com'è u risultatu, riceveremu l'istruzzioni

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

Questu hè cumu l'infurmazione di a mappa hè trasferita à un prugramma BPF caricatu specificu. In questu casu, a mappa pò esse creata usendu BPF_MAP_CREATE, è apertu da ID usendu BPF_MAP_GET_FD_BY_ID.

Total, quandu usu libbpf l'algoritmu hè cusì:

  • durante a compilazione, i registri sò creati in a tavola di trasferimentu per i ligami à i mape
  • libbpf apre u libru d'ughjettu ELF, trova tutte e carte usate è crea descriptori di file per elli
  • i descrittori di u schedariu sò caricati in u kernel cum'è parte di l'istruzzioni LD64

Comu pudete imagine, ci hè più à vene è avemu da circà in u core. Fortunatamente, avemu una idea - avemu scrittu u significatu BPF_PSEUDO_MAP_FD in u registru di a fonte è pudemu intarrallu, chì ci purterà à u santu di tutti i santi - kernel/bpf/verifier.c, induve una funzione cù un nome distintivu rimpiazza un descrittore di file cù l'indirizzu di una struttura di tipu 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;

(U codice cumpletu pò esse truvatu Member). Allora pudemu espansione u nostru algoritmu:

  • mentre carica u prugramma, verificatore verifica l'usu currettu di a mappa è scrive l'indirizzu di a struttura currispondente struct bpf_map

Quandu scaricate u binariu ELF usendu libbpf Ci hè assai di più, ma ne discutemu in altri articuli.

Carica di prugrammi è carte senza libbpf

Cum'è prumessu, quì hè un esempiu per i lettori chì volenu sapè cumu creà è carricà un prugramma chì usa carte, senza aiutu. libbpf. Questu pò esse utile quandu si travaglia in un ambiente per quale ùn pudete micca custruisce dipendenze, o salvà ogni pocu, o scrive un prugramma cum'è ply, chì genera codice binariu BPF nantu à a mosca.

Per fà più faciule per seguità a logica, avemu da riscrive u nostru esempiu per questi scopi xdp-simple. U codice cumpletu è pocu allargatu di u prugramma cunsideratu in questu esempiu pò esse truvatu in questu levitu.

A logica di a nostra applicazione hè a siguenti:

  • creà una mappa di tipu BPF_MAP_TYPE_ARRAY usendu u cumandamentu BPF_MAP_CREATE,
  • creà un prugramma chì usa sta mappa,
  • cunnette u prugramma à l'interfaccia lo,

chì si traduce in umanu cum'è

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);
}

map_create crea una mappa in listessa manera chì avemu fattu in u primu esempiu di a chjama di u sistema bpf - "kernel, per piacè fatemi una nova mappa in forma di un array di 8 elementi cum'è __u64 è mi restituite u descrittore di u schedariu ":

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));
}

U prugramma hè ancu faciule di carricà:

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));
}

A parte dilicata prog_load hè a definizione di u nostru prugramma BPF cum'è una serie di strutture struct bpf_insn insns[]. Ma postu chì usemu un prugramma chì avemu in C, pudemu ingannà un pocu:

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

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

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

In totale, avemu bisognu di scrive 14 struzzioni in forma di strutture cum'è struct bpf_insn (cunsigliu: pigliate u dump da sopra, re-leghje a rùbbrica di instructions, apre linux/bpf.h и linux/bpf_common.h è pruvate à determinà struct bpf_insn insns[] per sè stessu):

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
    },
};

Un eserciziu per quelli chì ùn anu micca scrittu questu - truvà map_fd.

Ci hè una parte più micca divulgata in u nostru prugramma - xdp_attach. Sfortunatamente, prugrammi cum'è XDP ùn ponu micca esse cunnessi cù una chjama di sistema bpfE persone chì anu creatu BPF è XDP eranu di a cumunità in linea. Linux, ciò significa chì anu utilizatu quellu chì era u più familiare per elli (ma micca per normale people) interfaccia per interagisce cù u kernel: sockets netlink, vede ancu RFC 3549. A manera più simplice di implementà xdp_attach sta copia di codice da libbpf, vale à dì da u schedariu netlink.c, chì hè ciò chì avemu fattu, accurtendu un pocu:

Benvenuti à u mondu di i sockets netlink

Apertura un tipu di socket netlink 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;
}

Leghjemu da questu socket:

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;
}

Infine, eccu a nostra funzione chì apre un socket è manda un missaghju speciale chì cuntene un descrittore di file:

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

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

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

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

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

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

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

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

cleanup:
    close(sock);
    return ret;
}

Dunque, tuttu hè prontu per a prova:

$ 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 +++

Videmu s'ellu u nostru prugramma hà cunnessu lo:

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

Mandemu pings è fighjemu a mappa:

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

Hurrah, tuttu funziona. Nota, per via, chì a nostra mappa hè di novu affissata in forma di bytes. Questu hè duvuta à u fattu chì, à u cuntrariu libbpf ùn avemu micca caricatu infurmazione di tipu (BTF). Ma parleremu più di questu a prossima volta.

Strumenti di sviluppu

In questa sezione, guardemu à u toolkit minimu di sviluppatore BPF.

In generale, ùn avete micca bisognu di qualcosa di speciale per sviluppà prugrammi BPF - BPF funziona nantu à qualsiasi kernel di distribuzione decente, è i prugrammi sò custruiti usendu clang, chì pò esse furnitu da u pacchettu. Tuttavia, per via di u fattu chì BPF hè in sviluppu, u kernel è l'arnesi cambianu constantemente, se ùn vulete micca scrive prugrammi BPF cù metudi antichi da 2019, allora vi tuccherà à cumpilà.

  • llvm/clang
  • pahole
  • u so core
  • bpftool

(Per riferimentu: sta sezione è tutti l'esempii in l'articulu sò stati eseguiti nantu à Debian 10.)

llvm/clang

BPF hè amichevule cù LLVM è, ancu se recentemente i prugrammi per BPF ponu esse compilati cù gcc, tuttu u sviluppu attuale hè realizatu per LLVM. Dunque, prima di tuttu, avemu da custruisce a versione attuale clang da git:

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... много времени спустя
$

Avà pudemu verificà se tuttu hè ghjuntu bè:

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

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

(Istruzioni di montaggio clang pigliatu da mè da bpf_devel_QA.)

Ùn installemu micca i prugrammi chì avemu appena custruitu, ma invece solu aghjunghje à PATH, per esempiu:

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

(Questu pò esse aghjuntu à .bashrc o in un schedariu separatu. In modu persunale, aghju aghjunghje cose cum'è questu ~/bin/activate-llvm.sh è quandu hè necessariu u facciu . activate-llvm.sh.)

Pahole è BTF

Utilità pahole utilizatu quandu custruisce u kernel per creà informazioni di debugging in formatu BTF. Ùn andemu micca in dettagliu in questu articulu nantu à i dettagli di a tecnulugia BTF, fora di u fattu chì hè cunvenutu è vulemu aduprà. Dunque, sè vo site per custruisce u vostru kernel, custruite prima pahole (senza pahole ùn puderete micca custruisce u kernel cù l'opzione 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

Kernels per sperimentà cù BPF

Quandu scopre e pussibilità di BPF, vogliu assemble u mo core. Questu, in generale, ùn hè micca necessariu, postu chì puderete cumpilà è carricà i prugrammi BPF nantu à u kernel di distribuzione, però, avè u vostru propiu kernel vi permette di utilizà l'ultime funzioni BPF, chì apparisceranu in a vostra distribuzione in mesi à u megliu. , o, cum'è in u casu di certi arnesi di debugging ùn saranu micca imballati in tuttu in u futuru prevedibile. Inoltre, u so core propriu si senti impurtante di sperimentà cù u codice.

Per custruisce un kernel avete bisognu, prima, u kernel stessu, è in segundu, un schedariu di cunfigurazione di u kernel. Per sperimentà cù BPF pudemu usà u solitu vaniglia core o unu di i core di sviluppatori. Storicamente, u sviluppu di BPF hè statu realizatu in a cumunità di rete. Linux è dunque tutti i cambiamenti prima o poi passanu per David Miller, u mantenitore di a rete LinuxSicondu a so natura - correzioni o nuove funzionalità - i cambiamenti di rete finiscenu in unu di i dui nuclei: net o net-next. I cambiamenti per BPF sò distribuiti in u listessu modu trà bpf и bpf-next, chì sò poi riuniti in net è net-next, rispettivamente. Per più dettagli, vede bpf_devel_QA и netdev-FAQ. Allora sceglite un kernel basatu annantu à u vostru gustu è à i bisogni di stabilità di u sistema chì site a prova (*-next kernels sò i più instabili di quelli listati).

Hè fora di u scopu di questu articulu per parlà di cumu gestisce i schedarii di cunfigurazione di u kernel - si assume chì o sapete già cumu fà questu, o prontu à amparà per sè stessu. Tuttavia, e seguenti struzzioni duveranu esse più o menu abbastanza per dà un sistema chì funziona BPF.

Scaricate unu di i kernel sopra:

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

Custruite una cunfigurazione minima di u kernel di travagliu:

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

Habilita l'opzioni BPF in u schedariu .config di a vostra scelta (probabilmente CONFIG_BPF serà digià attivatu postu chì Systemd l'utiliza). Eccu una lista di l'opzioni da u kernel utilizatu per questu articulu:

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

Allora pudemu assemblà facilmente è installà i moduli è u kernel (per via, pudete assemble u kernel usendu u novu assemblatu). clangaghjustendu CC=clang):

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

è riavvia cù u novu kernel (aghju utilizatu per questu kexec da u pacchettu 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

L'utilità più cumuna in l'articulu serà l'utilità bpftool, furnitu cum'è parte di u kernel LinuxHè scrittu è mantinutu da sviluppatori BPF per sviluppatori BPF è pò esse adupratu per gestisce tutti i tipi d'uggetti BPF - caricà prugrammi, creà è mudificà carte, esplorà l'ecosistema BPF, è assai di più. A ducumentazione in forma di codice surghjente per e pagine man pò esse truvata. in u core o, digià compilatu, in rete.

À u mumentu di sta scrittura bpftool vene prontu solu per RHEL, Fedora è Ubuntu (vede, per esempiu, stu filu, chì conta a storia infinita di imballaggi bpftool в DebianMa s'è vo avete digià assemblatu u vostru kernel, allora assemblatelu bpftool faciule cum'è una torta:

$ 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  ]

$

(Qua ${linux} - questu hè u vostru repertoriu di u kernel.) Dopu eseguite questi cumandamenti bpftool serà cullatu in un annuariu ${linux}/tools/bpf/bpftool è pò esse aghjuntu à a strada (prima di tuttu à l'utilizatore root) o solu copia à /usr/local/sbin.

Coglie bpftool hè megliu aduprà l'ultimi clang, assemblatu cum'è descrittu sopra, è verificate s'ellu hè assemblatu currettamente - usendu, per esempiu, u cumandamentu

$ 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
...

chì mostrarà quali funzioni BPF sò attivate in u vostru kernel.

In modu, u cumandamentu precedente pò esse eseguitu cum'è

# bpftool f p k

Questu hè fattu per analogia cù l'utilità da u pacchettu iproute2, induve pudemu, per esempiu, dì ip a s eth0 invece di ip addr show dev eth0.

cunchiusioni

BPF vi permette di scarpi una pulga per misurà in modu efficau è cambià a funziunalità di u core. U sistema hè diventatu assai successu, in i migliori tradizioni di UNIX: un mecanismu simplice chì permette di (ri)programmà u kernel hà permessu à un gran numaru di persone è urganisazione per sperimentà. E, ancu s'è l'esperimenti, è ancu u sviluppu di l'infrastruttura BPF stessu, sò luntanu da esse finitu, u sistema hà digià un ABI stabile chì permette di custruisce una logica di cummerciale affidativa è più impurtante, efficace.

Vogliu nutà chì, in my opinion, a tecnulugia hè diventata cusì populari perchè, da una banda, pò играть (l'architettura di a machina pò esse capitu più o menu in una sera), è da l'altra parte, risolve i prublemi chì ùn puderanu micca esse risolti (bellissima) prima di a so apparizione. Sti dui cumpunenti inseme forzanu a ghjente à sperimentà è sunnià, chì porta à l'emergenza di suluzioni sempre più innovatori.

Questu articulu, ancu s'ellu ùn hè micca particularmente curtu, hè solu una introduzione à u mondu di BPF è ùn descrive micca funziunalità "avanzata" è parti impurtanti di l'architettura. U pianu avanti hè qualcosa di questu: u prossimu articulu serà una panoramica di i tipi di prugrammi BPF (ci sò 5.8 tippi di prugrammi supportati in u kernel 30), allora avemu da vede infine cumu scrive l'applicazioni BPF reali cù i prugrammi di traccia di u kernel. cum'è un esempiu, allora hè ora di un cursu più approfonditu nantu à l'architettura BPF, seguitu da esempi di applicazioni di rete è di sicurezza BPF.

Articuli precedenti in questa serie

  1. BPF per i picculi, parte zero: BPF classicu

Ligami

  1. Guida di riferimentu BPF è XDP - documentazione nantu à BPF da cilium, o più precisamente da Daniel Borkman, unu di i creatori è mantenitori di BPF. Questu hè unu di i primi discrizzioni serii, chì difiere di l'altri in chì Daniel sapi esattamente ciò chì scrive è ùn ci sò micca sbagliati. In particulare, stu documentu descrive cumu travaglià cù i prugrammi BPF di i tipi XDP è TC cù l'utilità ben cunnisciuta. ip da u pacchettu iproute2.

  2. Documentazione/networking/filter.txt - u schedariu originale cù a documentazione per u BPF classicu è poi allargatu. Una lettura utile se vulete approfondisce a lingua di l'assemblea è i dettagli architettonici tecnichi.

  3. Blog nantu à BPF da Facebook. Hè aghjurnatu raramente, ma bè, cum'è Alexei Starovoitov (autore di eBPF) è Andrii Nakryiko - (mantenitore) scrivenu quì. libbpf).

  4. Sicreti di bpftool. Un filu di Twitter divertente da Quentin Monnet cù esempi è sicreti di usu di bpftool.

  5. Immergete in BPF: una lista di materiale di lettura. Una lista gigante (è sempre mantenuta) di ligami à a documentazione BPF da Quentin Monnet.

Source: www.habr.com

Cumprate un hosting affidabile per i siti cù prutezzione DDoS, servitori VPS VDS 🔥 Cumprate un hosting di siti web affidabile cù prutezzione DDoS, servitori VPS VDS | ProHoster