BPF kanggo bocah cilik, bagean siji: BPF lengkap

Ing wiwitan ana teknologi lan diarani BPF. Kita nyawang dheweke sadurunge, artikel Prajanjian Lawas saka seri iki. Ing 2013, liwat upaya Alexei Starovoitov lan Daniel Borkman, versi sing luwih apik, sing dioptimalake kanggo mesin 64-bit modern, dikembangake lan kalebu ing kernel Linux. Teknologi anyar iki sedhela diarani Internal BPF, banjur diganti jeneng BPF Extended, lan saiki, sawise sawetara taun, kabeh wong mung nyebut BPF.

Secara kasar, BPF ngidini sampeyan mbukak kode sing diwenehake pangguna kanthi sewenang-wenang ing ruang kernel Linux, lan arsitektur anyar kasebut sukses banget, mula kita butuh luwih akeh artikel kanggo nggambarake kabeh aplikasi kasebut. (Siji-sijine pangembang ora nindakake kanthi apik, kaya sing sampeyan deleng ing kode kinerja ing ngisor iki, yaiku nggawe logo sing apik.)

Artikel iki njlèntrèhaké struktur mesin virtual BPF, antarmuka kernel kanggo nggarap BPF, alat pangembangan, uga ringkesan, ringkesan banget saka kapabilitas ana, i.e. kabeh sing bakal dibutuhake ing mangsa ngarep kanggo sinau luwih jero babagan aplikasi praktis BPF.
BPF kanggo bocah cilik, bagean siji: BPF lengkap

Ringkesan artikel

Pambuka kanggo arsitektur BPF. Pisanan, kita bakal nliti arsitektur BPF lan njelasake komponen utama.

Register lan sistem printah saka mesin virtual BPF. Wis duwe gagasan arsitektur sakabèhé, kita bakal njlèntrèhaké struktur mesin virtual BPF.

Siklus urip obyek BPF, sistem file bpffs. Ing bagean iki, kita bakal nliti siklus urip obyek BPF - program lan peta.

Ngatur obyek nggunakake telpon sistem bpf. Kanthi sawetara pangerten babagan sistem sing wis ana, pungkasane kita bakal ndeleng carane nggawe lan ngapusi obyek saka ruang pangguna nggunakake panggilan sistem khusus - bpf(2).

Пишем программы BPF с помощью libbpf. Mesthi, sampeyan bisa nulis program nggunakake panggilan sistem. Nanging angel. Kanggo skenario sing luwih nyata, programer nuklir ngembangake perpustakaan libbpf. Kita bakal nggawe kerangka aplikasi BPF dhasar sing bakal digunakake ing conto sabanjure.

Kernel Helpers. Ing kene, kita bakal sinau kepiye program BPF bisa ngakses fungsi helper kernel - alat sing, bebarengan karo peta, kanthi dhasar ngembangake kemampuan BPF anyar dibandhingake karo sing klasik.

Akses menyang peta saka program BPF. Ing titik iki, kita bakal ngerti cukup kanggo ngerti persis carane nggawe program sing nggunakake peta. Lan ayo ndeleng cepet menyang verifier sing gedhe lan kuwat.

Piranti pangembangan. Bagean pitulung babagan carane ngumpulake keperluan lan kernel sing dibutuhake kanggo eksperimen.

Kesimpulan. Ing pungkasan artikel, wong-wong sing maca nganti saiki bakal nemokake tembung-tembung motivasi lan katrangan ringkes babagan apa sing bakal kelakon ing artikel-artikel sabanjure. Kita uga bakal dhaptar sawetara pranala kanggo sinau dhewe kanggo wong-wong sing ora duwe kepinginan utawa kemampuan kanggo ngenteni terusan.

Pengantar Arsitektur BPF

Sadurunge kita miwiti kanggo nimbang arsitektur BPF, kita bakal ngrujuk pungkasan (oh) kanggo BPF klasik, sing dikembangake minangka respon kanggo tekane mesin RISC lan ngrampungake masalah panyaring paket sing efisien. Arsitèktur kasebut dadi sukses banget, amarga lair ing taun nineties ing Berkeley UNIX, ditransfer menyang sistem operasi sing paling ana, bisa urip nganti umur rong puluhan edan lan isih nemokake aplikasi anyar.

BPF anyar dikembangake minangka respon kanggo ubiquity mesin 64-bit, layanan maya lan tambah kabutuhan alat kanggo nggawe SDN (Ssaka-defined neworking). Dikembangake dening insinyur jaringan kernel minangka panggantos sing luwih apik kanggo BPF klasik, BPF anyar kanthi harfiah nem sasi mengko nemokake aplikasi ing tugas sing angel kanggo nglacak sistem Linux, lan saiki, nem taun sawise katon, kita butuh artikel sabanjure mung kanggo dhaptar macem-macem jinis program.

Gambar lucu

Ing inti, BPF minangka mesin virtual kothak wedhi sing ngidini sampeyan mbukak kode "sewenang-wenang" ing ruang kernel tanpa kompromi keamanan. Program BPF digawe ing ruang pangguna, dimuat menyang kernel, lan disambungake menyang sawetara sumber acara. Acara bisa uga, contone, pangiriman paket menyang antarmuka jaringan, peluncuran sawetara fungsi kernel, lsp. Ing kasus paket, program BPF bakal duwe akses menyang data lan metadata paket (kanggo maca lan, bisa uga, nulis, gumantung saka jinis program); ing kasus mbukak fungsi kernel, argumen saka fungsi, kalebu penunjuk kanggo memori kernel, etc.

Ayo padha nliti proses iki. Kanggo miwiti, ayo ngomong babagan prabédan pisanan saka BPF klasik, program sing ditulis ing assembler. Ing versi anyar, arsitektur ditambahi supaya program bisa ditulis ing basa tingkat dhuwur, utamané, mesthi, ing C. Kanggo iki, backend kanggo llvm dikembangaké, sing ngijini sampeyan kanggo generate bytecode kanggo arsitektur BPF.

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Arsitèktur BPF dirancang, sebagian, supaya bisa mlaku kanthi efisien ing mesin modern. Kanggo nindakake iki ing laku, bytecode BPF, sawise dimuat menyang kernel, diterjemahake menyang kode asli nggunakake komponen sing disebut JIT compiler (JUst In Time). Sabanjure, yen sampeyan ngelingi, ing BPF klasik, program kasebut dimuat menyang kernel lan dipasang ing sumber acara kanthi atom - ing konteks panggilan sistem siji. Ing arsitektur anyar, iki kedadeyan ing rong tahap - pisanan, kode kasebut dimuat menyang kernel nggunakake panggilan sistem bpf(2)banjur, mengko, liwat mekanisme liyane sing beda-beda gumantung ing jinis program, program nempel ing sumber acara.

Ing kene sing maca bisa uga duwe pitakon: apa bisa? Kepiye keamanan eksekusi kode kasebut dijamin? Keamanan eksekusi dijamin kanggo kita kanthi tahap loading program BPF sing diarani verifier (ing basa Inggris tahap iki diarani verifier lan aku bakal terus nggunakake tembung Inggris):

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Verifier minangka analisa statis sing njamin program ora ngganggu operasi normal kernel. Iki, kanthi cara, ora ateges program kasebut ora bisa ngganggu operasi sistem - program BPF, gumantung saka jinise, bisa maca lan nulis ulang bagean memori kernel, ngasilake nilai fungsi, motong, nambah, nulis ulang. lan malah nerusake paket jaringan. Verifier njamin yen mbukak program BPF ora bakal nabrak kernel lan program sing, miturut aturan, nduweni akses nulis, contone, data paket sing metu, ora bakal bisa nimpa memori kernel ing njaba paket kasebut. Kita bakal ndeleng verifier kanthi luwih rinci ing bagean sing cocog, sawise kita kenal karo kabeh komponen BPF liyane.

Dadi apa sing wis kita sinau nganti saiki? Pangguna nulis program ing C, mbukak menyang kernel nggunakake panggilan sistem bpf(2), sing dicenthang dening verifier lan diterjemahake menyang bytecode asli. Banjur pangguna sing padha utawa liyane nyambungake program kasebut menyang sumber acara lan wiwit dieksekusi. Pamisahan boot lan sambungan perlu kanggo sawetara alasan. Kaping pisanan, mbukak verifier relatif larang lan kanthi ndownload program sing padha kaping pirang-pirang, kita mbuwang wektu komputer. Kapindho, persis carane program disambungake gumantung saka jinise, lan siji antarmuka "universal" sing dikembangake setahun kepungkur bisa uga ora cocog kanggo jinis program anyar. (Sanajan saiki arsitektur dadi luwih diwasa, ana gagasan kanggo nyawiji antarmuka iki ing tingkat libbpf.)

