BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Am Ufank gouf et eng Technologie an et gouf BPF genannt. Mir hunn hir gekuckt virdrun, Alen Testament Artikel vun dëser Serie. Am Joer 2013, duerch d'Efforte vum Alexei Starovoitov an Daniel Borkman, gouf eng verbessert Versioun dovun, optimiséiert fir modern 64-Bit Maschinnen, entwéckelt an am Linux Kernel abegraff. Dës nei Technologie gouf kuerz Intern BPF genannt, duerno ëmbenannt Extended BPF, an elo, e puer Joer méi spéit, nennt jiddereen et einfach BPF.

Grof geschwat, BPF erlaabt Iech arbiträr Benotzer geliwwert Code am Linux Kernelraum auszeféieren, an déi nei Architektur huet sech esou erfollegräich gewisen datt mir eng Dose méi Artikele brauchen fir all seng Uwendungen ze beschreiwen. (Dat eenzegt wat d'Entwéckler net gutt gemaach hunn, wéi Dir am Leeschtungscode hei ënnen kënnt gesinn, war en anstännege Logo erstallt.)

Dësen Artikel beschreift d'Struktur vun der BPF virtueller Maschinn, Kernel-Interfaces fir mat BPF ze schaffen, Entwécklungsinstrumenter, souwéi e kuerzen, ganz kuerzen Iwwerbléck iwwer existéierend Fäegkeeten, d.h. alles wat mir an Zukunft brauchen fir eng méi déif Studie vun de prakteschen Uwendunge vu BPF.
BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Resumé vum Artikel

Aféierung an BPF Architektur. Als éischt wäerte mir e Vugelperspektiv vun der BPF Architektur huelen an d'Haaptkomponenten skizzéieren.

Registere a Kommando System vun der BPF virtuell Maschinn. Schonn eng Iddi vun der Architektur als Ganzt hunn, wäerte mir d'Struktur vun der BPF virtuell Maschinn beschreiwen.

Liewenszyklus vu BPF Objekter, bpffs Dateisystem. An dëser Sektioun wäerte mir de Liewenszyklus vu BPF Objete méi no kucken - Programmer a Kaarten.

Gestioun vun Objeten mam bpf System Uruff. Mat e bësse Verständnis vum System, dee schonn op der Plaz ass, wäerte mir endlech kucken wéi een Objeten aus dem Benotzerraum erstellt a manipuléiert mat engem spezielle Systemruff - bpf(2).

Пишем программы BPF с помощью libbpf. Natierlech kënnt Dir Programmer mat engem Systemruff schreiwen. Awer et ass schwéier. Fir e méi realistesche Szenario hunn nuklear Programméierer eng Bibliothéik entwéckelt libbpf. Mir wäerten eng Basis BPF Applikatioun Skelett schafen, datt mir an pafolgende Beispiller benotzen wäert.

Kernel Helfer. Hei léiere mir wéi BPF Programmer Zougang zu Kernel Helper Funktiounen kréien - en Tool dat, zesumme mat Kaarten, d'Kapazitéite vun der neier BPF am Verglach zum klassesche erweidert.

Zougang zu Kaarten aus BPF Programmer. Zu dësem Zäitpunkt wësse mir genuch fir genau ze verstoen wéi mir Programmer kënne kreéieren déi Kaarten benotzen. A loosst eis souguer e séiere Bléck an de groussen a mächtege Verifizéierer huelen.

Entwécklung Tools. Hëllef Sektioun iwwer wéi een déi erfuerderlech Utilities a Kernel fir Experimenter zesummestellt.

D 'Conclusioun. Um Enn vum Artikel fannen déi, déi esou wäit liesen, motivéierend Wierder an eng kuerz Beschreiwung vun deem, wat an den folgenden Artikelen geschitt. Mir wäerten och eng Rei vu Linken fir Selbststudie oplëschten fir déi, déi net de Wonsch oder d'Fäegkeet hunn op d'Fortsetzung ze waarden.

Aféierung an BPF Architektur

Ier mir ufänken d'BPF Architektur ze berücksichtegen, wäerte mir eng leschte Kéier (oh) op klassesch BPF, déi als Äntwert op d'Entstoe vu RISC Maschinnen entwéckelt gouf an de Problem vun der effizienter Paketfilterung geléist huet. D'Architektur huet sech sou erfollegräich erausgestallt datt, an de schrecklechen XNUMXer Joeren zu Berkeley UNIX gebuer gouf, gouf se op déi meescht existent Betribssystemer portéiert, an de verréckten zwanzeger Joeren iwwerlieft an ëmmer nach nei Uwendungen ze fannen.

Den neie BPF gouf entwéckelt als Äntwert op d'Ubiquity vu 64-Bit Maschinnen, Cloud Servicer an de verstäerkte Bedierfnes fir Tools fir SDN ze kreéieren (Software-definéiert nVeraarbechtung). Entwéckelt vu Kernelnetzingenieuren als e verbesserten Ersatz fir de klassesche BPF, huet den neie BPF wuertwiertlech sechs Méint méi spéit Uwendungen fonnt an der schwiereger Aufgab fir Linux Systemer ze verfolgen, an elo, sechs Joer no senger Erscheinung, brauche mir e ganzen nächsten Artikel just fir Lëscht déi verschidden Zorte vu Programmer.

Witzeg Biller

Am Kär ass BPF eng Sandkëscht virtuell Maschinn déi Iech erlaabt "arbiträr" Code am Kernelraum ze lafen ouni d'Sécherheet ze kompromittéieren. BPF Programmer ginn am Benotzerraum erstallt, an de Kernel gelueden a mat enger Eventquell verbonnen. En Event kéint zum Beispill d'Liwwerung vun engem Paket op eng Netzwierkinterface sinn, de Start vun enger Kernelfunktioun, asw. Am Fall vun engem Package huet de BPF Programm Zougang zu den Daten a Metadaten vum Package (fir ze liesen an eventuell ze schreiwen, ofhängeg vun der Aart vum Programm); am Fall vun enger Kernelfunktioun lafen d'Argumenter vun d'Funktioun, dorënner Hiweiser op Kernel Erënnerung, etc.

Loosst eis dëse Prozess méi no kucken. Fir unzefänken, schwätze mer iwwer den éischten Ënnerscheed aus dem klassesche BPF, Programmer fir déi am Assembler geschriwwe goufen. An der neier Versioun gouf d'Architektur erweidert sou datt Programmer an héije Sprooche geschriwwe kënne ginn, virun allem natierlech an C. Fir dëst gouf e Backend fir llvm entwéckelt, deen Iech erlaabt Bytecode fir d'BPF Architektur ze generéieren.

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

D'BPF Architektur gouf entworf, deelweis, fir effizient op modernen Maschinnen ze lafen. Fir dëst an der Praxis ze schaffen, gëtt de BPF Bytecode, eemol an de Kernel gelueden, an native Code iwwersat mat engem Komponent genannt JIT Compiler (Just In Tech). Als nächst, wann Dir Iech erënnert, am klassesche BPF gouf de Programm an de Kernel gelueden an un d'Evenementquell atomesch befestegt - am Kontext vun engem eenzege Systemruff. An der neier Architektur geschitt dat an zwou Etappen - als éischt gëtt de Code an de Kernel gelueden mat engem Systemruff bpf(2)an dann, méi spéit, duerch aner Mechanismen, datt op der Zort Programm variéieren je, de Programm befestegt op d'Evenement Quell.

Hei kann de Lieser eng Fro hunn: war et méiglech? Wéi ass d'Ausféierungssécherheet vun esou Code garantéiert? D'Exekutiounssécherheet ass eis garantéiert duerch d'Etapp vu Luede BPF Programmer genannt Verifier (op Englesch gëtt dës Etapp Verifier genannt an ech wäert weider dat englesch Wuert benotzen):

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Verifier ass e statesche Analysator dee garantéiert datt e Programm net déi normal Operatioun vum Kernel stéiert. Dëst, iwwregens, heescht net datt de Programm net mat der Operatioun vum System Amëschung kann - BPF Programmer, ofhängeg vun der Aart, kënne Sektioune vum Kernel Memory liesen an iwwerschreiwe, Wäerter vu Funktiounen zréckginn, trimmen, appendéieren, iwwerschreiwe a souguer Netzpakete weiderginn. Verifier garantéiert datt d'Lafen vun engem BPF Programm de Kernel net ofbriechen an datt e Programm deen, laut de Regelen, Schreifzougang huet, zum Beispill d'Donnéeën vun engem erausginn Paket, net fäeg sinn d'Kernel Memory ausserhalb vum Paket ze iwwerschreiwe. Mir kucken de Verifizéierer e bësse méi am Detail an der entspriechender Rubrik, nodeems mir all déi aner Komponente vu BPF vertraut hunn.

Also wat hu mir bis elo geléiert? De Benotzer schreift e Programm am C, lued et an de Kernel mat engem Systemruff bpf(2), wou et vun engem Verifizéierer gepréift gëtt an an gebierteg Bytecode iwwersat gëtt. Dann verbënnt dee selwechten oder en anere Benotzer de Programm mat der Eventquell an et fänkt un auszeféieren. Trennung vun Boot a Verbindung ass aus verschiddene Grënn néideg. Éischtens, e Verifizéierer lafen ass relativ deier an andeems mir dee selwechte Programm e puer Mol eroflueden verschwende mir Computerzäit. Zweetens, genau wéi e Programm verbonnen ass hänkt vu senger Aart of, an eng "universell" Interface, déi virun engem Joer entwéckelt gouf, ass vläicht net gëeegent fir nei Zorte vu Programmer. (Obwuel elo datt d'Architektur méi reift gëtt, gëtt et eng Iddi fir dësen Interface um Niveau ze vereenegen libbpf.)

Den opgepasste Lieser mierkt vläicht datt mir nach net fäerdeg sinn mat de Biller. Tatsächlech erklärt all dat hei uewen net firwat BPF d'Bild grondsätzlech ännert am Verglach zum klassesche BPF. Zwee Innovatiounen, déi den Ëmfang vun der Uwendung wesentlech ausbauen, sinn d'Fäegkeet fir gemeinsame Gedächtnis a Kernel Helper Funktiounen ze benotzen. A BPF gëtt gedeelt Gedächtnis mat sougenannte Kaarten implementéiert - gedeelt Datenstrukture mat enger spezifescher API. Si kruten wahrscheinlech dësen Numm well déi éischt Zort Kaart déi erschéngt war en Hash-Tabelle. Dunn sinn Arrays erschéngen, lokal (per-CPU) Hash-Tabellen a lokal Arrays, Sichbeem, Kaarten mat Hiweiser op BPF Programmer a vill méi. Wat fir eis elo interessant ass, ass datt BPF Programmer elo d'Fäegkeet hunn den Zoustand tëscht Uriff ze behalen an et mat anere Programmer a mat Benotzerraum ze deelen.

Kaarten ass zougänglech vu Benotzerprozesser mat engem Systemruff bpf(2), a vu BPF Programmer déi am Kernel lafen mat Hëllefsfunktiounen. Ausserdeem existéieren Helfer net nëmme fir mat Kaarten ze schaffen, awer och fir Zougang zu anere Kernelfäegkeeten. Zum Beispill kënnen BPF Programmer Hëllefsfunktiounen benotzen fir Päckchen op aner Interfaces weiderzebréngen, Perf-Evenementer ze generéieren, Zougang zu Kernelstrukturen a sou weider.

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Zesummegefaasst bitt BPF d'Fäegkeet fir arbiträr ze lueden, dh Verifizéierer-getest, Benotzercode an de Kernelraum. Dëse Code kann de Staat tëscht Uriff späicheren an Daten mat Benotzerraum austauschen, an huet och Zougang zu Kernel-Subsystemer erlaabt vun dëser Aart vu Programm.

Dëst ass schonn ähnlech wéi d'Fäegkeeten, déi vu Kernelmoduler geliwwert ginn, am Verglach mat deem BPF e puer Virdeeler huet (natierlech kënnt Dir nëmmen ähnlech Uwendungen vergläichen, zum Beispill System Tracing - Dir kënnt net en arbiträre Chauffer mat BPF schreiwen). Dir kënnt e méi nidderegen Entrée-Schwell notéieren (e puer Utilities déi BPF benotzen erfuerderen de Benotzer net Kernelprogramméierungsfäegkeeten, oder Programméierungsfäegkeeten am Allgemengen), Runtime Sécherheet (hief Är Hand an de Kommentarer fir déi, déi de System net gebrach hunn beim Schreiwen oder Testmoduler), Atomitéit - et gëtt Ausdauer wann Dir Moduler nei lued, an de BPF-Subsystem garantéiert datt keng Eventer verpasst ginn (fir fair ze sinn, dat ass net wouer fir all Typ vu BPF Programmer).

D'Präsenz vun esou Fäegkeeten mécht BPF en universellt Tool fir de Kernel auszebauen, wat an der Praxis bestätegt ass: ëmmer méi nei Zorte vu Programmer ginn op BPF bäigefüügt, ëmmer méi grouss Firmen benotzen BPF op Kampfserver 24×7, ëmmer méi Startups bauen hiert Geschäft op Léisunge baséiert op deenen op BPF baséiert. BPF gëtt iwwerall benotzt: fir géint DDoS Attacken ze schützen, SDN ze kreéieren (zum Beispill d'Ëmsetzung vun Netzwierker fir kubernetes), als Haaptsystem Tracing Tool a Statistik Sammler, an Intrusiounserkennungssystemer a Sandbox Systemer, etc.

Loosst eis den Iwwersiichtsdeel vum Artikel hei ofschléissen a kucken d'virtuell Maschinn an de BPF-Ökosystem méi detailléiert.

Digressioun: Utilities

Fir d'Beispiller an de folgende Sektiounen auszeféieren, musst Dir op d'mannst e puer Utilitys brauchen llvm/clang mat bpf Ënnerstëtzung an bpftool... Am Kapitel Entwécklung Tools Dir kënnt d'Instruktioune liesen fir d'Utilities ze montéieren, souwéi Äre Kernel. Dës Sektioun ass hei ënnen gesat fir d'Harmonie vun eiser Presentatioun net ze stéieren.

BPF Virtuell Maschinn Registere an Instruktiounssystem

D'Architektur an de Kommandosystem vu BPF goufen entwéckelt andeems d'Tatsaach berücksichtegt datt d'Programmer an der C Sprooch geschriwwe ginn an, nodeems se an de Kär gelueden sinn, an de gebiertege Code iwwersat ginn. Dofir gouf d'Zuel vun de Registere an de Set vu Kommandoen gewielt mat engem Aen op d'Kräizung, am mathematesche Sënn, vun de Fäegkeeten vun de modernen Maschinnen. Zousätzlech goufen verschidde Restriktiounen op Programmer opgesat, zum Beispill, bis viru kuerzem war et net méiglech Schleifen a Subroutine ze schreiwen, an d'Zuel vun den Instruktiounen war limitéiert op 4096 (elo privilegiéiert Programmer kënne bis zu enger Millioun Instruktioune lueden).

BPF huet eelef Benotzer zougänglech 64-Bit Registere r0-r10 an engem Programm Konter. Aschreiwen r10 enthält e Frame Pointer an ass nëmme liesen. Programmer hunn Zougang zu engem 512-Byte Stack bei der Runtime an eng onlimitéiert Betrag u gemeinsame Gedächtnis a Form vu Kaarten.

BPF Programmer sinn erlaabt e spezifesche Set vu Programm-Typ Kernelhëllefer ze lafen a méi kierzlech regelméisseg Funktiounen. All genannte Funktioun kann bis zu fënnef Argumenter huelen, a Registere passéiert r1-r5, an de Retourwäert gëtt op r0. Et ass garantéiert datt nom Retour vun der Funktioun den Inhalt vun de Registere r6-r9 Wäert net änneren.

Fir efficace Programm Iwwersetzung, Registere r0-r11 fir all ënnerstëtzt Architektur sinn eendeiteg op real Registere gemoolt, Rechnung huelen d'ABI Fonctiounen vun der aktueller Architektur. Zum Beispill, fir x86_64 registréiert r1-r5, benotzt fir Funktiounsparameter ze passéieren, ginn ugewisen op rdi, rsi, rdx, rcx, r8, déi benotzt gi fir Parameteren op Funktiounen weiderzeginn x86_64. Zum Beispill, de Code op der lénker Säit iwwersetzt de Code op der rietser sou:

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

Registréiert r0 och benotzt fir d'Resultat vun der Ausféierung vum Programm zréckzekommen, an am Register r1 de Programm gëtt e Pointer op de Kontext iwwerginn - ofhängeg vun der Aart vum Programm kann dëst zum Beispill eng Struktur sinn struct xdp_md (fir XDP) oder Struktur struct __sk_buff (fir verschidden Netzwierkprogrammer) oder Struktur struct pt_regs (fir verschidden Zorte vu Tracing Programmer), etc.

Also hu mir e Set vu Registere, Kernelhëllefer, e Stack, e Kontextpointer a gemeinsame Gedächtnis a Form vu Kaarten. Net datt dat alles absolut néideg ass op der Rees, awer ...

Loosst eis d'Beschreiwung weidergoen a schwätzen iwwer de Kommandosystem fir mat dësen Objeten ze schaffen. All (Bal all) BPF Uweisungen hunn eng fix 64-bëssen Gréisst. Wann Dir eng Instruktioun op enger 64-Bit Big Endian Maschinn kuckt, gesitt Dir

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

et ass Code - dëst ass d'Kodéierung vun der Instruktioun, Dst/Src sinn d'Kodéierunge vum Empfänger respektiv Quell, Off - 16-bëssen ënnerschriwwen indentation, an Imm ass en 32-Bit ënnerschriwwen Ganzt, deen an e puer Instruktiounen benotzt gëtt (ähnlech wéi d'cBPF Konstante K). Kodéierung Code huet eng vun zwou Zorte:

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Instruktiounsklassen 0, 1, 2, 3 definéieren Kommandoe fir mat Erënnerung ze schaffen. Si ginn geruff, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, respektiv. Klassen 4, 7 (BPF_ALU, BPF_ALU64) bilden eng Rei vun ALU-Instruktiounen. Klassen 5, 6 (BPF_JMP, BPF_JMP32) sprangen Uweisungen enthalen.

Den zukünftege Plang fir de BPF Instruktiounssystem ze studéieren ass wéi follegt: amplaz all d'Instruktiounen an hir Parameteren virsiichteg opzeféieren, wäerte mir e puer Beispiller an dëser Rubrik kucken a vun hinnen wäert et kloer ginn wéi d'Instruktioune funktionnéieren a wéi een manuell all binär Datei fir BPF demontéieren. Fir d'Material méi spéit am Artikel ze konsolidéieren, wäerte mir och mat individuellen Instruktiounen an de Rubriken iwwer Verifier, JIT Compiler, Iwwersetzung vu klassesche BPF treffen, wéi och wann Dir Kaarten studéiert, rufft Funktiounen, etc.

Wa mir iwwer individuell Instruktioune schwätzen, bezéie mir op d'Kärdateien bpf.h и bpf_common.h, déi d'numeresch Coden vun BPF Instruktioune definéieren. Wann Dir Architektur eleng studéiert an / oder Binären parséiert, kënnt Dir Semantik an de folgende Quelle fannen, sortéiert an der Komplexitéitsuerdnung: Inoffiziell eBPF Spezifizéierung, BPF an XDP Referenz Guide, Instruktiounsset, Documentation/networking/filter.txt an, natierlech, am Linux Quellcode - Verifizéierer, JIT, BPF Dolmetscher.

Beispill: BPF an Ärem Kapp ofbauen

Loosst eis e Beispill kucken an deem mir e Programm kompiléieren readelf-example.c a kuckt op déi resultéierend binär. Mir wäerten den originalen Inhalt opzeweisen readelf-example.c drënner, nodeems mir seng Logik aus binäre Coden restauréieren:

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

Éischt Kolonn am Ausgang readelf ass en Indentatioun an eise Programm besteet also aus véier Kommandoen:

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

Kommando Coden sinn gläich b7, 15, b7 и 95. Erënneren, datt déi mannst bedeitendst dräi Stécker d'Instruktioun Klass sinn. An eisem Fall ass de véierte Bit vun all Instruktiounen eidel, sou datt d'Instruktiounsklassen 7, 5, 7, 5 respektiv sinn. BPF_ALU64,an 5 ass BPF_JMP. Fir béid Klassen ass d'Instruktiounsformat d'selwecht (kuckt hei uewen) a mir kënnen eise Programm esou nei schreiwen (gläichzäiteg wäerte mir déi verbleiwen Kolonnen a mënschlech Form ëmschreiwen):

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

Operatioun b Grad ALU64 Ass BPF_MOV. Et gëtt e Wäert un den Destinatiounsregister zou. Wann de Bit gesat ass s (Quell), da gëtt de Wäert aus dem Quellregister geholl, a wann, wéi an eisem Fall, et net gesat ass, da gëtt de Wäert aus dem Feld geholl Imm. Also an der éischter an drëtter Instruktioun maache mir d'Operatioun r0 = Imm. Weider, JMP Klass 1 Operatioun ass BPF_JEQ (sprangen wann gläich). An eisem Fall, zënter dem bëssen S ass null, et vergläicht de Wäert vum Quellregister mam Feld Imm. Wann d'Wäerter zesummekommen, da geschitt den Iwwergang zu PC + Offwou PC, wéi gewinnt, enthält d'Adress vun der nächster Instruktioun. Endlech, JMP Class 9 Operatioun ass BPF_EXIT. Dës Instruktioun schléisst de Programm of, geet zréck an de Kernel r0. Loosst eis eng nei Kolonn op eisen Dësch setzen:

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

Mir kënnen dëst an enger méi bequemer Form ëmschreiwen:

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

Wa mir erënneren wat am Register ass r1 de Programm gëtt e Pointer op de Kontext vum Kernel iwwerginn, an am Register r0 de Wäert gëtt an de Kärel zréckgezunn, da kënne mir gesinn datt wann de Pointer op de Kontext null ass, da gi mir 1 zréck, an soss - 2. Loosst eis kucken ob mir richteg sinn andeems Dir d'Quell kuckt:

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

Jo, et ass e sënnlose Programm, awer et iwwersetzt nëmme véier einfach Instruktiounen.

Ausnam Beispill: 16-Byte Uweisunge

Mir hunn virdru gesot datt e puer Instruktioune méi wéi 64 Bits ophuelen. Dëst gëllt zum Beispill fir d'Instruktioune lddw (Code = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - lued en duebelt Wuert aus de Felder an de Register Imm. D'Tatsaach ass dat Imm huet eng Gréisst pa 32, an engem duebel Wuert ass 64 bëssen, sou Luede engem 64-bëssen direkt Wäert an engem register an engem 64-bëssen Uweisunge wäert net schaffen. Fir dëst ze maachen, ginn zwee ugrenzend Instruktioune benotzt fir den zweeten Deel vum 64-Bit Wäert am Feld ze späicheren ImmAn. Beispill:

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

Et ginn nëmmen zwou Instruktiounen an engem binäre Programm:

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

Mir treffen eis erëm mat Instruktiounen lddw, wa mir iwwer Verlagerunge schwätzen a mat Kaarten schaffen.

Beispill: Demontage BPF mat Standard Tools

Also, mir hu geléiert BPF binär Coden ze liesen a si prett all Instruktioun ze analyséieren wann néideg. Wéi och ëmmer, et ass derwäert ze soen datt et an der Praxis méi bequem a méi séier ass fir Programmer mat Standard Tools ze disassemble, zum Beispill:

$ 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

Liewenszyklus vu BPF Objekter, bpffs Dateisystem

(Ech hunn als éischt e puer vun den Detailer geléiert, déi an dëser Ënnersektioun beschriwwe ginn posten Alexei Starovoitov an BPF Blog.)

BPF Objekter - Programmer a Kaarten - gi vu Benotzerraum erstallt mat Kommandoen BPF_PROG_LOAD и BPF_MAP_CREATE System Opruff bpf(2), Mir schwätzen iwwer genee wéi dëst an der nächster Rubrik geschitt. Dëst erstellt Kerneldatenstrukturen a fir jidderee vun hinnen refcount (Referenzzuel) ass op ee gesat, an e Dateideskriptor, deen op den Objet weist, gëtt dem Benotzer zréckginn. Nodeems de Grëff zougemaach ass refcount den Objet gëtt vun engem reduzéiert, a wann et null erreecht, gëtt den Objet zerstéiert.

Wann de Programm Kaarten benotzt, dann refcount dës Kaarte ginn no der Luede vum Programm ëm eng erhéicht, d.h. hir Fichier descriptors kann aus dem Benotzer Prozess zougemaach ginn an nach refcount wäert net null ginn:

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Nodeems mir e Programm erfollegräich gelueden hunn, befestigen mir et normalerweis un eng Zort Eventgenerator. Zum Beispill kënne mir et op engem Netzwierk setzen fir erakommen Pakete ze veraarbecht oder et mat e puer ze verbannen tracepoint am Kär. Zu dësem Zäitpunkt wäert de Referenzzähler och ëm een ​​eropgoen a mir kënnen de Dateideskriptor am Loaderprogramm zoumaachen.

Wat geschitt wa mir elo de Bootloader ausschalten? Et hänkt vun der Aart vum Eventgenerator (Hook) of. All Netzwierkhaken existéieren nodeems de Loader fäerdeg ass, dat sinn déi sougenannte global Haken. An, zum Beispill, Spuerprogrammer ginn verëffentlecht nodeems de Prozess deen se erstallt huet ofgeschloss ass (an dofir lokal genannt ginn, vu "lokal zum Prozess"). Technesch hunn lokal Haken ëmmer e entspriechende Dateideskriptor am Benotzerraum an dofir zou wann de Prozess zou ass, awer global Haken net. An der folgender Figur, mat roude Kräizer, probéieren ech ze weisen, wéi d'Kënnegung vum Luedeprogramm d'Liewensdauer vun Objeten am Fall vun lokalen a globalen Haken beaflosst.

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Firwat gëtt et en Ënnerscheed tëscht lokalen a globalen Haken? E puer Zorte vu Netzwierkprogrammer auszeféieren mécht Sënn ouni Userspace, zum Beispill, stellt Iech vir DDoS-Schutz - de Bootloader schreift d'Regele a verbënnt de BPF-Programm un d'Netzwierk-Interface, duerno kann de Bootloader goen a sech selwer ëmbréngen. Op der anerer Säit, stellt Iech en Debugging-Spuerprogramm vir, deen Dir an zéng Minutten op de Knéien geschriwwen hutt - wann et fäerdeg ass, wëllt Dir datt et keen Dreck am System bleift, a lokal Haken wäerten dat garantéieren.

Op der anerer Säit, stellt Iech vir datt Dir mat engem Tracepoint am Kärel verbannen wëllt a Statistike iwwer vill Joer sammelen. An dësem Fall wëllt Dir de Benotzerdeel fäerdeg maachen an vun Zäit zu Zäit op d'Statistiken zréckkommen. De bpf Dateisystem bitt dës Méiglechkeet. Et ass en In-Memory-nëmmen Pseudo-Datei System deen d'Schafe vu Dateien erlaabt déi BPF Objekter referenzéieren an doduerch eropgoen refcount Objeten. Duerno kann de Loader erausgoen, an d'Objeten déi se erstallt hunn bleiwen lieweg.

BPF fir déi Kleng, Deel XNUMX: verlängert BPF

Dateien an bpffs erstellen, déi BPF-Objete referenzéieren, gëtt "pinning" genannt (wéi an der folgender Ausdrock: "Prozess kann e BPF Programm oder Kaart pinnéieren"). D'Erstelle vun Dateiobjekte fir BPF Objete mécht Sënn net nëmme fir d'Liewen vun lokalen Objeten ze verlängeren, awer och fir d'Benotzerfrëndlechkeet vu globalen Objeten - zréck op d'Beispill mam globalen DDoS Schutzprogramm, mir wëllen fäeg sinn Statistiken ze kucken heiansdo.

De BPF Dateisystem ass normalerweis montéiert /sys/fs/bpf, awer et kann och lokal montéiert ginn, zum Beispill, sou:

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

Dateisystemnamen gi mam Kommando erstallt BPF_OBJ_PIN BPF System Opruff. Fir ze illustréieren, loosst eis e Programm huelen, et kompiléieren, eroplueden a festleeën bpffs. Eise Programm mécht näischt nëtzlech, mir presentéieren nëmmen de Code fir datt Dir d'Beispill reproduzéieren kënnt:

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

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

Loosst eis dëse Programm kompiléieren an eng lokal Kopie vum Dateiesystem erstellen bpffs:

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

Loosst eis elo eise Programm eroflueden mat dem Utility bpftool a kuckt op d'accordéiert System Appellen bpf(2) (e puer irrelevant Linnen aus der Strace Output ewechgeholl):

$ 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

Hei hu mir de Programm gelueden benotzt BPF_PROG_LOAD, krut e Fichier Descriptor vum Kernel 3 a benotzt de Kommando BPF_OBJ_PIN huet dëse Fichier Descriptor als Datei festgeluecht "bpf-mountpoint/test". Duerno ass de Bootloader Programm bpftool fäerdeg geschafft, awer eise Programm ass am Kernel bliwwen, obwuel mir et net un all Netzwierksinterface befestegt hunn:

$ 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

Mir kënnen de Fichier Objet normalerweis läschen unlink(2) an duerno gëtt de entspriechende Programm geläscht:

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

Objekter läschen

Schwätzen iwwer d'Objeten ze läschen, ass et néideg ze klären datt nodeems mir de Programm vum Hook (Event Generator) getrennt hunn, keen eenzegen neien Event säin Start ausléist, awer all aktuell Instanzen vum Programm ginn an der normaler Uerdnung ofgeschloss .

E puer Zorte vu BPF Programmer erlaben Iech de Programm op der Flucht z'ersetzen, d.h. Sequenz Atomitéit ubidden replace = detach old program, attach new program. An dësem Fall wäert all aktiv Instanzen vun der aler Versioun vum Programm hir Aarbecht fäerdeg, an nei Event Handler wäert aus dem neie Programm geschaf ginn, an "Atomicity" heescht hei, datt net eng eenzeg Manifestatioun verpasst ginn.

Befestegt Programmer op Eventquellen

An dësem Artikel wäerte mir d'Verbindungsprogrammer mat Eventquellen net separat beschreiwen, well et Sënn mécht dëst am Kontext vun enger spezifescher Zort Programm ze studéieren. Cm. Beispill ënnendrënner, an deem mir weisen wéi Programmer wéi XDP verbonne sinn.

Manipuléiere vun Objekter mam bpf System Call

BPF Programmer

All BPF Objete ginn erstallt a verwaltet aus dem Benotzerraum mat engem Systemruff bpf, mat de folgende Prototyp:

#include <linux/bpf.h>

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

Hei ass d'Equipe cmd ass ee vun de Wäerter vum Typ enum bpf_cmd, attr - e Pointer op Parameteren fir e spezifesche Programm an size — Objektgröße laut dem Pointer, d.h. normalerweis dëser sizeof(*attr). Am Kernel 5.8 rufft de System bpf ënnerstëtzt 34 verschidde Kommandoen, an Determinatioun vu union bpf_attr besetzt 200 Linnen. Mä mir sollten eis net dovunner ofschrecken, well mir eis am Laf vun e puer Artikele mat de Kommandoen a Parameteren vertraut ginn.

Loosst d'mat der Equipe ufänken BPF_PROG_LOAD, déi BPF Programmer erstellt - hëlt eng Rei vu BPF Instruktiounen a lued se an de Kernel. Am Moment vum Luede gëtt de Verifizéierer gestart, an dann de JIT Compiler an, no erfollegräicher Ausféierung, gëtt de Programmdatei Descriptor un de Benotzer zréckginn. Mir hu gesinn wat him duerno geschitt an der viregter Rubrik iwwer de Liewenszyklus vu BPF Objeten.

Mir schreiwen elo e personaliséierte Programm deen en einfachen BPF Programm lued, awer als éischt musse mir entscheeden wéi eng Zort Programm mir wëllen lueden - mir mussen auswielen Typ an am Kader vun dësem Typ, schreift e Programm deen de Verifizéierungstest passéiert. Wéi och ëmmer, fir de Prozess net ze komplizéieren, hei ass eng fäerdeg Léisung: mir huelen e Programm wéi BPF_PROG_TYPE_XDP, déi de Wäert zréckginn XDP_PASS (sprangen all Packagen). Am BPF Assembler gesäit et ganz einfach aus:

r0 = 2
exit

Nodeems mir decidéiert hunn op dass mir lueden erop, mir kënnen Iech soen wéi mir et maachen:

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

Interessant Eventer an engem Programm fänken un mat der Definitioun vun enger Array insns - eise BPF Programm am Maschinncode. An dësem Fall ass all Instruktioun vum BPF Programm an d'Struktur agepaakt bpf_insn. Éischt Element insns entsprécht den Instruktiounen r0 = 2, déi zweet - exit.

Réckzuch. De Kernel definéiert méi praktesch Makroen fir Maschinncoden ze schreiwen an d'Kernel Header Datei ze benotzen tools/include/linux/filter.h mir kéinten schreiwen

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

Awer well BPF Programmer am gebiertege Code schreiwen ass nëmme noutwendeg fir Tester am Kärel an Artikelen iwwer BPF ze schreiwen, komplizéiert d'Feele vun dëse Makroen net wierklech d'Liewen vum Entwéckler.

Nodeems mir de BPF Programm definéiert hunn, fuere mir weider fir en an de Kernel ze lueden. Eis minimalistesch Set vu Parameteren attr enthält de Programmtyp, Set an Zuel vun Instruktiounen, erfuerderlech Lizenz an Numm "woo", déi mir benotze fir eise Programm um System nom Download ze fannen. De Programm, wéi versprach, gëtt an de System gelueden mat engem Systemruff bpf.

Um Enn vum Programm komme mir an enger onendlecher Loop déi d'Notzlaascht simuléiert. Ouni et gëtt de Programm vum Kernel ëmbruecht wann de Dateideskriptor, deen de Systemruff un eis zréckgeet, zou ass bpf, a mir wäerten et net am System gesinn.

Gutt, mir si prett fir ze testen. Loosst eis de Programm ënnerhuelen a lafen stracefir ze kontrolléieren ob alles funktionnéiert wéi et soll:

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

Alles ass gutt, bpf(2) zréck Griff 3 fir eis a mir sinn an eng onendlech Loop mat pause(). Loosst eis probéieren eise Programm am System ze fannen. Fir dëst ze maachen gi mir op en aneren Terminal a benotzen d'Utility 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)

Mir gesinn datt et e geluedene Programm um System ass woo deem seng global ID 390 ass an am Moment amgaang ass simple-prog et gëtt en oppene Dateibeschreiwung deen op de Programm weist (a wann simple-prog wäert d'Aarbecht fäerdeg maachen, dann woo verschwannen). Wéi erwaart, de Programm woo hëlt 16 Bytes - zwee Instruktioune - vu binäre Coden an der BPF Architektur, awer a senger gebierteg Form (x86_64) ass et scho 40 Bytes. Loosst eis eise Programm a senger ursprénglecher Form kucken:

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

keng Iwwerraschungen. Elo kucke mer de Code generéiert vum JIT Compiler:

# 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

net ganz effektiv fir exit(2), mee fairerweis ass eise Programm ze einfach, a fir net-trivial Programmer sinn de Prolog an den Epilog, déi vum JIT Compiler bäigefüügt ginn, natierlech néideg.

Kaarten

BPF Programmer kënne strukturéiert Erënnerungsberäicher benotzen, déi souwuel fir aner BPF Programmer wéi och fir Programmer am Benotzerraum zougänglech sinn. Dës Objete ginn Kaarten genannt an an dëser Sektioun wäerte mir weisen wéi se se mat engem Systemruff manipuléieren bpf.

Loosst eis direkt soen datt d'Kapazitéite vu Kaarten net nëmme limitéiert sinn op Zougang zu gemeinsame Erënnerung. Et gi speziell Zweck Kaarte mat, zum Beispill, Hiweiser op BPF Programmer oder Hiweiser op Netzwierkschnëttplazen, Kaarten fir mat Perf Eventer ze schaffen, etc. Mir wäerten net iwwer hinnen hei schwätzen, fir de Lieser net duercherneen ze bréngen. Ausserdeem ignoréiere mir Synchroniséierungsprobleemer, well dëst fir eis Beispiller net wichteg ass. Eng komplett Lëscht vun verfügbare Kaartentypen fannt Dir an <linux/bpf.h>, an an dëser Rubrik wäerte mir als Beispill den historeschen éischten Typ huelen, den Hash-Tabelle BPF_MAP_TYPE_HASH.

Wann Dir en Hash-Table erstellt, sot, C ++, géift Dir soen unordered_map<int,long> woo, wat op Russesch heescht "Ech brauch en Dësch woo onlimitéiert Gréisst, deenen hir Schlësselen vun Typ sinn int, an d'Wäerter sinn den Typ long" Fir e BPF Hash Dësch ze kreéieren, musse mir vill datselwecht maachen, ausser datt mir déi maximal Gréisst vun der Tabell musse spezifizéieren, an amplaz d'Typen vu Schlësselen a Wäerter ze spezifizéieren, musse mir hir Gréissten a Bytes spezifizéieren . Fir Kaarten ze kreéieren benotzt de Kommando BPF_MAP_CREATE System Opruff bpf. Loosst eis e méi oder manner minimale Programm kucken, deen eng Kaart erstellt. Nom virege Programm deen BPF Programmer lued, sollt dësen Iech einfach schéngen:

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

Hei definéiere mir eng Rei vu Parameteren attr, an deem mir soen "Ech brauch en Hash-Table mat Schlësselen a Gréisst Wäerter sizeof(int), an deenen ech maximal véier Elementer ka setzen." Wann Dir BPF Kaarten erstellt, kënnt Dir aner Parameteren spezifizéieren, zum Beispill, op déiselwecht Manéier wéi am Beispill mam Programm, mir spezifizéieren den Numm vum Objet als "woo".

Loosst eis de Programm kompiléieren a lafen:

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

Hei ass de System Uruff bpf(2) huet eis d'Descriptor Kaartnummer zréckginn 3 an dann de Programm, wéi erwaart, gewaart weider Uweisungen am System Opruff pause(2).

Loosst eis elo eise Programm op den Hannergrond schécken oder en aneren Terminal opmaachen a kucken eisen Objet mat dem Utility bpftool (mir kënnen eis Kaart vun aneren duerch hiren Numm ënnerscheeden):

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

D'Nummer 114 ass déi global ID vun eisem Objet. All Programm um System kann dës ID benotzen fir eng existent Kaart opzemaachen mam Kommando BPF_MAP_GET_FD_BY_ID System Opruff bpf.

Elo kënne mir mat eisem Hash Dësch spillen. Loosst eis säin Inhalt kucken:

$ sudo bpftool map dump id 114
Found 0 elements

Eidel. Loosst eis e Wäert dran setzen hash[1] = 1:

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

Loosst eis den Dësch nach eng Kéier kucken:

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

Hour! Mir hunn et fäerdeg bruecht een Element ze addéieren. Notéiert datt mir um Byte-Niveau musse schaffen fir dëst ze maachen, zënter bptftool weess net wéi en Typ d'Wäerter an der Hash-Tabelle sinn. (Dëst Wëssen kann op hir mat BTF transferéiert ginn, awer méi doriwwer elo.)

Wéi genau liest a füügt bpftool Elementer un? Kucke mer ënnert der Hood:

$ 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

Als éischt hu mir d'Kaart opgemaach mat senger globaler ID mam Kommando BPF_MAP_GET_FD_BY_ID и bpf(2) eis descriptor 3 zréckginn. Weider de Kommando benotzt BPF_MAP_GET_NEXT_KEY mir fonnt den éischte Schlëssel an der Tabell duerch laanschtgoungen NULL als Hiweis op de "virdrun" Schlëssel. Wa mir de Schlëssel hunn, kënne mir maachen BPF_MAP_LOOKUP_ELEMdéi e Wäert op e Pointer zréckginn value. De nächste Schrëtt ass datt mir probéieren dat nächst Element ze fannen andeems Dir e Pointer op den aktuelle Schlëssel passéiert, awer eis Tabell enthält nëmmen een Element an de Kommando BPF_MAP_GET_NEXT_KEY geet zréck ENOENT.

Okay, loosst eis de Wäert mam Schlëssel 1 änneren, loosst eis soen datt eis Geschäftslogik registréiert 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

Wéi erwaart ass et ganz einfach: de Kommando BPF_MAP_GET_FD_BY_ID mécht eis Kaart op ID op, an de Kommando BPF_MAP_UPDATE_ELEM iwwerschreift d'Element.

Also, nodeems Dir en Hash-Tabelle vun engem Programm erstallt hutt, kënne mir säin Inhalt vun engem aneren liesen a schreiwen. Bedenkt datt wa mir et konnten aus der Kommandozeil maachen, da kann all aner Programm um System et maachen. Zousätzlech zu den uewe beschriwwenen Kommandoen, fir mat Kaarten aus dem Benotzerraum ze schaffen, de folgenden:

  • BPF_MAP_LOOKUP_ELEM: fannen Wäert vun Schlëssel
  • BPF_MAP_UPDATE_ELEM: update / schafen Wäert
  • BPF_MAP_DELETE_ELEM: Schlëssel ewechhuelen
  • BPF_MAP_GET_NEXT_KEY: fannen déi nächst (oder éischt) Schlëssel
  • BPF_MAP_GET_NEXT_ID: erlaabt Iech duerch all bestehend Kaarten ze goen, dat ass wéi et funktionnéiert bpftool map
  • BPF_MAP_GET_FD_BY_ID: eng existent Kaart opmaachen mat senger globaler ID
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: Update de Wäert vun engem Objet atomesch an zréck déi al
  • BPF_MAP_FREEZE: Maacht d'Kaart onverännerbar vum Benotzerraum (dës Operatioun kann net réckgängeg gemaach ginn)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: Mass Operatiounen. Zum Beispill, BPF_MAP_LOOKUP_AND_DELETE_BATCH - Dëst ass deen eenzegen zouverléissege Wee fir all Wäerter vun der Kaart ze liesen an zréckzesetzen

Net all dës Befehle funktionnéieren fir all Kaarttypen, awer am Allgemengen, mat aneren Aarte vu Kaarten aus dem Benotzerraum ze schaffen, gesäit genee d'selwecht aus wéi mat Hashtabellen ze schaffen.

Fir d'Wuel vun der Uerdnung, loosst eis eis Hash-Table Experimenter ofschléissen. Denkt drun datt mir en Dësch erstallt hunn dee bis zu véier Schlësselen enthalen kann? Loosst eis e puer méi Elementer derbäi:

$ 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

Sou wäit sou gutt:

$ 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

Loosst eis probéieren nach eng derbäi ze ginn:

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

Wéi erwaart hu mir et net gelongen. Loosst eis de Feeler méi detailléiert kucken:

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

Alles ass gutt: wéi erwaart, d'Equipe BPF_MAP_UPDATE_ELEM probéiert eng nei ze schafen, fënneften, Schlëssel, mee Crash E2BIG.

Also kënne mir BPF Programmer erstellen a lueden, souwéi Kaarte vum Benotzerraum erstellen a verwalten. Elo ass et logesch ze kucken wéi mir Kaarte vun de BPF Programmer selwer kënne benotzen. Mir kéinten doriwwer schwätzen an der Sprooch vun schwéier liesbare Programmer a Maschinn Makro Coden, awer tatsächlech ass d'Zäit komm fir ze weisen wéi BPF Programmer tatsächlech geschriwwe ginn an ënnerhalen - benotzt libbpf.

(Fir Lieser déi onzefridden sinn mam Mangel un engem nidderegen Niveau Beispill: mir analyséieren am Detail Programmer déi Kaarten an Hëllefsfunktiounen benotzen, erstallt mat libbpf a soen Iech wat um Instruktiounsniveau geschitt. Fir Lieser déi onzefridden sinn ganz vill, mir dobäi Beispill op der entspriechender Plaz am Artikel.)

Schreiwen BPF Programmer mat libbpf

BPF Programmer schreiwen mat Maschinncoden kann nëmmen déi éischte Kéier interessant sinn, an da setzt d'Sättigkeet an. Zu dësem Zäitpunkt musst Dir Är Opmierksamkeet op llvm, deen e Backend huet fir Code fir d'BPF Architektur ze generéieren, souwéi eng Bibliothéik libbpf, wat Iech erlaabt d'Benotzersäit vu BPF Uwendungen ze schreiwen an de Code vu BPF Programmer ze lueden, déi mat llvm/clang.

Tatsächlech, wéi mir an dësem a folgenden Artikelen gesinn, libbpf mécht zimlech vill Aarbecht ouni et (oder ähnlech Tools - iproute2, libbcc, libbpf-go, etc.) et ass onméiglech ze liewen. Ee vun de Killer Feature vum Projet libbpf ass BPF CO-RE (Compile Once, Run Everywhere) - e Projet deen Iech erlaabt BPF Programmer ze schreiwen déi portabel vun engem Kernel an en anert sinn, mat der Fäegkeet fir op verschidden APIen ze lafen (zum Beispill wann d'Kernelstruktur vun der Versioun ännert) op Versioun). Fir mat CO-RE ze schaffen, muss Äre Kernel mat BTF-Ënnerstëtzung kompiléiert ginn (mir beschreiwen wéi Dir dëst an der Sektioun maacht Entwécklung Tools. Dir kënnt kontrolléieren ob Äre Kernel mat BTF gebaut ass oder net ganz einfach - duerch d'Präsenz vun der folgender Datei:

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

Dës Datei späichert Informatioun iwwer all Datentypen déi am Kernel benotzt ginn a gëtt an all eise Beispiller benotzt libbpf. Mir wäerten am Detail iwwer CO-RE am nächsten Artikel schwätzen, awer an dësem - baut Iech just e Kernel mat CONFIG_DEBUG_INFO_BTF.

Bibliothéik libbpf Liewen direkt am Verzeechnes tools/lib/bpf Kernel a seng Entwécklung gëtt duerch d'Mailing Lëscht duerchgefouert [email protected]. Wéi och ëmmer, e separaten Repository gëtt fir d'Bedierfnesser vun Uwendungen erhale gelooss, déi ausserhalb vum Kernel liewen https://github.com/libbpf/libbpf an där d'Kernelbibliothéik gespigelt ass fir Lieszougang méi oder manner wéi ass.

An dëser Rubrik wäerte mir kucken wéi Dir e Projet erstellt deen benotzt libbpf, loosst eis e puer (méi oder manner sënnlos) Testprogrammer schreiwen an am Detail analyséieren wéi dat alles funktionéiert. Dëst erlaabt eis méi einfach an de folgende Sektiounen genau z'erklären wéi BPF Programmer mat Kaarten, Kernelhëllefer, BTF, etc.

Typesch Projeten benotzt libbpf e GitHub Repository als Git Submodul fügen, mir maachen datselwecht:

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

Goen libbpf ganz einfach:

$ 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

Eise nächste Plang an dëser Sektioun ass wéi follegt: mir schreiwen e BPF Programm wéi BPF_PROG_TYPE_XDP, d'selwecht wéi am virege Beispill, awer am C kompiléiere mir et mat clang, a schreift en Helperprogramm deen et an de Kernel lued. An de folgende Sektioune wäerte mir d'Fäegkeete vum BPF Programm an dem Assistentprogramm ausbauen.

Beispill: eng vollwäerteg Applikatioun erstellen mat libbpf

Fir unzefänken, benotze mir d'Datei /sys/kernel/btf/vmlinux, déi uewen ernimmt gouf, a erstellt säin Äquivalent a Form vun enger Headerdatei:

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

Dës Datei späichert all d'Datestrukturen, déi an eisem Kernel verfügbar sinn, zum Beispill, sou ass den IPv4 Header am Kernel definéiert:

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

Elo schreiwen mir eise BPF Programm am 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";

Och wann eise Programm ganz einfach ass, musse mir nach op vill Detailer oppassen. Als éischt ass déi éischt Header Datei déi mir enthalen vmlinux.h, déi mir just generéiert benotzt bpftool btf dump - elo brauche mir net de Kernel-Header Package z'installéieren fir erauszefannen wéi d'Kernelstrukturen ausgesinn. Déi folgend Headerdatei kënnt bei eis aus der Bibliothéik libbpf. Elo brauche mer et just fir de Makro ze definéieren SEC, déi de Charakter an déi entspriechend Sektioun vun der ELF Objektdatei schéckt. Eise Programm steet an der Rubrik xdp/simple, wou virum Slash mir de Programmtyp BPF definéieren - dat ass d'Konventioun déi an libbpf, baséiert op dem Sektiounsnumm gëtt et de richtegen Typ beim Start ersat bpf(2). De BPF Programm selwer ass C - ganz einfach a besteet aus enger Linn return XDP_PASS. Endlech eng separat Sektioun "license" enthält den Numm vun der Lizenz.

Mir kënnen eise Programm kompiléieren mat llvm/clang, Versioun >= 10.0.0, oder besser nach, méi grouss (kuckt Sektioun Entwécklung Tools):

$ 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

Ënnert den interessanten Features: Mir weisen d'Zilarchitektur un -target bpf an de Wee op d'Header libbpf, déi mir viru kuerzem installéiert hunn. Och, vergiesst net iwwer -O2, ouni dës Optioun kënnt Dir an Zukunft fir Iwwerraschungen sinn. Loosst eis eise Code kucken, hu mir et fäerdeg bruecht de Programm ze schreiwen dee mir wollten?

$ 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

Jo, et huet geschafft! Elo hu mir eng binär Datei mam Programm, a mir wëllen eng Applikatioun erstellen déi se an de Kernel lued. Fir dësen Zweck d'Bibliothéik libbpf bitt eis zwou Méiglechkeeten - benotzt eng méi niddereg-Niveau API oder eng méi héich-Niveau API. Mir wäerten den zweete Wee goen, well mir wëlle léiere wéi BPF Programmer mat minimalem Effort schreiwen, lueden a verbannen fir hir spéider Studie.

Als éischt musse mir de "Skelett" vun eisem Programm aus senger Binär generéieren mat deemselwechten Utility bpftool - de Schwäizer Messer vun der BPF Welt (wat wuertwiertlech geholl ka ginn, well den Daniel Borkman, ee vun de Grënner an Ënnerhalter vu BPF, Schwäizer ass):

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

Am Dossier xdp-simple.skel.h enthält de binäre Code vun eisem Programm a Funktiounen fir d'Gestioun - Lueden, Befestigung, läschen eisen Objet. An eisem einfache Fall gesäit dat aus wéi Iwwerkill, awer et funktionnéiert och am Fall wou d'Objetdatei vill BPF Programmer a Kaarten enthält a fir dëse Ris ELF ze lueden brauche mir just de Skelett ze generéieren an eng oder zwou Funktiounen aus der personaliséierter Applikatioun ze ruffen. schreiwen Loosst eis elo weidergoen.

Streng geschwat ass eise Loaderprogramm trivial:

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

et ass struct xdp_simple_bpf an der Datei definéiert xdp-simple.skel.h a beschreift eis Objektdatei:

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

Mir kënnen Spure vun engem nidderegen Niveau API hei gesinn: d'Struktur struct bpf_program *simple и struct bpf_link *simple. Déi éischt Struktur beschreift speziell eise Programm, geschriwwen an der Rubrik xdp/simple, an déi zweet beschreift wéi de Programm mat der Eventquell verbënnt.

Funktioun xdp_simple_bpf__open_and_load, mécht en ELF Objet op, parséiert et, erstellt all d'Strukturen an Ënnerstrukturen (nieft dem Programm enthält ELF och aner Sektiounen - Daten, nëmmen liest Daten, Debugging Informatioun, Lizenz, etc.), a lued se dann an de Kernel mat engem System ruffen bpf, déi mir iwwerpréiwen andeems Dir de Programm kompiléiert an ausféiert:

$ 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

Loosst eis elo op eise Programm kucken bpftool. Loosst eis hir ID fannen:

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

an dump (mir benotzen eng verkierzte Form vum Kommando bpftool prog dump xlated):

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

Eppes Neies! De Programm huet Stécker vun eiser C Quelldatei gedréckt. Dëst gouf vun der Bibliothéik gemaach libbpf, deen d'Debugsektioun am Binär fonnt huet, et an e BTF-Objet kompiléiert huet, an de Kernel gelueden mat BPF_BTF_LOAD, a spezifizéiert dann de resultéierende Dateideskriptor beim Luede vum Programm mam Kommando BPG_PROG_LOAD.

Kernel Helfer

BPF Programmer kënnen "extern" Funktiounen ausféieren - Kernelhëllefer. Dës Hëllefsfunktiounen erlaben BPF Programmer Zougang zu Kernelstrukturen, Kaarten ze verwalten, an och mat der "real Welt" ze kommunizéieren - Perf Eventer erstellen, Hardware ze kontrolléieren (zum Beispill Redirect Päckchen), etc.

Beispill: bpf_get_smp_processor_id

Am Kader vum "Léieren duerch Beispill" Paradigma, loosst eis eng vun den Helperfunktiounen betruechten, bpf_get_smp_processor_id(), bestëmmt am Fichier kernel/bpf/helpers.c. Et gëtt d'Nummer vum Prozessor zréck op deem de BPF Programm deen et genannt huet leeft. Awer mir sinn net esou interesséiert a senger Semantik wéi an der Tatsaach datt seng Ëmsetzung eng Linn hëlt:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

D'BPF Helper Funktioun Definitioune sinn ähnlech wéi de Linux System Call Definitiounen. Hei gëtt zum Beispill eng Funktioun definéiert déi keng Argumenter huet. (Eng Funktioun déi dräi Argumenter hëlt ass definéiert mam Macro BPF_CALL_3. Déi maximal Unzuel vun Argumenter ass fënnef.) Dëst ass awer nëmmen den éischten Deel vun der Definitioun. Den zweeten Deel ass d'Typ Struktur ze definéieren struct bpf_func_proto, déi eng Beschreiwung vun der Helperfunktioun enthält déi Verifizéierer versteet:

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

Enregistréiere Helper Funktiounen

Fir datt BPF Programmer vun engem bestëmmten Typ dës Funktioun benotzen, musse se se registréieren, zum Beispill fir den Typ BPF_PROG_TYPE_XDP eng Funktioun ass am Kärel definéiert xdp_func_proto, déi vun der Hëllefsfunktioun ID bestëmmt ob XDP dës Funktioun ënnerstëtzt oder net. Eis Funktioun ass ënnerstëtzt:

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

Nei BPF Programmtypen ginn an der Datei "definéiert". include/linux/bpf_types.h mat engem Macro BPF_PROG_TYPE. Definéiert an Zitater well et eng logesch Definitioun ass, an an C Sprooche Begrëffer d'Definitioun vun enger ganzer Rei vu konkret Strukturen geschitt op anere Plazen. Besonnesch am Dossier kernel/bpf/verifier.c all Definitiounen aus Fichier bpf_types.h gi benotzt fir eng Rei vu Strukturen ze kreéieren 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
};

Dat ass, fir all Typ vu BPF Programm gëtt e Pointer op eng Datestruktur vum Typ definéiert struct bpf_verifier_ops, déi mam Wäert initialiséiert gëtt _name ## _verifier_ops, d.h. xdp_verifier_ops fir xdp... Struktur xdp_verifier_ops bestëmmt vum am Fichier net/core/filter.c wéi folgend:

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

Hei gesi mir eis vertraute Funktioun xdp_func_proto, déi de Verifizéierer lafen all Kéier wann et eng Erausfuerderung begéint eng Aart Funktiounen bannent engem BPF Programm, kuckt verifier.c.

Loosst eis kucken wéi en hypotheteschen BPF Programm d'Funktioun benotzt bpf_get_smp_processor_id. Fir dëst ze maachen, schreiwen mir de Programm aus eiser viregter Sektioun wéi follegt:

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

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

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

Symbol bpf_get_smp_processor_id bestëmmt vum в <bpf/bpf_helper_defs.h> Bibliothéiken libbpf wéi

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

dat ass, bpf_get_smp_processor_id ass e Funktiounszeiger deem säi Wäert 8 ass, wou 8 de Wäert ass BPF_FUNC_get_smp_processor_id Zort enum bpf_fun_id, déi fir eis am Fichier definéiert ass vmlinux.h (Datei bpf_helper_defs.h am Kärel gëtt vun engem Skript generéiert, sou datt d'"Magie" Zuelen ok sinn). Dës Funktioun hëlt keng Argumenter a gëtt e Wäert vun Typ zréck __u32. Wa mir et an eisem Programm lafen, clang generéiert eng Instruktioun BPF_CALL "déi richteg Aart" Loosst eis de Programm kompiléieren a kucken d'Sektioun 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

An der éischter Zeil gesi mir Instruktiounen call, Parameter IMM déi gläich ass 8, an SRC_REG - null. Geméiss dem ABI Vertrag benotzt vum Verifizéierer, ass dëst en Uruff un d'Hëlleffunktioun Nummer aacht. Wann et lancéiert ass, ass d'Logik einfach. Retour Wäert vum Register r0 kopéiert op r1 an op Linnen 2,3 et ëmgewandelt Typ u32 - déi iewescht 32 Bits ginn geläscht. Op Linnen 4,5,6,7 gi mir zréck 2 (XDP_PASSoder 1 (XDP_DROP) ofhängeg ob d'Hëlleffunktioun vun der Linn 0 en Null- oder Net-Nullwäert zréckginn.

Loosst eis eis testen: lued de Programm a kuckt d'Ausgab 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, Verifizéierer huet de richtege Kernel-Helfer fonnt.

Beispill: Argumenter laanschtgoen an endlech de Programm lafen!

All Run-Level Helper Funktiounen hunn e Prototyp

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

Parameteren fir Hëllefsfunktiounen ginn a Registere weidergeleet r1-r5, an de Wäert gëtt am Register zréckginn r0. Et gi keng Funktiounen déi méi wéi fënnef Argumenter huelen, an Ënnerstëtzung fir si gëtt erwaart net an Zukunft dobäi ze ginn.

Loosst eis den neie Kernelhelfer kucken a wéi BPF Parameter passéiert. Loosst eis ëmschreiwen xdp-simple.bpf.c wéi follegt (de Rescht vun den Zeilen hunn net geännert):

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

Eise Programm dréckt d'Zuel vun der CPU op där se leeft. Loosst eis et kompiléieren a kucken de Code:

$ 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

An den Zeilen 0-7 schreiwen mir d'String running on CPU%un, an dann op der Linn 8 lafe mir dee bekannten bpf_get_smp_processor_id. Op de Linnen 9-12 preparéiere mir d'Hëllefargumenter bpf_printk - Registere r1, r2, r3. Firwat sinn et dräi vun hinnen an net zwee? Well bpf_printkdëst ass e Macro Wrapper ronderëm de richtege Helfer bpf_trace_printk, déi d'Gréisst vun der Formatstring muss passéieren.

Loosst eis elo e puer Zeilen derbäi xdp-simple.csou datt eise Programm un den Interface verbënnt lo a wierklech ugefaang!

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

Hei benotze mir d'Funktioun bpf_set_link_xdp_fd, déi XDP-Typ BPF Programmer mat Netzwierkschnëttplazen verbënnt. Mir hardcoded der Interface Zuel lo, wat ëmmer 1. Mir lafen d'Funktioun zweemol fir éischt den ale Programm ofzeschléissen, wann et ugeschloss ass. Notéiert datt mir elo keng Erausfuerderung brauchen pause oder eng onendlech Loop: eise Loaderprogramm geet eraus, awer de BPF Programm gëtt net ëmbruecht well et mat der Eventquell verbonnen ass. Nom erfollegräichen Download a Verbindung gëtt de Programm fir all Netzwierkpaket gestart, deen ukommt lo.

Loosst eis de Programm eroflueden an d'Interface kucken 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

De Programm dee mir erofgelueden hunn ID 669 a mir gesinn déi selwecht ID op der Interface lo. Mir schécken e puer Packagen un 127.0.0.1 (Ufro + Äntwert):

$ ping -c1 localhost

an elo kucke mer den Inhalt vun der virtueller Debug Datei /sys/kernel/debug/tracing/trace_pipe, an deem bpf_printk schreift seng Messagen:

# 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

Zwee Pakete goufe gesinn lo a veraarbecht op CPU0 - eisen éischte vollwäertege sënnlosen BPF Programm huet geschafft!

Et ass derwäert ze bemierken datt bpf_printk Et ass net fir näischt datt et an d'Debug-Datei schreift: dëst ass net den erfollegräichsten Helfer fir an der Produktioun ze benotzen, awer eist Zil war eppes einfach ze weisen.

Zougang zu Kaarten vu BPF Programmer

Beispill: eng Kaart aus dem BPF Programm benotzen

An de fréiere Sektiounen hu mir geléiert wéi Dir Kaarten aus dem Benotzerraum erstellt a benotzt, a kucke mer elo de Kernel Deel. Fänke mer wéi gewinnt mat engem Beispill un. Loosst eis eise Programm ëmschreiwen xdp-simple.bpf.c wéi folgend:

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

Am Ufank vum Programm hu mir eng Kaartdefinitioun bäigefüügt woo: Dëst ass eng 8-Elementarray déi Wäerter späichert wéi u64 (am C wäerte mir sou eng Array definéieren wéi u64 woo[8]). An engem Programm "xdp/simple" mir kréien déi aktuell Prozessor Zuel an eng Variabel key an dann d'Hëlleffunktioun benotzen bpf_map_lookup_element mir kréien e Pointer op déi entspriechend Entrée an der Array, déi mir ëm een ​​erhéijen. Op Russesch iwwersat: mir berechent Statistiken iwwer wéi eng CPU erakommende Päck veraarbecht huet. Loosst eis probéieren de Programm ze lafen:

$ 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

Loosst eis iwwerpréiwen datt hatt ugeschloss ass lo a schéckt e puer Päck:

$ 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

Loosst eis elo den Inhalt vun der Array kucken:

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

Bal all Prozesser goufen op CPU7 veraarbecht. Dëst ass net wichteg fir eis, den Haapt Saach ass datt de Programm funktionnéiert a mir verstinn wéi Zougang zu Kaarten vu BPF Programmer kënnt - benotzt хелперов bpf_mp_*.

Mystesch Index

Also, mir kënnen Zougang zu der Kaart vum BPF Programm benotze mat Uriff wéi

val = bpf_map_lookup_elem(&woo, &key);

wou d'Hëlleffunktioun ausgesäit

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

mee mir ginn e Pointer laanscht &woo op eng onbenannt Struktur struct { ... }...

Wa mir de Programm Assembler kucken, gesi mir datt de Wäert &woo ass net tatsächlech definéiert (Linn 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
...

an ass a Relocatioune enthale:

$ 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

Awer wa mir de scho geluedene Programm kucken, gesi mir e Pointer op déi richteg Kaart (Linn 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]
...

Also kënne mir schléissen datt zum Zäitpunkt vun der Start vun eisem Loaderprogramm de Link op &woo gouf duerch eppes mat enger Bibliothéik ersat libbpf. Als éischt wäerte mir d'Ausgab kucken 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

Mir gesinn dat libbpf eng Kaart erstallt woo an dann eise Programm erofgelueden simple. Loosst eis méi no kucken wéi mir de Programm lueden:

  • ruffen xdp_simple_bpf__open_and_load aus Fichier xdp-simple.skel.h
  • déi bewierkt xdp_simple_bpf__load aus Fichier xdp-simple.skel.h
  • déi bewierkt bpf_object__load_skeleton aus Fichier libbpf/src/libbpf.c
  • déi bewierkt bpf_object__load_xattr aus libbpf/src/libbpf.c

Déi lescht Funktioun, ënner anerem, wäert ruffen bpf_object__create_maps, déi existéierend Kaarten erstellt oder opmaacht, se an Dateideskriptoren ëmgewandelt. (Dëst ass wou mir gesinn BPF_MAP_CREATE am Ausgang strace.) Nächst gëtt d'Funktioun genannt bpf_object__relocate an et ass hatt, déi eis interesséiert, well mir erënneren wat mir gesinn hunn woo an der Ëmsetzung Dësch. Entdeckt et, fanne mir eis schlussendlech an der Funktioun bpf_program__relocate, déi beschäftegt sech mat Kaartverlagerungen:

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

Also mir huelen eis Instruktiounen

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

an ersetzt d'Quellregister dran mat BPF_PSEUDO_MAP_FD, an den éischten IMM zum Dateideskriptor vun eiser Kaart a wann et gläich ass, zum Beispill, 0xdeadbeef, da kréie mir als Resultat d'Instruktioun

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

Dëst ass wéi d'Kaartinformatioun op e spezifesche geluedene BPF Programm transferéiert gëtt. An dësem Fall kann d'Kaart benotzt ginn BPF_MAP_CREATE, an opgemaach vun ID benotzt BPF_MAP_GET_FD_BY_ID.

Ganzen, wann Dir benotzt libbpf den Algorithmus ass wéi follegt:

  • während der Zesummesetzung ginn records an der Verlagerungstabell fir Linken op Kaarten erstallt
  • libbpf mécht d'ELF Objektbuch op, fënnt all benotzte Kaarten a kreéiert Dateideskriptorer fir si
  • Dateibeschreiwunge ginn an de Kernel als Deel vun der Instruktioun gelueden LD64

Wéi Dir Iech kënnt virstellen, ass et méi ze kommen a mir mussen an de Kär kucken. Glécklecherweis hu mir en Hiweis - mir hunn d'Bedeitung opgeschriwwen BPF_PSEUDO_MAP_FD an d'Quellregister a mir kënnen et begruewen, wat eis zum Hellege vun allen Hellegen féiert - kernel/bpf/verifier.c, wou eng Funktioun mat engem markanten Numm e Fichier Descriptor mat der Adress vun enger Struktur vun Typ ersetzt 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;

(voll Code kann fonnt ginn Link). Also kënne mir eisen Algorithmus ausbauen:

  • beim Luede vum Programm kontrolléiert de Verifizéierer déi richteg Notzung vun der Kaart a schreift d'Adress vun der entspriechender Struktur struct bpf_map

Wann Dir den ELF binär eroflueden benotzt libbpf Et gëtt vill méi lass, awer mir wäerten dat an aneren Artikelen diskutéieren.

Luede Programmer a Kaarten ouni libbpf

Wéi versprach, hei ass e Beispill fir Lieser déi wësse wëllen wéi een e Programm erstellt a lued deen Kaarten benotzt, ouni Hëllef libbpf. Dëst kann nëtzlech sinn wann Dir an engem Ëmfeld schafft, fir deen Dir keng Ofhängegkeete bauen kënnt, oder all Stéck späichert, oder e Programm schreift wéi ply, déi BPF binäre Code op der Flucht generéiert.

Fir et méi einfach ze maachen d'Logik ze verfollegen, wäerte mir eist Beispill fir dës Zwecker ëmschreiwen xdp-simple. De komplette a liicht erweiderten Code vum Programm, deen an dësem Beispill diskutéiert gëtt, kann an dësem fonnt ginn huel.

D'Logik vun eiser Applikatioun ass wéi follegt:

  • eng Zort Kaart erstellen BPF_MAP_TYPE_ARRAY de Kommando benotzt BPF_MAP_CREATE,
  • erstellt e Programm deen dës Kaart benotzt,
  • konnektéieren de Programm un den Interface lo,

déi iwwersetze Mënsch als

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

et ass map_create erstellt eng Kaart op déiselwecht Manéier wéi mir am éischte Beispill iwwer de Systemruff gemaach hunn bpf - "Kernel, maacht mir w.e.g. eng nei Kaart a Form vun enger Array vun 8 Elementer wéi __u64 a gitt mir de Dateibeschreiwung zréck":

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

De Programm ass och einfach ze lueden:

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

De schwieregen Deel prog_load ass d'Definitioun vun eisem BPF Programm als eng Rei vu Strukturen struct bpf_insn insns[]. Awer well mir e Programm benotzen dee mir am C hunn, kënne mir e bësse fuddelen:

$ 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

Am Ganzen musse mir 14 Instruktioune schreiwen a Form vu Strukturen wéi struct bpf_insn (Tipp: huelt den Dump vun uewen, liest d'Instruktioune Sektioun erëm, oppen linux/bpf.h и linux/bpf_common.h a probéieren ze bestëmmen struct bpf_insn insns[] eleng):

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

Eng Übung fir déi, déi dëst net selwer geschriwwen hunn - fannen map_fd.

Et gëtt nach een onbekannten Deel an eisem Programm - xdp_attach. Leider kënne Programmer wéi XDP net mat engem Systemruff verbonne ginn bpf. D'Leit, déi BPF an XDP erstallt hunn, ware vun der Online Linux Gemeinschaft, dat heescht datt se deen am meeschte vertraut hunn (awer net fir normal Leit) Interface fir mam Kernel ze interagéieren: netlink Sockets, kuck och RFC3549. Deen einfachste Wee fir ëmzesetzen xdp_attach kopéiert Code vun libbpf, nämlech aus der Datei netlink.c, dat ass wat mir gemaach hunn, et e bësse verkierzt:

Wëllkomm op der Welt vun Netlink Sockets

Öffnen en Netlink Socket Typ 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;
}

Mir liesen aus dësem 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;
}

Endlech, hei ass eis Funktioun déi e Socket opmaacht an e spezielle Message un et schéckt mat engem Dateibeschreiwung:

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

Also ass alles prett fir ze testen:

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

Loosst eis kucken ob eise Programm ugeschloss huet 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

Loosst eis Pings schécken a kucken op d'Kaart:

$ 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

Hurra, alles funktionnéiert. Notéiert iwwregens datt eis Kaart erëm a Form vu Bytes ugewise gëtt. Dëst ass wéinst der Tatsaach, datt am Géigesaz libbpf mir hunn net Typ Informatiounen lued (BTF). Mee mir wäerten d'nächst Kéier méi doriwwer schwätzen.

Entwécklung Tools

An dëser Sektioun wäerte mir de Minimum BPF Entwéckler Toolkit kucken.

Allgemeng brauch Dir näischt Besonnesches fir BPF Programmer z'entwéckelen - BPF leeft op all anstänneg Verdeelungskern, a Programmer gi gebaut mat clang, déi aus dem Package geliwwert kënne ginn. Wéi och ëmmer, wéinst der Tatsaach datt BPF ënner Entwécklung ass, änneren de Kernel an Tools dauernd, wann Dir BPF Programmer net wëllt schreiwen mat almoudesche Methoden vun 2019, da musst Dir kompiléieren

  • llvm/clang
  • pahole
  • säi Kär
  • bpftool

(Fir Referenz, dës Sektioun an all Beispiller am Artikel goufen op Debian 10 lafen.)

llvm/klang

BPF ass frëndlech mat LLVM an, obwuel viru kuerzem Programmer fir BPF mat gcc kompiléiert kënne ginn, gëtt all aktuell Entwécklung fir LLVM duerchgefouert. Dofir, als éischt, wäerte mir déi aktuell Versioun bauen clang vum 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
... много времени спустя
$

Elo kënne mir kucken ob alles richteg zesummegefaasst ass:

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

(Versammlungsinstruktiounen clang vu mir geholl aus bpf_devel_QA.)

Mir wäerten d'Programmer net installéieren, déi mir just gebaut hunn, mee amplaz se einfach derbäi PATH, zum Beispill:

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

(Dëst kann derbäigesat ginn .bashrc oder op eng separat Datei. Perséinlech addéieren ech esou Saachen ~/bin/activate-llvm.sh a wann néideg maachen ech et . activate-llvm.sh.)

Pahole an BTF

Utility pahole benotzt wann de Kernel baut fir Debugging Informatioun am BTF Format ze kreéieren. Mir wäerten an dësem Artikel net am Detail iwwer d'Detailer vun der BTF Technologie goen, ausser datt et bequem ass a mir wëllen et benotzen. Also wann Dir Äre Kernel baut, baut als éischt pahole (ouni pahole Dir kënnt de Kernel net mat der Optioun bauen 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 fir ze experimentéieren mat BPF

Wann ech d'Méiglechkeete vu BPF exploréieren, wëll ech mäin eegene Kär zesummestellen. Dëst, allgemeng geschwat, ass net néideg, well Dir wäert fäeg sinn BPF Programmer um Verdeelungskär ze kompiléieren an ze lueden, awer Ären eegene Kernel ze hunn erlaabt Iech déi lescht BPF Features ze benotzen, déi an Ärer Verdeelung am beschten a Méint erschéngen. , oder, wéi am Fall vun e puer Debugging Tools wäert guer net an absehbarer Zukunft verpackt ginn. Och säin eegene Kär mécht et wichteg fir mam Code ze experimentéieren.

Fir e Kernel ze bauen braucht Dir éischtens de Kernel selwer, an zweetens eng Kernel Konfiguratiounsdatei. Fir mat BPF ze experimentéieren kënne mir déi üblech benotzen Vanille Kernel oder ee vun den Entwécklungskären. Historesch fënnt d'BPF Entwécklung bannent der Linux Netzwierker Gemeinschaft statt an dofir ginn all Ännerunge fréier oder spéider duerch den David Miller, de Linux Networking Instander. Ofhängeg vun hirer Natur - Ännerungen oder nei Fonctiounen - Reseau Ännerungen falen an ee vun zwee Kären - net oder net-next. Ännerungen fir BPF sinn an déi selwecht Manéier verdeelt tëscht bpf и bpf-next, déi dann an Netz an Net-Next zesummegefaasst ginn, respektiv. Fir méi Detailer, kuckt bpf_devel_QA и netdev-FAQ. Also wielt e Kernel baséiert op Ärem Goût an de Stabilitéitsbedierfnesser vum System op deem Dir testt (*-next Kären sinn déi onbestänneg vun deenen opgezielt).

Et ass iwwer den Ëmfang vun dësem Artikel ze schwätzen iwwer wéi een Kernel Konfiguratiounsdateien verwalten - et gëtt ugeholl datt Dir entweder scho wësst wéi Dir dëst maacht, oder prett ze léieren eleng. Wéi och ëmmer, déi folgend Instruktioune solle méi oder manner genuch sinn fir Iech e funktionnéierende BPF-aktivéierte System ze ginn.

Luet ee vun den uewe genannte Kärelen erof:

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

Baut eng minimal funktionéierend Kernel Konfiguratioun:

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

Aktivéiert BPF Optiounen am Fichier .config vun Ärer eegener Wiel (wahrscheinlech CONFIG_BPF wäert scho aktivéiert sinn well systemd et benotzt). Hei ass eng Lëscht vun Optiounen aus dem Kernel benotzt fir dësen Artikel:

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

Da kënne mir d'Moduler an de Kernel ganz einfach montéieren an installéieren (iwwregens, Dir kënnt de Kernel mat der nei montéierter zesummesetzen clangduerch dobäi CC=clang):

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

a restart mam neie Kernel (ech benotze fir dëst kexec aus Package 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

Déi meescht benotzt Utility am Artikel ass den Utility bpftool, als Deel vum Linux Kernel geliwwert. Et ass geschriwwen an erhale vun BPF Entwéckler fir BPF Entwéckler a ka benotzt ginn fir all Zorte vu BPF Objekter ze managen - Programmer lueden, Kaarten erstellen an z'änneren, d'Liewen vum BPF Ökosystem entdecken, asw. Dokumentatioun a Form vu Source Coden fir Mann Säiten kann fonnt ginn am Kär oder scho kompiléiert, am Netz.

Zu der Zäit vun dësem Schreiwen bpftool kënnt nëmme fäerdeg fir RHEL, Fedora an Ubuntu (kuckt z.B. dësem Fuedem, déi d'ongeschloss Geschicht vun der Verpakung erzielt bpftool an Debian). Awer wann Dir Äre Kernel scho gebaut hutt, da baut bpftool sou einfach wéi e Patt:

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

$

(hei ${linux} - dëst ass Äre Kernel Verzeichnis.) Nodeems Dir dës Kommandoen ausgefouert hutt bpftool wäert an engem Verzeechnes gesammelt ginn ${linux}/tools/bpf/bpftool an et kann op de Wee bäigefüügt ginn (virun allem dem Benotzer root) oder kopéiert einfach op /usr/local/sbin.

Sammelen bpftool et ass am beschten déi lescht ze benotzen clang, zesummegesat wéi uewen beschriwwen, a kontrolléiert ob et richteg zesummegesat ass - mat zum Beispill de Kommando

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

wat weist wéi eng BPF Features an Ärem Kernel aktivéiert sinn.

Iwwregens, de fréiere Kommando kann als

# bpftool f p k

Dëst gëtt duerch Analogie mat den Utilities aus dem Package gemaach iproute2, wou mer zum Beispill soen ip a s eth0 anstatt ip addr show dev eth0.

Konklusioun

BPF erlaabt Iech e Flou ze schong fir effektiv d'Funktionalitéit vum Kär ze moossen an on-the-fly z'änneren. De System huet sech als ganz erfollegräich erausgestallt, an de beschten Traditioune vun UNIX: en einfache Mechanismus deen Iech erlaabt de Kärel (nei) ze programméieren erlaabt eng grouss Zuel vu Leit an Organisatiounen ze experimentéieren. An, obwuel d'Experimenter, wéi och d'Entwécklung vun der BPF Infrastruktur selwer, wäit vun fäerdeg sinn, huet de System schonn e stabile ABI, deen Iech erlaabt Iech zouverlässeg, an am wichtegsten, effektiv Geschäftslogik ze bauen.

Ech wëll bemierken datt menger Meenung no d'Technologie sou populär ginn ass well se engersäits benotzt ka ginn ze spillen (d'Architektur vun enger Maschinn kann an engem Owend méi oder manner verstane ginn), an op der anerer Säit, Problemer ze léisen, déi net (schéin) geléist kënne ginn ier se erscheint. Dës zwee Komponenten zwéngen d'Leit zesummen ze experimentéieren an ze dreemen, wat zu der Entstoe vu méi a méi innovative Léisungen féiert.

Dësen Artikel, och wann net besonnesch kuerz, ass nëmmen eng Aféierung an d'Welt vu BPF a beschreift net "fortgeschratt" Funktiounen a wichteg Deeler vun der Architektur. De Plang no vir ass eppes wéi dëst: den nächsten Artikel wäert en Iwwerbléck iwwer BPF Programmarten sinn (et ginn 5.8 Programmarten, déi am 30 Kernel ënnerstëtzt ginn), da wäerte mir endlech kucken wéi ech richteg BPF Uwendungen schreiwen mat Kernel Tracing Programmer als Beispill, dann ass et Zäit fir e méi am-Déift Cours op BPF Architektur, gefollegt vun Beispiller vun BPF Netzwierker a Sécherheet Uwendungen.

Virdrun Artikelen an dëser Serie

  1. BPF fir déi Kleng, Deel Null: klassesch BPF

Linken

  1. BPF an XDP Referenz Guide - Dokumentatioun iwwer BPF aus cilium, oder méi präzis vum Daniel Borkman, ee vun de Créateuren an Ënnerhalter vu BPF. Dëst ass eng vun den éischten seriöse Beschreiwungen, déi sech vun deenen aneren ënnerscheeden, datt den Daniel genee weess iwwer wat hie schreift an do keng Feeler gëtt. Besonnesch beschreift dëst Dokument wéi Dir mat BPF Programmer vun den Typen XDP an TC schafft mat dem bekannten Utility ip aus Package iproute2.

  2. Documentation/networking/filter.txt - Original Fichier mat Dokumentatioun fir klassesch an dann verlängert BPF. Eng gutt Liesung wann Dir wëllt an d'Versammlungssprooch an d'technesch architektonesch Detailer verdéiwen.

  3. Blog iwwer BPF vu Facebook. Et gëtt selten aktualiséiert, awer passend, wéi den Alexei Starovoitov (Auteur vun eBPF) an Andrii Nakryiko - (Maintainer) do schreiwen libbpf).

  4. Geheimnisser vun bpftool. En erhuelsamen Twitter-Thread vum Quentin Monnet mat Beispiller a Geheimnisser fir bpftool ze benotzen.

  5. Taucht an BPF: eng Lëscht vu Liesmaterial. Eng rieseg (an nach ëmmer erhale) Lëscht vu Linken op BPF Dokumentatioun vum Quentin Monnet.

Source: will.com

Setzt e Commentaire