Sing maca sing ati-ati bisa uga ngerteni manawa kita durung rampung karo gambar kasebut. Pancen, kabeh ing ndhuwur ora nerangake apa BPF dhasar ngganti gambar dibandhingake BPF klasik. Rong inovasi sing nggedhekake ruang lingkup aplikasi yaiku kemampuan nggunakake memori sing dienggo bareng lan fungsi helper kernel. Ing BPF, memori bareng diimplementasikake nggunakake peta sing diarani - struktur data sing dienggo bareng karo API tartamtu. Dheweke mbokmenawa entuk jeneng iki amarga jinis peta pisanan sing katon yaiku tabel hash. Banjur larik muncul, tabel hash lokal (saben-CPU) lan susunan lokal, wit telusuran, peta sing ngemot penunjuk menyang program BPF lan liya-liyane. Apa sing menarik kanggo kita saiki yaiku program BPF saiki duwe kemampuan kanggo tetep ing antarane telpon lan bareng karo program liyane lan karo ruang pangguna.

Peta diakses saka proses pangguna nggunakake panggilan sistem bpf(2), lan saka program BPF sing mlaku ing kernel - nggunakake fungsi helper. Kajaba iku, helpers ana ora mung kanggo nggarap peta, nanging uga kanggo ngakses kapabilitas kernel liyane. Contone, program BPF bisa nggunakake fungsi helper kanggo nerusake paket menyang antarmuka liyane, ngasilake acara perf, ngakses struktur kernel, lan liya-liyane.

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Ing ringkesan, BPF nyedhiyakake kemampuan kanggo mbukak sembarang, yaiku, verifier-dites, kode pangguna menyang ruang kernel. Kode iki bisa nyimpen negara antarane telpon lan ijol-ijolan data karo papan panganggo, lan uga nduweni akses menyang subsistem kernel diijini dening jinis program iki.

Iki wis padha karo kemampuan sing diwenehake dening modul kernel, dibandhingake karo BPF sing duwe sawetara kaluwihan (mesthi, sampeyan mung bisa mbandhingake aplikasi sing padha, contone, tracing sistem - sampeyan ora bisa nulis driver sewenang-wenang karo BPF). Sampeyan bisa nyathet ambang entri sing luwih murah (sawetara utilitas sing nggunakake BPF ora mbutuhake pangguna duwe katrampilan pemrograman kernel, utawa katrampilan pemrograman umume), safety runtime (angkat tangan sampeyan ing komentar kanggo sing ora ngrusak sistem nalika nulis. utawa modul testing), atomicity - ana downtime nalika reloading modul, lan subsistem BPF mesthekake yen ora acara ora kejawab (dadi adil, iki ora bener kanggo kabeh jinis program BPF).

Anane kemampuan kasebut ndadekake BPF minangka alat universal kanggo ngembangake kernel, sing dikonfirmasi ing praktik: luwih akeh jinis program anyar sing ditambahake menyang BPF, luwih akeh perusahaan gedhe nggunakake BPF ing server pertempuran 24 × 7, luwih akeh. startups mbangun bisnis ing solusi adhedhasar kang adhedhasar BPF. BPF digunakake ing endi wae: kanggo nglindhungi serangan DDoS, nggawe SDN (contone, ngleksanakake jaringan kanggo kubernetes), minangka alat nelusuri sistem utama lan kolektor statistik, ing sistem deteksi intrusi lan sistem kothak wedhi, lsp.

Ayo rampungake bagean ringkesan artikel ing kene lan deleng mesin virtual lan ekosistem BPF kanthi luwih rinci.

Digression: utilitas

Supaya bisa mbukak conto ing bagean ing ngisor iki, sampeyan bisa uga kudu sawetara keperluan, paling llvm/clang kanthi dhukungan bpf lan bpftool... Ing bagean Piranti Pangembangan Sampeyan bisa maca pandhuan kanggo ngrakit utilitas, uga kernel sampeyan. Bagian iki diselehake ing ngisor iki supaya ora ngganggu keharmonisan presentasi kita.

BPF Virtual Machine Register lan Sistem Instruksi

Arsitektur lan sistem komando BPF dikembangake kanthi nimbang kasunyatan manawa program bakal ditulis ing basa C lan, sawise dimuat ing kernel, diterjemahake menyang kode asli. Mulane, jumlah ndhaptar lan set printah dipilih kanthi mripat menyang persimpangan, ing pangertèn matématika, saka kemampuan mesin modern. Kajaba iku, macem-macem watesan dileksanakake ing program, contone, nganti saiki ora bisa nulis puteran lan subrutin, lan jumlah instruksi diwatesi nganti 4096 (saiki program sing duwe hak istimewa bisa mbukak nganti sejuta instruksi).

BPF duwe sewelas registrasi 64-bit sing bisa diakses pangguna r0-r10 lan counter program. Ndaftar r10 ngandhut pitunjuk pigura lan mung diwaca. Program duwe akses menyang tumpukan 512-byte nalika runtime lan jumlah memori sing dienggo bareng tanpa wates ing wangun peta.

Program BPF diijini kanggo mbukak set tartamtu saka program-jinis kernel helpers lan, luwih anyar, fungsi biasa. Saben fungsi disebut bisa njupuk nganti limang argumen, liwati ing register r1-r5, lan nilai bali liwati menyang r0. Dijamin yen sawise bali saka fungsi, isi ndhaptar r6-r9 Ora bakal owah.

Kanggo terjemahan program efisien, ndhaftar r0-r11 kanggo kabeh arsitektur didhukung unik peta kanggo ndhaftar nyata, njupuk menyang akun fitur ABI arsitektur saiki. Contone, kanggo x86_64 ndhaftar r1-r5, digunakake kanggo pass paramèter fungsi, ditampilake ing rdi, rsi, rdx, rcx, r8, sing digunakake kanggo pass paramèter kanggo fungsi ing x86_64. Contone, kode ing sisih kiwa nerjemahake kode ing sisih tengen kaya iki:

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

Ndhaptar r0 uga digunakake kanggo ngasilake asil eksekusi program, lan ing daftar r1 program liwati pitunjuk kanggo konteks - gumantung ing jinis program, iki bisa uga, contone, struktur struct xdp_md (kanggo XDP) utawa struktur struct __sk_buff (kanggo program jaringan beda) utawa struktur struct pt_regs (kanggo macem-macem jinis program nelusuri), lsp.

Dadi, kita duwe set register, helpers kernel, tumpukan, pointer konteks lan memori bareng ing wangun peta. Ora kabeh iki pancen perlu ing perjalanan, nanging ...

Ayo nerusake katrangan lan pirembagan babagan sistem perintah kanggo nggarap obyek kasebut. kabeh (Meh kabeh) Instruksi BPF duwe ukuran 64-bit tetep. Yen sampeyan ndeleng siji instruksi ing mesin Big Endian 64-bit sampeyan bakal weruh

BPF kanggo bocah cilik, bagean siji: BPF lengkap

iku Code - iki enkoding instruksi, Dst/Src yaiku enkoding panrima lan sumber, Off - 16-dicokot mlebu indentation, lan Imm punika 32-dicokot mlebu integer digunakake ing sawetara instruksi (padha cBPF pancet K). Enkoding Code nduweni salah siji saka rong jinis:

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Kelas instruksi 0, 1, 2, 3 nemtokake perintah kanggo nggarap memori. padha diarani, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, mungguh. Kelas 4, 7 (BPF_ALU, BPF_ALU64) minangka set instruksi ALU. Kelas 5, 6 (BPF_JMP, BPF_JMP32) ngemot instruksi mlumpat.

Rencana luwih lanjut kanggo nyinaoni sistem instruksi BPF yaiku kaya ing ngisor iki: tinimbang kanthi tliti nyathet kabeh instruksi lan paramèter, kita bakal ndeleng sawetara conto ing bagean iki lan saka wong-wong mau bakal dadi jelas carane instruksi kasebut bener lan cara kerjane. mbongkar file binar kanggo BPF kanthi manual. Kanggo nggabungake materi ing artikel kasebut, kita uga bakal ketemu karo pandhuan individu ing bagean babagan Verifier, JIT compiler, terjemahan BPF klasik, uga nalika sinau peta, fungsi nelpon, lsp.

Nalika kita ngomong babagan instruksi individu, kita bakal ngrujuk menyang file inti bpf.h и bpf_common.h, sing nemtokake kode numerik instruksi BPF. Nalika sinau arsitektur dhewe lan / utawa parsing binari, sampeyan bisa nemokake semantik ing sumber ing ngisor iki, diurutake miturut kerumitan: Spesifikasi eBPF ora resmi, BPF lan XDP Reference Guide, Instruction Set, Dokumentasi/jaringan/filter.txt lan, mesthi, ing kode sumber Linux - verifier, JIT, BPF interpreter.

Conto: mbongkar BPF ing sirah

Ayo goleki conto sing kita ngumpulake program readelf-example.c lan katon ing binar asil. Kita bakal mbukak isi asli readelf-example.c ing ngisor iki, sawise kita mulihake logika saka kode binar:

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

Kolom pisanan ing output readelf minangka indentasi lan program kita kalebu papat perintah:

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

Kode printah padha b7, 15, b7 и 95. Elinga yen telung bit sing paling penting yaiku kelas instruksi. Ing kasus kita, bit kaping papat kabeh instruksi kosong, mula kelas instruksi yaiku 7, 5, 7, 5. Kelas 7 yaiku BPF_ALU64lan 5, BPF_JMP. Kanggo loro kelas, format instruksi padha (ndeleng ndhuwur) lan kita bisa nulis ulang program kaya iki (ing wektu sing padha bakal nulis ulang kolom sing isih ana ing wangun manungsa):

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

Operasi b kelas ALU64 Punika BPF_MOV. Iki menehi nilai menyang ndhaptar tujuan. Yen bit disetel s (sumber), banjur nilai kasebut dijupuk saka registrasi sumber, lan yen, kaya ing kasus kita, ora disetel, banjur nilai kasebut dijupuk saka lapangan. Imm. Dadi ing pandhuan pisanan lan katelu kita nindakake operasi kasebut r0 = Imm. Salajengipun, operasi JMP kelas 1 punika BPF_JEQ (mlumpat yen padha). Ing kasus kita, wiwit dicokot S punika nul, iku mbandhingaké Nilai saka ndhaftar sumber karo lapangan Imm. Yen nilai-nilai kasebut pas, mula transisi kasebut kedadeyan PC + Offngendi PC, kaya biasane, ngemot alamat instruksi sabanjure. Akhire, JMP Kelas 9 Operasi punika BPF_EXIT. Pandhuan iki mungkasi program, bali menyang kernel r0. Ayo nambah kolom anyar menyang tabel:

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

Kita bisa nulis maneh iki ing wangun sing luwih trep:

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

Yen kita ngelingi apa sing ana ing daftar r1 program liwati pitunjuk kanggo konteks saka kernel, lan ing register r0 Nilai bali menyang kernel, banjur kita bisa ndeleng yen pointer kanggo konteks iku nul, banjur kita bali 1, lan liya - 2. Ayo dadi mriksa sing kita tengen dening looking ing sumber:

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

Ya, iku program sing ora ana gunane, nanging nerjemahake mung papat instruksi sing prasaja.

Tuladha pangecualian: instruksi 16-bait

Kita kasebut sadurunge sawetara instruksi njupuk luwih saka 64 bit. Iki ditrapake, contone, kanggo instruksi lddw (Kode = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - mbukak tembung dobel saka kolom menyang registrasi Imm... Kasunyatan iku Imm wis ukuran 32, lan tembung pindho 64 bit, supaya loading nilai langsung 64-dicokot menyang register ing siji 64-dicokot instruction ora bisa. Kanggo nindakake iki, rong instruksi jejer digunakake kanggo nyimpen bagean liya saka nilai 64-bit ing lapangan. Imm. Tuladha:

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

Mung ana rong instruksi ing program binar:

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

Kita bakal ketemu maneh karo instruksi lddw, nalika kita ngomong babagan relokasi lan nggarap peta.

Conto: mbongkar BPF nggunakake piranti standar

Dadi, kita wis sinau maca kode binar BPF lan siap ngurai instruksi apa wae yen perlu. Nanging, sampeyan kudu ujar manawa ing praktik luwih trep lan luwih cepet kanggo mbongkar program kanthi nggunakake alat standar, umpamane:

$ 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

Siklus urip obyek BPF, sistem file bpffs

(Aku pisanan sinau sawetara rincian sing diterangake ing bagean iki saka kirim Alexei Starovoitov ing Blog BPF.)

Objek BPF - program lan peta - digawe saka papan pangguna nggunakake printah BPF_PROG_LOAD и BPF_MAP_CREATE telpon sistem bpf(2), kita bakal ngomong babagan persis kepiye kedadeyan kasebut ing bagean sabanjure. Iki nggawe struktur data kernel lan kanggo saben wong refcount (count referensi) disetel dadi siji, lan deskriptor file sing ngarahake obyek kasebut bali menyang pangguna. Sawise gagang ditutup refcount obyek wis suda siji, lan nalika tekan nul, obyek wis numpes.

Yen program nggunakake peta, banjur refcount peta iki tambah siji sawise mbukak program, i.e. deskriptor file bisa ditutup saka proses pangguna lan isih refcount ora bakal nol:

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Sawise kasil mbukak program, kita biasane masang menyang sawetara jenis generator acara. Contone, kita bisa sijine iku ing antarmuka jaringan kanggo proses paket mlebu utawa nyambung menyang sawetara tracepoint ing inti. Ing titik iki, counter referensi uga bakal nambah siji lan kita bakal bisa nutup deskriptor file ing program loader.

Apa sing kedadeyan yen saiki kita mateni bootloader? Iku gumantung ing jinis generator acara (pancing). Kabeh pancingan jaringan bakal ana sawise loader rampung, iki sing disebut pancingan global. Lan, contone, program tilak bakal dirilis sawise proses sing digawe wong terminates (lan mulane disebut lokal, saka "lokal kanggo proses"). Secara teknis, pancing lokal tansah duwe deskriptor file sing cocog ing ruang pangguna lan mulane ditutup nalika proses ditutup, nanging pancingan global ora. Ing tokoh ing ngisor iki, nggunakake salib abang, aku nyoba kanggo nuduhake carane mandap saka program loader mengaruhi umur obyek ing cilik saka pancingan lokal lan global.

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Apa ana bedane antarane pancing lokal lan global? Mlaku sawetara jinis program jaringan ndadekake pangertèn tanpa userspace, contone, mbayangno pangayoman DDoS - bootloader nulis aturan lan nyambung program BPF kanggo antarmuka jaringan, sawise kang bootloader bisa pindhah lan matèni dhewe. Ing tangan liyane, mbayangno program tilak debugging sing ditulis ing dhengkul ing sepuluh menit - yen wis rampung, sampeyan pengin ora ana sampah kiwa ing sistem, lan pancingan lokal bakal mesthekake yen.

Ing tangan liyane, mbayangno sampeyan pengin nyambung menyang tracepoint ing kernel lan ngumpulake statistik sajrone pirang-pirang taun. Ing kasus iki, sampeyan pengin ngrampungake bagean pangguna lan bali menyang statistik saka wektu kanggo wektu. Sistem file bpf nyedhiyakake kesempatan iki. Iku sistem pseudo-file mung ing memori sing ngidini nggawe file sing ngrujuk obyek BPF lan kanthi mangkono nambah. refcount obyek. Sawise iki, loader bisa metu, lan obyek sing digawe bakal tetep urip.

BPF kanggo bocah cilik, bagean siji: BPF lengkap

Nggawe file ing bpffs sing ngrujuk obyek BPF diarani "pinning" (kaya ing frasa ing ngisor iki: "proses bisa pin program utawa peta BPF"). Nggawe obyek file kanggo obyek BPF ndadekake pangertèn ora mung kanggo ndawakake umur obyek lokal, nanging uga kanggo migunani obyek global - bali menyang conto karo program pangayoman DDoS global, kita pengin bisa teka lan katon ing statistik saka wektu kanggo wektu.

Sistem file BPF biasane dipasang ing /sys/fs/bpf, nanging uga bisa dipasang sacara lokal, contone, kaya iki:

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

Jeneng sistem file digawe nggunakake printah BPF_OBJ_PIN Telpon sistem BPF. Kanggo ilustrasi, ayo njupuk program, kompilasi, upload, lan pin menyang bpffs. Program kita ora nindakake apa-apa sing migunani, kita mung nampilake kode supaya sampeyan bisa ngasilake conto kasebut:

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

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

Ayo ngumpulake program iki lan nggawe salinan lokal sistem file bpffs:

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

Saiki ayo ndownload program kita nggunakake sarana bpftool lan katon ing telpon sistem gawan bpf(2) (sawetara garis sing ora relevan dibusak saka output strace):

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

Kene kita wis dimuat program nggunakake BPF_PROG_LOAD, nampa deskriptor file saka kernel 3 lan nggunakake printah BPF_OBJ_PIN Pinned deskriptor file iki minangka file "bpf-mountpoint/test". Sawise iki, program bootloader bpftool rampung, nanging program kita tetep ing kernel, sanajan kita ora masang antarmuka jaringan apa wae:

$ 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

Kita bisa mbusak obyek file biasane unlink(2) lan sawise iku program sing cocog bakal dibusak:

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

Mbusak obyek

Ngomong babagan mbusak obyek, sampeyan kudu njlentrehake manawa sawise medhot program saka pancing (generator acara), ora ana acara anyar sing bakal diluncurake, nanging kabeh program saiki bakal rampung ing urutan normal. .

Sawetara jinis program BPF ngijini sampeyan kanggo ngganti program ing fly, i.e. nyedhiyakake atomicity urutan replace = detach old program, attach new program. Ing kasus iki, kabeh kedadean aktif saka versi lawas saka program bakal rampung karya, lan panangan acara anyar bakal digawe saka program anyar, lan "atomicity" kene tegese ora acara siji ora kejawab.

Masang program menyang sumber acara

Ing artikel iki, kita ora bakal njlèntrèhaké kanthi kapisah program nyambung menyang sumber acara, amarga iku ndadekake pangertèn kanggo sinau iki ing konteks saka jinis program tartamtu. Cm. conto ngisor, kang kita nuduhake carane program kaya XDP disambungake.

Manipulasi Obyek Nggunakake Panggilan Sistem bpf

program BPF

Kabeh obyek BPF digawe lan dikelola saka ruang pangguna nggunakake panggilan sistem bpf, duwe prototipe ing ngisor iki:

#include <linux/bpf.h>

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

Punika tim cmd minangka salah sawijining nilai jinis enum bpf_cmd, attr - pointer kanggo paramèter kanggo program tartamtu lan size - ukuran obyek miturut pointer, i.e. biasane iki sizeof(*attr). Ing kernel 5.8 sistem nelpon bpf ndhukung 34 printah beda, lan definisi union bpf_attr manggoni 200 baris. Nanging kita ora kudu wedi karo iki, amarga kita bakal ngerti prentah lan paramèter sajrone sawetara artikel.

Ayo dadi miwiti karo tim BPF_PROG_LOAD, sing nggawe program BPF - njupuk set instruksi BPF lan dimuat menyang kernel. Ing wayahe loading, verifier diluncurake, banjur kompiler JIT lan, sawise eksekusi sukses, deskriptor file program bali menyang pangguna. Kita weruh apa sing kedadeyan ing dheweke ing bagean sadurunge babagan siklus urip obyek BPF.

Saiki kita bakal nulis program khusus sing bakal mbukak program BPF sing prasaja, nanging luwih dhisik kita kudu mutusake program apa sing arep kita muat - kita kudu milih ketik lan ing framework saka jinis iki, nulis program sing bakal pass test verifier. Nanging, supaya ora kanggo complicate proses, punika solusi siap-digawe: kita bakal njupuk program kaya BPF_PROG_TYPE_XDP, sing bakal ngasilake regane XDP_PASS (skip kabeh paket). Ing assembler BPF katon gampang banget:

r0 = 2
exit

Sawise kita mutusake sing kita bakal upload, kita bisa pitutur marang kowe carane kita bakal nindakake iku:

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

Acara sing menarik ing program diwiwiti kanthi definisi array insns - program BPF kita ing kode mesin. Ing kasus iki, saben instruksi saka program BPF dikempalken menyang struktur bpf_insn. Unsur pisanan insns tundhuk karo instruksi r0 = 2, sing nomer loro - exit.

mundur. Kernel nemtokake makro sing luwih trep kanggo nulis kode mesin, lan nggunakake file header kernel tools/include/linux/filter.h kita bisa nulis

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

Nanging wiwit nulis program BPF ing kode native mung perlu kanggo nulis tes ing kernel lan artikel bab BPF, anané macro iki ora tenan complicate urip pangembang kang.

Sawise nemtokake program BPF, kita nerusake kanggo mbukak menyang kernel. Set paramèter minimalis kita attr kalebu jinis program, set lan nomer instruksi, lisensi sing dibutuhake, lan jeneng "woo", sing digunakake kanggo nemokake program ing sistem sawise diundhuh. Program kasebut, kaya sing dijanjekake, dimuat menyang sistem nggunakake panggilan sistem bpf.

Ing mburi program kita mungkasi munggah ing daur ulang tanpa wates sing simulates payload. Tanpa iku, program bakal matèni kernel nalika deskriptor file sing sistem telpon bali menyang kita ditutup bpf, lan kita ora bakal weruh ing sistem.

Inggih, kita siap kanggo tes. Ayo dadi ngumpul lan mbukak program ing stracekanggo mriksa manawa kabeh bisa digunakake kaya sing dikarepake:

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

Kabeh apik, bpf(2) bali nangani 3 kanggo kita lan kita tindak menyang daur ulang tanpa wates karo pause(). Ayo goleki program kita ing sistem kasebut. Kanggo nindakake iki, kita bakal pindhah menyang terminal liyane lan nggunakake sarana 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)

Kita weruh manawa ana program sing dimuat ing sistem kasebut woo kang ID global 390 lan saiki ing proses simple-prog ana deskriptor file mbukak sing nuduhake program kasebut (lan yen simple-prog bakal rampung proyek, banjur woo bakal ilang). Kaya sing dikarepake, program kasebut woo njupuk 16 bait - loro instruksi - kode binar ing arsitektur BPF, nanging ing wangun asli (x86_64) wis 40 bait. Ayo katon ing program kita ing wangun asli:

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

ora surprises. Saiki ayo goleki kode sing digawe dening kompiler JIT:

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

ora banget efektif kanggo exit(2), nanging kanthi adil, program kita prasaja banget, lan kanggo program sing ora pati penting, prolog lan epilog sing ditambahake dening kompiler JIT, mesthine dibutuhake.

Maps

Program BPF bisa nggunakake area memori terstruktur sing bisa diakses kanggo program BPF liyane lan kanggo program ing ruang pangguna. Obyek kasebut diarani peta lan ing bagean iki kita bakal nuduhake carane ngapusi kanthi nggunakake panggilan sistem bpf.

Ayo langsung ngomong manawa kemampuan peta ora mung diwatesi kanggo ngakses memori sing dienggo bareng. Ana peta khusus sing ngemot, contone, penunjuk program BPF utawa penunjuk antarmuka jaringan, peta kanggo nggarap acara perf, lsp. Kita ora bakal ngomong babagan dheweke ing kene, supaya ora mbingungake sing maca. Kajaba iku, kita ora nglirwakake masalah sinkronisasi, amarga iki ora penting kanggo conto kita. Dhaptar lengkap jinis peta sing kasedhiya bisa ditemokake ing <linux/bpf.h>, lan ing bagean iki kita bakal njupuk minangka conto jinis historis pisanan, tabel hash BPF_MAP_TYPE_HASH.

Yen sampeyan nggawe tabel hash ing, ngandika, C ++, sampeyan bakal ngomong unordered_map<int,long> woo, sing ing basa Rusia tegese "Aku butuh meja woo ukuran Unlimited, kang tombol saka jinis int, lan nilai kasebut minangka jinis long" Kanggo nggawe tabel hash BPF, kita kudu nindakake perkara sing padha, kajaba kita kudu nemtokake ukuran maksimum tabel, lan tinimbang nemtokake jinis tombol lan nilai, kita kudu nemtokake ukurane ing bita. . Kanggo nggawe peta nggunakake printah BPF_MAP_CREATE telpon sistem bpf. Ayo goleki program sing luwih minimal sing nggawe peta. Sawise program sadurunge sing ngemot program BPF, iki mesthine katon gampang kanggo sampeyan:

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

Ing kene kita nemtokake set paramèter attr, ing ngendi kita ngomong "Aku butuh tabel hash kanthi tombol lan nilai ukuran sizeof(int), ing ngendi aku bisa nyelehake maksimal papat unsur." Nalika nggawe peta BPF, sampeyan bisa nemtokake paramèter liyane, contone, ing cara sing padha ing conto program, kita nemtokake jeneng obyek minangka "woo".

Ayo ngumpulake lan mbukak program:

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

Punika telpon sistem bpf(2) bali kita nomer peta deskriptor 3 banjur program, kaya sing dikarepake, ngenteni instruksi luwih lanjut ing telpon sistem pause(2).

Saiki ayo ngirim program kita menyang latar mburi utawa mbukak terminal liyane lan deleng obyek kita nggunakake sarana kasebut bpftool (kita bisa mbedakake peta kita saka wong liya kanthi jeneng):

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

Nomer 114 minangka ID global obyek kita. Sembarang program ing sistem bisa nggunakake ID iki kanggo mbukak peta sing ana nggunakake printah BPF_MAP_GET_FD_BY_ID telpon sistem bpf.

Saiki kita bisa muter karo tabel hash kita. Ayo dideleng isine:

$ sudo bpftool map dump id 114
Found 0 elements

kosong. Ayo dadi sijine nilai ing hash[1] = 1:

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

Ayo ndeleng tabel maneh:

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

Hore! Kita bisa nambah siji unsur. Elinga yen kita kudu bisa ing tingkat byte kanggo nindakake iki, wiwit bptftool ora ngerti apa jinis nilai ing tabel hash. (Kawruh iki bisa ditransfer menyang dheweke nggunakake BTF, nanging luwih akeh babagan saiki.)

Kepiye carane bpftool maca lan nambah unsur? Ayo katon ing ngisor 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

Kaping pisanan, kita mbukak peta kanthi ID global nggunakake perintah kasebut BPF_MAP_GET_FD_BY_ID и bpf(2) bali descriptor 3 kanggo kita. Luwih nggunakake printah BPF_MAP_GET_NEXT_KEY kita ketemu tombol pisanan ing meja dening maringaken NULL minangka pointer kanggo tombol "sadurunge". Yen kita duwe kunci kita bisa nindakake BPF_MAP_LOOKUP_ELEMsing ngasilake nilai menyang pointer value. Langkah sabanjure yaiku nyoba nemokake unsur sabanjure kanthi menehi penunjuk menyang tombol saiki, nanging tabel kita mung ngemot siji unsur lan perintah kasebut. BPF_MAP_GET_NEXT_KEY bali ENOENT.

Oke, ayo ngganti nilai kanthi kunci 1, mula logika bisnis kita mbutuhake ndhaptar 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

Kaya sing dikarepake, prasaja banget: printah BPF_MAP_GET_FD_BY_ID mbukak peta kita kanthi ID, lan printah BPF_MAP_UPDATE_ELEM nimpa unsur.

Dadi, sawise nggawe tabel hash saka siji program, kita bisa maca lan nulis isine saka program liyane. Elinga yen kita bisa nindakake iki saka baris printah, banjur program liyane ing sistem bisa nindakake. Saliyane prentah kasebut ing ndhuwur, kanggo nggarap peta saka ruang pangguna, ing ngisor iki:

  • BPF_MAP_LOOKUP_ELEM: golek nilai dening tombol
  • BPF_MAP_UPDATE_ELEM: nganyari / nggawe nilai
  • BPF_MAP_DELETE_ELEM: mbusak kunci
  • BPF_MAP_GET_NEXT_KEY: golek tombol sabanjuré (utawa pisanan).
  • BPF_MAP_GET_NEXT_ID: ngijini sampeyan kanggo mbukak liwat kabeh peta ana, sing cara kerjane bpftool map
  • BPF_MAP_GET_FD_BY_ID: mbukak peta ana dening ID global sawijining
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: nganyari atom ing Nilai saka obyek lan bali lawas
  • BPF_MAP_FREEZE: nggawe peta ora bisa diganti saka ruang panganggo (operasi iki ora bisa dibatalake)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: operasi massal. Tuladhane, BPF_MAP_LOOKUP_AND_DELETE_BATCH - iki mung siji-sijine cara sing bisa dipercaya kanggo maca lan ngreset kabeh nilai saka peta

Ora kabeh printah kasebut bisa digunakake kanggo kabeh jinis peta, nanging umume nggarap jinis peta liyane saka ruang pangguna katon padha karo nggarap tabel hash.

Kanggo pesenan, ayo rampung eksperimen tabel hash. Elinga yen kita nggawe tabel sing bisa ngemot nganti papat tombol? Ayo nambah sawetara unsur liyane:

$ 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

Nganti saiki apik:

$ 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

Ayo coba nambah siji maneh:

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

Kaya sing dikarepake, kita ora sukses. Ayo goleki kesalahan kanthi luwih rinci:

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

Kabeh apik: kaya sing dikarepake, tim BPF_MAP_UPDATE_ELEM nyoba nggawe anyar, kalima, tombol, nanging tubrukan E2BIG.

Dadi, kita bisa nggawe lan mbukak program BPF, uga nggawe lan ngatur peta saka ruang pangguna. Saiki iku logis kanggo dipikir carane kita bisa nggunakake peta saka program BPF piyambak. Kita bisa ngomong babagan iki ing basa program sing angel diwaca ing kode makro mesin, nanging nyatane wektu wis teka kanggo nuduhake carane program BPF bener-bener ditulis lan dikelola - nggunakake libbpf.

(Kanggo pembaca sing ora marem karo kekurangan conto tingkat rendah: kita bakal nganalisa kanthi rinci program sing nggunakake peta lan fungsi helper sing digawe nggunakake libbpf lan pitutur marang kowe apa mengkono ing tingkat instruksi. Kanggo para pamaca sing ora marem akeh banget, kita nambah conto ing panggonan sing cocog ing artikel.)

Nulis program BPF nggunakake libbpf

Nulis program BPF nggunakake kode mesin bisa dadi menarik mung sepisanan, lan banjur kenyang. Ing wayahe sampeyan kudu nguripake manungsa waé kanggo llvm, sing nduweni backend kanggo ngasilake kode kanggo arsitektur BPF, uga perpustakaan libbpf, sing ngidini sampeyan nulis sisih pangguna aplikasi BPF lan mbukak kode program BPF sing digawe nggunakake llvm/clang.

Nyatane, kaya sing bakal kita deleng ing artikel iki lan sabanjure, libbpf nindakake cukup akeh karya tanpa iku (utawa alat sing padha - iproute2, libbcc, libbpf-go, lsp) ora bisa urip. Salah sawijining fitur pembunuh proyek kasebut libbpf yaiku BPF CO-RE (Compile Once, Run Everywhere) - proyek sing ngidini sampeyan nulis program BPF sing portabel saka siji kernel menyang liyane, kanthi kemampuan kanggo mbukak ing API sing beda-beda (contone, nalika struktur kernel diganti saka versi. kanggo versi). Supaya bisa nggarap CO-RE, kernel sampeyan kudu dikompilasi kanthi dhukungan BTF (kita nerangake carane nindakake iki ing bagean Piranti Pangembangan. Sampeyan bisa mriksa apa kernel sampeyan dibangun nganggo BTF utawa ora gampang banget - kanthi ana file ing ngisor iki:

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

Berkas iki nyimpen informasi babagan kabeh jinis data sing digunakake ing kernel lan digunakake ing kabeh conto nggunakake libbpf. Kita bakal ngomong kanthi rinci babagan CO-RE ing artikel sabanjure, nanging ing siji iki - mung nggawe kernel dhewe CONFIG_DEBUG_INFO_BTF.

perpustakaan libbpf manggon tengen ing direktori tools/lib/bpf kernel lan pangembangane ditindakake liwat mailing list [email protected]. Nanging, gudang kapisah dikelola kanggo kabutuhan aplikasi sing manggon ing njaba kernel https://github.com/libbpf/libbpf kang perpustakaan kernel mirrored kanggo akses diwaca luwih utawa kurang minangka.

Ing bagean iki kita bakal katon ing carane sampeyan bisa nggawe proyek sing nggunakake libbpf, ayo nulis sawetara program tes (luwih utawa kurang migunani) lan analisa kanthi rinci babagan cara kerjane. Iki bakal ngidini kita luwih gampang nerangake ing bagean ing ngisor iki kanthi persis kepiye program BPF sesambungan karo peta, pembantu kernel, BTF, lsp.

Biasane proyek nggunakake libbpf nambah repositori GitHub minangka submodule git, kita bakal nindakake perkara sing padha:

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

Menyang libbpf prasaja banget:

$ 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

Rencana sabanjure ing bagean iki yaiku: kita bakal nulis program BPF kaya BPF_PROG_TYPE_XDP, padha karo conto sadurunge, nanging ing C, kita ngumpulake nggunakake clang, lan nulis program helper sing bakal mbukak menyang kernel. Ing bagean ing ngisor iki kita bakal nggedhekake kemampuan program BPF lan program asisten.

Conto: nggawe aplikasi lengkap nggunakake libbpf

Kanggo miwiti, kita nggunakake file /sys/kernel/btf/vmlinux, sing kasebut ing ndhuwur, lan nggawe sing padha karo file header:

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

Berkas iki bakal nyimpen kabeh struktur data sing kasedhiya ing kernel kita, contone, iki carane header IPv4 ditetepake ing kernel:

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

Saiki kita bakal nulis program BPF ing 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";

Sanajan program kita dadi gampang banget, kita isih kudu menehi perhatian marang akeh rincian. Pisanan, file header pisanan sing kalebu yaiku vmlinux.h, kang kita mung kui nggunakake bpftool btf dump - saiki kita ora perlu nginstal paket kernel-headers kanggo ngerteni kaya apa struktur kernel kasebut. File header ing ngisor iki teka saka perpustakaan libbpf. Saiki kita mung perlu kanggo nemtokake makro SEC, sing ngirim karakter menyang bagean cocok saka file obyek ELF. Program kita ana ing bagean kasebut xdp/simple, ing ngendi sadurunge garis miring kita nemtokake jinis program BPF - iki minangka konvensi sing digunakake ing libbpf, adhedhasar jeneng bagean bakal ngganti jinis sing bener nalika wiwitan bpf(2). Program BPF dhewe yaiku C - banget prasaja lan kasusun saka siji baris return XDP_PASS. Pungkasan, bagean sing kapisah "license" ngandhut jeneng lisensi.

Kita bisa ngumpulake program kita nggunakake llvm/clang, versi>= 10.0.0, utawa luwih apik, luwih gedhe (ndeleng bagean Piranti Pangembangan):

$ 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

Antarane fitur menarik: kita nunjukake arsitektur target -target bpf lan path menyang headers libbpf, sing bubar diinstal. Uga, aja lali babagan -O2, tanpa pilihan iki, sampeyan bisa uga bakal kaget ing mangsa ngarep. Ayo ndeleng kode kita, apa kita bisa nulis program sing dikarepake?

$ 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

Ya, iku bisa! Saiki, kita duwe file binar karo program kasebut, lan kita pengin nggawe aplikasi sing bakal dimuat ing kernel. Kanggo maksud iki perpustakaan libbpf nawakake rong pilihan - nggunakake API tingkat ngisor utawa API tingkat sing luwih dhuwur. Kita bakal pindhah cara liya, amarga kita pengin sinau nulis, mbukak lan nyambungake program BPF kanthi minimal gaweyan kanggo sinau sakteruse.

Kaping pisanan, kita kudu ngasilake "kerangka" program kita saka binar nggunakake sarana sing padha bpftool - piso Swiss ing jagad BPF (sing bisa dijupuk kanthi harfiah, amarga Daniel Borkman, salah sawijining pencipta lan njaga BPF, yaiku Swiss):

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

Ing file xdp-simple.skel.h ngemot kode binar program kita lan fungsi kanggo ngatur - loading, masang, mbusak obyek kita. Ing kasus prasaja iki katon kaya overkill, nanging uga bisa digunakake ing kasus file obyek ngemot akeh program BPF lan peta lan kanggo mbukak ELF buta iki kita mung kudu generate balung lan nelpon siji utawa loro fungsi saka aplikasi adat kita. lagi nulis Ayo nerusake saiki.

Tegese, program loader kita ora pati penting:

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

iku struct xdp_simple_bpf ditetepake ing file xdp-simple.skel.h lan nggambarake file obyek kita:

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

Kita bisa ndeleng jejak API tingkat rendah ing kene: struktur struct bpf_program *simple и struct bpf_link *simple. Struktur pisanan khusus njlèntrèhaké program kita, ditulis ing bagean xdp/simple, lan kaloro nerangake carane program nyambung menyang sumber acara.

fungsi xdp_simple_bpf__open_and_load, mbukak obyek ELF, ngurai, nggawe kabeh struktur lan substruktur (saliyane program, ELF uga ngemot bagean liyane - data, data mung diwaca, informasi debugging, lisensi, lan sapiturute), banjur dimuat menyang kernel nggunakake sistem nelpon bpf, sing bisa dipriksa kanthi nyusun lan mbukak program:

$ 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

Ayo saiki katon ing program kita nggunakake bpftool. Ayo goleki ID dheweke:

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

lan mbucal (kita nggunakake wangun shortened saka printah bpftool prog dump xlated):

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

Ana sing anyar! Program dicithak potongan file sumber C. Iki ditindakake dening perpustakaan libbpf, sing nemokake bagean debug ing binar, disusun dadi obyek BTF, dimuat menyang kernel nggunakake BPF_BTF_LOAD, banjur nemtokake deskriptor file sing diasilake nalika ngemot program kasebut nganggo perintah kasebut BPG_PROG_LOAD.

Kernel Helpers

Program BPF bisa mbukak fungsi "eksternal" - pembantu kernel. Fungsi helper iki ngidini program BPF ngakses struktur kernel, ngatur peta, lan uga komunikasi karo "donya nyata" - nggawe acara perf, ngontrol hardware (contone, pangalihan paket), lsp.

Tuladha: bpf_get_smp_processor_id

Ing kerangka paradigma "sinau kanthi conto", ayo dipikirake salah sawijining fungsi pembantu, bpf_get_smp_processor_id(), mesthi ing file kernel/bpf/helpers.c. Iki ngasilake nomer prosesor sing program BPF sing diarani mlaku. Nanging kita ora kasengsem ing semantik kaya kasunyatan sing implementasine njupuk siji baris:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

Dhéfinisi fungsi helper BPF padha karo definisi panggilan sistem Linux. Ing kene, contone, fungsi ditetepake sing ora duwe argumen. (Fungsi sing njupuk, sebutno, telung argumen ditetepake nggunakake macro BPF_CALL_3. Jumlah maksimum argumen limang.) Nanging, iki mung bagean pisanan saka definisi. Bagian kapindho yaiku kanggo nemtokake struktur jinis struct bpf_func_proto, sing ngemot katrangan babagan fungsi helper sing dimangerteni dening verifier:

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

Ndhaptar Fungsi Helper

Supaya program BPF saka jinis tartamtu nggunakake fungsi iki, padha kudu ndhaftar, contone, kanggo jinis BPF_PROG_TYPE_XDP fungsi ditetepake ing kernel xdp_func_proto, sing nemtokake saka ID fungsi helper apa XDP ndhukung fungsi iki utawa ora. Fungsi kita yaiku ndhukung:

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

Jinis program BPF anyar "ditetepake" ing file include/linux/bpf_types.h nggunakake macro BPF_PROG_TYPE. Ditetepake ing kuotasi amarga iku definisi logis, lan ing istilah basa C definisi saka kabèh pesawat saka struktur konkrit dumadi ing panggonan liyane. Ing tartamtu, ing file kernel/bpf/verifier.c kabeh definisi saka file bpf_types.h digunakake kanggo nggawe macem-macem struktur 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
};

Sing, kanggo saben jinis program BPF, pointer menyang struktur data saka jinis ditetepake struct bpf_verifier_ops, sing diwiwiti kanthi nilai _name ## _verifier_ops, yaiku, xdp_verifier_ops kanggo xdp. Struktur xdp_verifier_ops ditemtokake dening ing file net/core/filter.c kaya mangkene:

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

Ing kene kita ndeleng fungsi sing akrab xdp_func_proto, sing bakal mbukak verifier saben-saben nemoni tantangan sawetara jinis fungsi nang program BPF, ndeleng verifier.c.

Ayo goleki carane program BPF hipotetis nggunakake fungsi kasebut bpf_get_smp_processor_id. Kanggo nindakake iki, kita nulis maneh program saka bagean sadurunge kaya ing ngisor iki:

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

Simbol bpf_get_smp_processor_id ditemtokake dening в <bpf/bpf_helper_defs.h> perpustakaan libbpf carane

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

iku, bpf_get_smp_processor_id minangka pointer fungsi sing nilai 8, ing ngendi 8 minangka nilai BPF_FUNC_get_smp_processor_id Tipe enum bpf_fun_id, sing ditetepake kanggo kita ing file kasebut vmlinux.h (file bpf_helper_defs.h ing kernel kui dening script, supaya nomer "sihir" ok). Fungsi iki ora njupuk bantahan lan ngasilake nilai saka jinis __u32. Nalika kita mbukak ing program kita, clang ngasilake instruksi BPF_CALL "jenis sing bener" Ayo ngumpulake program lan deleng bagean kasebut 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

Ing baris pisanan kita ndeleng instruksi call, paramèter IMM kang padha karo 8, lan SRC_REG - nul. Miturut persetujuan ABI sing digunakake dening verifier, iki minangka telpon kanggo fungsi helper nomer wolu. Sawise diluncurake, logikane gampang. Nilai bali saka register r0 disalin menyang r1 lan ing baris 2,3 diowahi kanggo ngetik u32 - 32 bit ndhuwur wis dibusak. Ing baris 4,5,6,7 kita bali 2 (XDP_PASS) utawa 1 (XDP_DROP) gumantung apa fungsi helper saka baris 0 ngasilake nilai nul utawa non-nol.

Ayo kita nyoba dhewe: mbukak program lan ndeleng output bpftool prog dump xlated:

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

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

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

Ok, verifier nemokake kernel-helper sing bener.

Conto: maringaken argumen lan pungkasanipun mbukak program!

Kabeh fungsi helper run-level duwe prototipe

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

Parameter kanggo fungsi helper diterusake ing register r1-r5, lan nilai bali ing register r0. Ora ana fungsi sing njupuk luwih saka limang bantahan, lan dhukungan kanggo wong-wong mau ora samesthine bakal ditambahake ing mangsa ngarep.

Ayo goleki helper kernel anyar lan kepiye BPF ngliwati paramèter. Ayo nulis maneh xdp-simple.bpf.c kaya ing ngisor iki (liyane baris ora diganti):

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

Program kita nyithak nomer CPU sing digunakake. Ayo kompilasi lan deleng kode kasebut:

$ 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

Ing baris 0-7 kita nulis senar running on CPU%un, lan banjur ing baris 8 kita mbukak siji menowo bpf_get_smp_processor_id. Ing baris 9-12 kita nyiapake argumen helper bpf_printk - ndhaftar r1, r2, r3. Kok ana telu dudu loro? Amarga bpf_printkiki bungkus makro watara helper nyata bpf_trace_printk, sing kudu ngliwati ukuran string format.

Ayo saiki nambah saperangan baris kanggo xdp-simple.csupaya program kita nyambung menyang antarmuka lo lan tenan diwiwiti!

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

Ing kene kita nggunakake fungsi kasebut bpf_set_link_xdp_fd, sing nyambungake program BPF jinis XDP menyang antarmuka jaringan. We hardcoded nomer antarmuka lo, kang tansah 1. We mbukak fungsi kaping pindho kanggo pisanan copot program lawas yen ditempelake. Elinga yen saiki kita ora butuh tantangan pause utawa daur ulang tanpa wates: program loader kita bakal metu, nanging program BPF ora bakal matèni wiwit disambungake menyang sumber acara. Sawise download lan sambungan sukses, program bakal dibukak kanggo saben paket jaringan sing teka ing lo.

Ayo download program lan katon ing antarmuka lo:

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

Program sing diundhuh duwe ID 669 lan kita ndeleng ID sing padha ing antarmuka lo. Kita bakal ngirim sawetara paket menyang 127.0.0.1 (panyuwunan + wangsulan):

$ ping -c1 localhost

lan saiki ayo deleng isi file virtual debug /sys/kernel/debug/tracing/trace_pipe, kang bpf_printk dheweke nulis pesen:

# 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

Rong paket katon ing lo lan diproses ing CPU0 - program BPF sing ora ana gunane lengkap pisanan!

Perlu dielingi bpf_printk Iku ora kanggo apa-apa sing nulis menyang file debug: iki dudu helper paling sukses digunakake ing produksi, nanging goal kita kanggo nuduhake soko prasaja.

Ngakses peta saka program BPF

Conto: nggunakake peta saka program BPF

Ing bagean sadurunge, kita sinau babagan nggawe lan nggunakake peta saka ruang pangguna, lan saiki ayo ndeleng bagean kernel. Ayo miwiti, kaya biasane, kanthi conto. Ayo nulis maneh program kita xdp-simple.bpf.c kaya mangkene:

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

Ing wiwitan program kita nambah definisi peta woo: Iki minangka array 8-elemen sing nyimpen nilai kaya u64 (ing C kita bakal nemtokake array kaya u64 woo[8]). Ing program "xdp/simple" kita entuk nomer prosesor saiki dadi variabel key banjur nggunakake fungsi helper bpf_map_lookup_element kita njaluk pitunjuk kanggo entri cocog ing Uploaded, kang nambah siji. Diterjemahake menyang Rusia: kita ngetung statistik babagan CPU ngolah paket sing mlebu. Ayo nyoba mbukak program:

$ 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

Ayo priksa manawa dheweke wis kecanthol lo lan ngirim sawetara paket:

$ 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

Saiki ayo ndeleng isi array:

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

Meh kabeh pangolahan diproses ing CPU7. Iki ora penting kanggo kita, sing utama yaiku program kasebut bisa digunakake lan kita ngerti carane ngakses peta saka program BPF - nggunakake хелперов bpf_mp_*.

Indeks mistis

Dadi, kita bisa ngakses peta saka program BPF nggunakake telpon kaya

val = bpf_map_lookup_elem(&woo, &key);

ngendi fungsi helper katon kaya

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

nanging kita ngliwati pointer &woo menyang struktur sing ora dijenengi struct { ... }...

Yen kita katon ing assembler program, kita waca sing Nilai &woo ora ditetepake kanthi bener (baris 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
...

lan ana ing relokasi:

$ 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

Nanging yen kita ndeleng program sing wis dimuat, kita bakal weruh pointer menyang peta sing bener (baris 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]
...

Mangkono, kita bisa nyimpulake yen nalika mbukak program loader kita, link menyang &woo iki diganti dening soko karo perpustakaan libbpf. Pisanan kita bakal katon ing output 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

We ndeleng sing libbpf digawe peta woo banjur download program kita simple. Ayo goleki kanthi luwih rinci babagan carane mbukak program kasebut:

  • nelpon xdp_simple_bpf__open_and_load saka file xdp-simple.skel.h
  • kang njalari xdp_simple_bpf__load saka file xdp-simple.skel.h
  • kang njalari bpf_object__load_skeleton saka file libbpf/src/libbpf.c
  • kang njalari bpf_object__load_xattr saka libbpf/src/libbpf.c

Fungsi pungkasan, antarane liyane, bakal nelpon bpf_object__create_maps, sing nggawe utawa mbukak peta sing ana, ngowahi dadi deskriptor file. (Iki ngendi kita ndeleng BPF_MAP_CREATE ing output strace.) Sabanjure fungsi kasebut diarani bpf_object__relocate lan iku dheweke sing kapentingan kita, awit kita elinga apa kita weruh woo ing meja relokasi. Njelajah iku, kita pungkasanipun nemokake dhéwé ing fungsi bpf_program__relocate, kang urusan karo relokasi peta:

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

Supaya kita njupuk instruksi kita

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

lan ngganti ndhaftar sumber ing karo BPF_PSEUDO_MAP_FD, lan IMM pisanan menyang deskriptor file peta kita lan, yen padha karo, contone, 0xdeadbeef, banjur minangka asil kita bakal nampa instruksi

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

Iki carane informasi peta ditransfer menyang program BPF dimuat tartamtu. Ing kasus iki, peta bisa digawe nggunakake BPF_MAP_CREATE, lan dibukak dening ID nggunakake BPF_MAP_GET_FD_BY_ID.

Total, nalika nggunakake libbpf algoritma minangka nderek:

  • sajrone kompilasi, rekaman digawe ing tabel relokasi kanggo pranala menyang peta
  • libbpf mbukak buku obyek ELF, nemokake kabeh peta digunakake lan nggawe deskriptor file kanggo wong-wong mau
  • deskriptor file dimuat menyang kernel minangka bagéan saka instruksi LD64

Minangka sampeyan bisa mbayangno, ana liyane sing bakal teka lan kita kudu ndeleng inti. Untunge, kita duwe pitunjuk - kita wis nulis maknane BPF_PSEUDO_MAP_FD menyang daftar sumber lan kita bisa ngubur, sing bakal nuntun kita menyang suci kabeh wong suci - kernel/bpf/verifier.c, ing ngendi fungsi kanthi jeneng khas ngganti deskriptor file kanthi alamat struktur jinis 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;

(kode lengkap bisa ditemokake link). Supaya kita bisa nggedhekake algoritma kita:

  • nalika mbukak program, verifier mriksa panggunaan peta sing bener lan nulis alamat struktur sing cocog struct bpf_map

Nalika ngundhuh binar ELF nggunakake libbpf Ana akeh liyane, nanging kita bakal ngrembug babagan iki ing artikel liyane.

Loading program lan peta tanpa libbpf

Kaya sing dijanjekake, iki minangka conto kanggo para pamaca sing pengin ngerti carane nggawe lan mbukak program sing nggunakake peta, tanpa bantuan. libbpf. Iki bisa migunani yen sampeyan lagi kerja ing lingkungan sing sampeyan ora bisa mbangun dependensi, utawa nyimpen saben bit, utawa nulis program kaya ply, sing ngasilake kode binar BPF kanthi cepet.

Kanggo luwih gampang ngetutake logika, kita bakal nulis ulang conto kanggo tujuan kasebut xdp-simple. Kode lengkap lan rada ditambahi program sing dibahas ing conto iki bisa ditemokake ing iki gisty.

Logika aplikasi kita kaya ing ngisor iki:

  • nggawe peta jinis BPF_MAP_TYPE_ARRAY nggunakake printah BPF_MAP_CREATE,
  • nggawe program sing nggunakake peta iki,
  • nyambungake program menyang antarmuka lo,

kang nerjemahake menyang manungsa minangka

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

iku map_create nggawe peta kanthi cara sing padha kaya ing conto pisanan babagan telpon sistem bpf - "kernel, gawe aku peta anyar ing wangun array saka 8 unsur kaya __u64 lan wenehake maneh deskriptor file":

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

Program kasebut uga gampang dimuat:

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

Sisih angel prog_load yaiku definisi program BPF kita minangka susunan struktur struct bpf_insn insns[]. Nanging amarga kita nggunakake program sing ana ing C, kita bisa ngapusi sethithik:

$ 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

Secara total, kita kudu nulis 14 instruksi ing wangun struktur kaya struct bpf_insn (saran: njupuk mbucal saka ndhuwur, maca maneh bagean instruksi, mbukak linux/bpf.h и linux/bpf_common.h lan nyoba kanggo nemtokake struct bpf_insn insns[] dhewe):

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

Latihan kanggo sing ora nulis iki dhewe - golek map_fd.

Ana siji bagean liyane sing ora dingerteni ing program kita - xdp_attach. Sayange, program kaya XDP ora bisa disambungake nggunakake panggilan sistem bpf. Wong-wong sing nggawe BPF lan XDP asale saka komunitas Linux online, tegese padha nggunakake sing paling akrab karo dheweke (nanging ora lumrah wong) antarmuka kanggo sesambungan karo kernel: soket netlink, deloken sisan RFC3549. Cara paling gampang kanggo ngleksanakake xdp_attach iku nyalin kode saka libbpf, yaiku, saka file netlink.c, yaiku apa sing ditindakake, nyepetake sethithik:

Sugeng rawuh ing jagad soket netlink

Bukak jinis soket netlink NETLINK_ROUTE:

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

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

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

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

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

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

    *nl_pid = sa.nl_pid;
    return sock;
}

Kita maca saka soket iki:

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

Pungkasan, iki fungsi kita sing mbukak soket lan ngirim pesen khusus sing ngemot deskriptor file:

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

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

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

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

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

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

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

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

cleanup:
    close(sock);
    return ret;
}

Dadi, kabeh wis siyap kanggo tes:

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

Ayo ndeleng apa program kita wis nyambung menyang 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

Ayo ngirim ping lan deleng peta:

$ 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

Hurray, kabeh bisa. Elinga, yen peta kita ditampilake maneh ing bentuk bita. Iki amarga kasunyatan sing, ora kaya libbpf kita ora mbukak informasi jinis (BTF). Nanging kita bakal ngomong liyane babagan iki ing wektu sabanjure.

Piranti Pangembangan

Ing bagean iki, kita bakal ndeleng toolkit pangembang BPF minimal.

Umumé, sampeyan ora butuh apa-apa khusus kanggo ngembangake program BPF - BPF mlaku ing kernel distribusi sing apik, lan program dibangun nggunakake clang, sing bisa diwenehake saka paket. Nanging, amarga kasunyatane BPF lagi dikembangake, kernel lan alat terus ganti, yen sampeyan ora pengin nulis program BPF nggunakake metode kuno wiwit taun 2019, mula sampeyan kudu ngumpulake.

  • llvm/clang
  • pahole
  • inti sawijining
  • bpftool

(Kanggo referensi, bagean iki lan kabeh conto ing artikel kasebut ditindakake ing Debian 10.)

llvm/clang

BPF ramah karo LLVM lan, sanajan saiki program kanggo BPF bisa dikompilasi nggunakake gcc, kabeh pembangunan saiki ditindakake kanggo LLVM. Mulane, pisanan kabeh, kita bakal mbangun versi saiki clang saka 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
... много времени спустя
$

Saiki kita bisa mriksa yen kabeh wis rampung kanthi bener:

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

(Instruksi Majelis clang dijupuk dening kula saka bpf_devel_QA.)

Kita ora bakal nginstal program sing lagi wae dibangun, nanging mung ditambahake PATH, contone:

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

(Iki bisa ditambahake menyang .bashrc utawa menyang file sing kapisah. Secara pribadi, aku nambahake perkara kaya iki ~/bin/activate-llvm.sh lan yen perlu aku nindakake . activate-llvm.sh.)

Pahole lan BTF

Utilitas pahole digunakake nalika mbangun kernel kanggo nggawe informasi debugging ing format BTF. Kita ora bakal nerangake rinci ing artikel iki babagan rincian teknologi BTF, kajaba kasunyatan sing trep lan pengin digunakake. Dadi yen sampeyan arep mbangun kernel, gawe dhisik pahole (tanpa pahole sampeyan ora bakal bisa kanggo mbangun kernel karo pilihan 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 kanggo eksperimen karo BPF

Nalika njelajah kemungkinan BPF, Aku pengin ngumpul inti dhewe. Iki, umume ngandika, ora perlu, amarga sampeyan bakal bisa ngumpulake lan mbukak program BPF ing kernel distribusi, Nanging, duwe kernel dhewe ngijini sampeyan kanggo nggunakake fitur BPF paling anyar, kang bakal katon ing distribusi ing sasi paling apik. , utawa, kaya ing sawetara alat debugging ora bakal dikemas ing mangsa ngarep. Uga, inti dhewe nggawe penting kanggo eksprimen karo kode kasebut.

Kanggo mbangun kernel sampeyan kudu, pisanan, kernel dhewe, lan kapindho, file konfigurasi kernel. Kanggo eksprimen karo BPF kita bisa nggunakake biasanipun vanila kernel utawa salah sawijining kernel pangembangan. Secara historis, pangembangan BPF dumadi ing komunitas jaringan Linux lan mula kabeh owah-owahan cepet utawa mengko liwat David Miller, pangurus jaringan Linux. Gumantung saka sifate - suntingan utawa fitur anyar - owah-owahan jaringan dadi salah siji saka rong inti - net utawa net-next. Owah-owahan kanggo BPF mbagekke ing cara sing padha antarane bpf и bpf-next, sing banjur dikumpulake dadi net lan net-next, mungguh. Kanggo rincian liyane, ndeleng bpf_devel_QA и netdev-FAQ. Dadi, pilih kernel adhedhasar rasa sampeyan lan kabutuhan stabilitas sistem sing sampeyan coba (*-next kernels sing paling ora stabil saka sing kadhaptar).

Ora ana ruang lingkup artikel iki kanggo ngomong babagan carane ngatur file konfigurasi kernel - dianggep sampeyan wis ngerti carane nindakake iki, utawa siap sinau dhewe. Nanging, instruksi ing ngisor iki kudu luwih utawa kurang cukup kanggo menehi sistem aktif BPF sing bisa digunakake.

Ngundhuh salah sawijining kernel ing ndhuwur:

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

Gawe konfigurasi kernel sing bisa digunakake minimal:

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

Aktifake pilihan BPF ing file .config pilihan sampeyan dhewe (paling kamungkinan CONFIG_BPF wis diaktifake wiwit systemd nggunakake). Ing ngisor iki dhaptar pilihan saka kernel sing digunakake kanggo artikel iki:

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

Banjur kita bisa kanthi gampang ngumpul lan nginstal modul lan kernel (kanthi cara, sampeyan bisa ngumpulake kernel nggunakake sing mentas dipasang. clangkanthi nambah CC=clang):

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

lan urip maneh nganggo kernel anyar (aku gunakake kanggo iki kexec saka paket 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

Utilitas sing paling umum digunakake ing artikel kasebut yaiku sarana bpftool, diwenehake minangka bagéan saka kernel Linux. Iki ditulis lan dikelola dening pangembang BPF kanggo pangembang BPF lan bisa digunakake kanggo ngatur kabeh jinis obyek BPF - mbukak program, nggawe lan ngowahi peta, njelajah urip ekosistem BPF, lsp. Dokumentasi arupa kode sumber kanggo kaca manual bisa ditemokake ing inti utawa, wis disusun, ing net.

Ing wektu iki nulis bpftool kasedhiya mung kanggo RHEL, Fedora lan Ubuntu (ndeleng, contone, thread iki, sing nyritakake babagan kemasan sing durung rampung bpftool ing Debian). Nanging yen sampeyan wis mbangun kernel, banjur mbangun bpftool gampang kaya pie:

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

$

(Niki ${linux} - iki direktori kernel Panjenengan.) Sawise nglakokaké printah iki bpftool bakal diklumpukake ing direktori ${linux}/tools/bpf/bpftool lan bisa ditambahake menyang path (pisanan kanggo pangguna root) utawa mung nyalin menyang /usr/local/sbin.

Ngumpulake bpftool luwih becik nggunakake sing terakhir clang, diklumpukake kaya sing kasebut ing ndhuwur, lan priksa manawa dipasang kanthi bener - nggunakake, contone, perintah kasebut

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

sing bakal nuduhake fitur BPF sing diaktifake ing kernel sampeyan.

Miturut cara, printah sadurungé bisa mbukak minangka

# bpftool f p k

Iki ditindakake kanthi analogi karo utilitas saka paket kasebut iproute2, ngendi kita bisa, contone, ngomong ip a s eth0 tinimbang ip addr show dev eth0.

kesimpulan

BPF ngijini sampeyan kanggo sepatu kutu kanggo èfèktif ngukur lan on-the-fly ngganti fungsi inti. Sistem kasebut sukses banget, ing tradhisi UNIX sing paling apik: mekanisme prasaja sing ngidini sampeyan (maneh) program kernel ngidini akeh wong lan organisasi kanggo eksperimen. Lan, sanajan eksperimen, uga pangembangan infrastruktur BPF dhewe, durung rampung, sistem kasebut wis duwe ABI stabil sing ngidini sampeyan mbangun logika bisnis sing dipercaya lan sing paling penting.

Aku pengin Wigati sing, ing mratelakake panemume, teknologi wis dadi populer amarga, ing tangan siji, iku bisa kanggo muter (arsitektur mesin bisa dimangerteni luwih utawa kurang ing sawijining sore), lan ing tangan liyane, kanggo ngatasi masalah sing ora bisa ditanggulangi (apik) sadurunge katon. Iki loro komponen bebarengan meksa wong kanggo eksprimen lan ngimpi, kang ndadékaké kanggo emergence saka solusi liyane lan liyane inovatif.

Artikel iki, sanajan ora cendhak, mung minangka introduksi ing donya BPF lan ora njlèntrèhaké fitur "maju" lan bagéan penting saka arsitektur. Rencana sing bakal ditindakake kaya mangkene: artikel sabanjure bakal ringkesan jinis program BPF (ana 5.8 jinis program sing didhukung ing kernel 30), banjur pungkasane kita bakal ndeleng carane nulis aplikasi BPF nyata nggunakake program tracing kernel. minangka conto, banjur iku wektu kanggo kursus liyane ing-ambane arsitektur BPF, ngiring dening conto jaringan BPF lan aplikasi keamanan.

Artikel sadurunge ing seri iki

  1. BPF kanggo bocah cilik, bagean nol: BPF klasik

Pranala

  1. Pandhuan Referensi BPF lan XDP - dokumentasi ing BPF saka cilium, utawa luwih tepat saka Daniel Borkman, salah siji saka pencipta lan maintainers saka BPF. Iki minangka salah sawijining deskripsi serius pisanan, sing beda karo liyane amarga Daniel ngerti persis apa sing ditulis lan ora ana kesalahan. Khususé, dokumen iki njlèntrèhaké cara nggarap program BPF saka jinis XDP lan TC nggunakake sarana sing kondhang. ip saka paket iproute2.

  2. Dokumentasi/jaringan/filter.txt - file asli karo dokumentasi kanggo klasik lan banjur lengkap BPF. Wacan sing apik yen sampeyan pengin sinau basa perakitan lan rincian arsitektur teknis.

  3. Blog babagan BPF saka facebook. Dianyari arang banget, nanging pas, amarga Alexei Starovoitov (penulis eBPF) lan Andrii Nakryiko - (maintainer) nulis ing kana. libbpf).

  4. Rahasia saka bpftool. Utas twitter sing nyenengake saka Quentin Monnet kanthi conto lan rahasia nggunakake bpftool.

  5. Nyilem menyang BPF: dhaptar bahan wacan. A raksasa (lan isih maintained) dhaftar pranala menyang dokumentasi BPF saka Quentin Monnet.

Source: www.habr.com

Add a comment