BPF keur leutik, bagian hiji: BPF nambahan

Dina awalna aya téhnologi sarta disebut BPF. Urang nempo manehna saméméhna, artikel Perjanjian Old runtuyan ieu. Dina 2013, ngaliwatan usaha Alexei Starovoitov jeung Daniel Borkman, versi ningkat tina eta, dioptimalkeun pikeun mesin 64-bit modern, dimekarkeun tur kaasup kana kernel Linux Ubuntu. Téknologi anyar ieu sakeudeung disebut Internal BPF, teras dingaranan Extended BPF, sareng ayeuna, saatos sababaraha taun, sadayana nyebatna BPF.

Sacara kasar, BPF ngamungkinkeun anjeun pikeun ngajalankeun kode anu disayogikeun ku pangguna dina rohangan kernel Linux, sareng arsitéktur énggal tétéla suksés sahingga urang peryogi langkung seueur artikel pikeun ngajelaskeun sadaya aplikasina. (Hiji-hijina hal anu henteu dilakukeun ku pamekar, sakumaha anu anjeun tingali dina kode kinerja di handap ieu, nyaéta nyiptakeun logo anu santun.)

Artikel ieu ngajelaskeun struktur mesin virtual BPF, interfaces kernel pikeun gawé bareng BPF, parabot ngembangkeun, kitu ogé ringkes, Tinjauan pisan ringkes kamampuhan aya, i.e. sadayana anu urang peryogikeun ka hareup pikeun diajar anu langkung jero ngeunaan aplikasi praktis BPF.
BPF keur leutik, bagian hiji: BPF nambahan

Ringkesan artikel

Bubuka arsitektur BPF. Kahiji, urang bakal nyandak pandangan panon manuk ngeunaan arsitektur BPF jeung outline komponén utama.

Registers sarta sistem paréntah tina mesin virtual BPF. Parantos gaduh ide ngeunaan arsitéktur sacara gembleng, kami bakal ngajelaskeun struktur mesin virtual BPF.

Daur hirup objék BPF, sistem file bpffs. Dina bagian ieu, urang bakal nyandak katingal ngadeukeutan dina siklus hirup objék BPF - program jeung peta.

Ngatur objék nganggo bpf system call. Kalayan sababaraha pamahaman sistem anu parantos aya, urang tungtungna bakal ningali kumaha cara nyiptakeun sareng ngamanipulasi objék tina rohangan pangguna nganggo telepon sistem khusus - bpf(2).

Пишем программы BPF с помощью libbpf. Tangtosna, anjeun tiasa nyerat program nganggo telepon sistem. Tapi hese. Pikeun skenario anu langkung realistis, programer nuklir ngembangkeun perpustakaan libbpf. Kami bakal nyiptakeun kerangka aplikasi BPF dasar anu bakal kami anggo dina conto anu salajengna.

Kernel Helpers. Di dieu urang bakal diajar kumaha program BPF tiasa ngaksés fungsi pembantu kernel - alat anu, sareng peta, dasarna ngalegaan kamampuan BPF énggal dibandingkeun sareng anu klasik.

Aksés ka peta tina program BPF. Ku titik ieu, urang bakal nyaho cukup pikeun ngarti persis kumaha urang bisa nyieun program nu make peta. Sarta hayu urang malah nyandak Toong gancang kana verifier hébat sarta perkasa.

parabot ngembangkeun. Pitulung bagian ngeunaan kumaha carana ngumpul utilitas diperlukeun tur kernel pikeun percobaan.

Kacindekan. Dina ahir tulisan, jalma anu maca dugi ka ieu bakal mendakan kecap-kecap anu ngamotivasi sareng katerangan ringkes ngeunaan naon anu bakal kajadian dina tulisan-tulisan di handap ieu. Urang ogé bakal daptar sababaraha tumbu pikeun timer ulikan pikeun maranéhanana anu teu boga kahayang atawa kamampuhan pikeun ngadagoan tuluyan.

Bubuka keur BPF Arsitéktur

Sateuacan urang ngawitan mertimbangkeun arsitéktur BPF, urang bakal ngarujuk hiji panungtungan waktu (oh) ka BPF Palasik, anu dikembangkeun salaku respon kana mecenghulna mesin RISC sarta direngsekeun masalah panyaring pakét efisien. Arsitéktur tétéla jadi suksés sahingga, sanggeus dilahirkeun dina nineties dashing di Berkeley UNIX, ieu porting ka paling sistem operasi aya, salamet kana duapuluhan gélo sarta masih manggihan aplikasi anyar.

BPF anyar dikembangkeun salaku réspon kana ubiquity mesin 64-bit, jasa awan sareng paningkatan kabutuhan alat pikeun nyiptakeun SDN (Sbiasa-ddititah neworking). Dimekarkeun ku insinyur jaringan kernel salaku gaganti ningkat pikeun BPF klasik, BPF anyar sacara harfiah genep bulan engké kapanggih aplikasi dina tugas hésé tracing sistem Linux Ubuntu, sarta ayeuna, genep taun sanggeus penampilan na, urang bakal butuh sakabeh artikel salajengna ngan pikeun daptar tipena béda program.

Gambar lucu

Dina inti na, BPF mangrupakeun mesin virtual sandbox nu ngidinan Anjeun pikeun ngajalankeun kode "sawenang" dina spasi kernel tanpa kompromi kaamanan. Program BPF dijieun dina rohangan pamaké, dimuat kana kernel, sarta disambungkeun ka sababaraha sumber acara. Hiji acara tiasa, contona, pangiriman pakét ka antarmuka jaringan, peluncuran sababaraha fungsi kernel, jsb. Dina kasus pakét, program BPF bakal gaduh aksés kana data sareng metadata pakét (pikeun maca sareng, sigana, nyerat, gumantung kana jinis program); dina kasus ngajalankeun fungsi kernel, argumen tina fungsi, kaasup pointers ka memori kernel, jsb.

Hayu urang nyandak katingal ngadeukeutan dina prosés ieu. Pikeun mimitian ku, hayu urang ngobrol ngeunaan bédana munggaran ti BPF Palasik, program nu ditulis dina assembler. Dina versi anyar, arsitéktur ieu dimekarkeun jadi program bisa ditulis dina basa-tingkat tinggi, utamana, tangtosna, dina C. Pikeun ieu, hiji backend pikeun llvm dikembangkeun, nu ngidinan generating bytecode pikeun arsitektur BPF.

BPF keur leutik, bagian hiji: BPF nambahan

Arsitéktur BPF dirancang, sabagian, pikeun ngajalankeun éfisién dina mesin modern. Pikeun ngalaksanakeun ieu prakna, bytecode BPF, sakali dimuat kana kernel, ditarjamahkeun kana kode asli nganggo komponén anu disebut JIT compiler (Just In Tiyeu). Salajengna, upami anjeun émut, dina BPF klasik, programna dimuat kana kernel sareng digantelkeun kana sumber acara sacara atom - dina konteks sauran sistem tunggal. Dina arsitéktur anyar, ieu lumangsung dina dua tahap - kahiji, kode dimuat kana kernel ngagunakeun panggero sistem bpf(2)lajeng, engké, ngaliwatan mékanisme séjén anu rupa-rupa gumantung kana jenis program, program nempel kana sumber acara.

Di dieu nu maca mungkin gaduh patarosan: éta mungkin? Kumaha kasalametan palaksanaan kode sapertos kitu dijamin? Kasalametan palaksanaan dijamin ku kami ku tahap ngamuat program BPF anu disebut verifier (dina basa Inggris tahap ieu disebut verifier sareng kuring bakal teras nganggo kecap Inggris):

BPF keur leutik, bagian hiji: BPF nambahan

Verifier mangrupikeun analisa statik anu mastikeun yén program henteu ngaganggu operasi normal kernel. Ku jalan kitu, lain hartosna yén program teu tiasa ngaganggu operasi sistem - program BPF, gumantung kana jenis, bisa maca jeung nulis ulang bagian memori kernel, balik nilai fungsi, motong, append, nulis balik. komo pakét jaringan maju. Verifier ngajamin yén ngajalankeun program BPF moal ngadat kernel sarta yén program nu, nurutkeun aturan, boga aksés nulis, contona, data pakét kaluar, moal bisa nimpa memori kernel luar pakét. Urang bakal kasampak di verifier dina saeutik leuwih jéntré dina bagian pakait, sanggeus urang meunang acquainted jeung sakabeh komponén séjén BPF.

Janten naon anu urang diajar dugi ka ayeuna? Pamaké nyerat program dina C, ngamuat kana kernel nganggo panggero sistem bpf(2), dimana eta dipariksa ku verifier sarta ditarjamahkeun kana bytecode asli. Teras pangguna anu sami atanapi anu sanés ngahubungkeun program éta ka sumber acara sareng éta mimiti ngaéksekusi. Misahkeun boot sareng sambungan dipikabutuh pikeun sababaraha alesan. Anu mimiti, ngajalankeun verifier relatif mahal sareng ku ngaunduh program anu sami sababaraha kali urang miceunan waktos komputer. Kadua, persis kumaha program disambungkeun gumantung kana jinisna, sareng hiji antarmuka "universal" anu dikembangkeun sataun katukang panginten henteu cocog pikeun jinis program énggal. (Sanaos ayeuna arsitéktur janten langkung dewasa, aya ide pikeun ngahijikeun antar muka ieu dina tingkat libbpf.)

Pamaca anu ati-ati tiasa perhatikeun yén kami henteu acan réngsé gambar-gambarna. Mémang, sadayana di luhur henteu ngajelaskeun naha BPF dasarna ngarobih gambar dibandingkeun sareng BPF klasik. Dua inovasi anu sacara signifikan ngalegaan ruang lingkup aplikasi nyaéta kamampuan ngagunakeun mémori anu dibagi sareng fungsi pembantu kernel. Dina BPF, memori dibagikeun dilaksanakeun ngagunakeun disebut peta - struktur data dibagikeun kalawan API husus. Éta sigana ngagaduhan nami ieu kusabab jinis peta anu munggaran muncul nyaéta méja hash. Lajeng arrays mucunghul, tabel hash lokal (per-CPU) jeung arrays lokal, tangkal pilarian, peta ngandung pointers kana program BPF jeung leuwih. Anu pikaresepeun pikeun urang ayeuna nyaéta program BPF ayeuna gaduh kamampuan pikeun tetep kaayaan antara telepon sareng ngabagikeunana ka program sanés sareng ruang pangguna.

Peta diaksés tina prosés pamaké ngagunakeun panggero sistem bpf(2), sareng tina program BPF dijalankeun dina kernel nganggo fungsi pembantu. Sumawona, asisten henteu ngan ukur damel sareng peta, tapi ogé pikeun ngaksés kamampuan kernel anu sanés. Contona, program BPF bisa ngagunakeun fungsi helper pikeun neraskeun pakét ka interfaces séjén, ngahasilkeun acara perf, struktur kernel aksés, jeung saterusna.

BPF keur leutik, bagian hiji: BPF nambahan

Kasimpulanana, BPF nyadiakeun kamampuhan pikeun ngamuat sawenang-wenang, nyaéta, verifier-dites, kode pamaké kana spasi kernel. Kode ieu tiasa ngahemat kaayaan antara telepon sareng tukeur data sareng rohangan pangguna, sareng ogé ngagaduhan aksés kana subsistem kernel anu diidinan ku program jinis ieu.

Ieu parantos sami sareng kamampuan anu disayogikeun ku modul kernel, dibandingkeun sareng BPF anu ngagaduhan sababaraha kaunggulan (tangtosna, anjeun ngan ukur tiasa ngabandingkeun aplikasi anu sami, contona, ngalacak sistem - anjeun moal tiasa nyerat supir sawenang-wenang sareng BPF). Anjeun tiasa nyatet ambang éntri anu langkung handap (sababaraha utilitas anu nganggo BPF henteu meryogikeun pangguna gaduh kaahlian program kernel, atanapi kaahlian program sacara umum), kaamanan runtime (angkat leungeun anjeun dina koméntar pikeun anu henteu ngalanggar sistem nalika nyerat. atawa modul nguji), atomicity - aya downtime nalika reloading modul, sarta subsistem BPF ensures yén euweuh acara lasut (janten adil, ieu teu bener keur sakabeh tipe program BPF).

Ayana kamampuan sapertos kitu ngajadikeun BPF alat universal pikeun ngalegaan kernel, anu dikonfirmasi dina praktékna: beuki loba jenis program anyar ditambahkeun kana BPF, beuki loba pausahaan badag ngagunakeun BPF dina server tempur 24 × 7, beuki loba. startups ngawangun bisnis maranéhanana dina solusi dumasar kana nu dumasar kana BPF. BPF dianggo di mana waé: dina ngajagaan tina serangan DDoS, nyiptakeun SDN (contona, ngalaksanakeun jaringan pikeun kubernetes), salaku alat panyajak sistem utama sareng kolektor statistik, dina sistem deteksi intrusion sareng sistem kotak pasir, jsb.

Hayu urang réngsé bagian tinjauan artikel di dieu sareng tingali mesin virtual sareng ékosistem BPF sacara langkung rinci.

Digression: utiliti

Pikeun bisa ngajalankeun conto dina bagian handap, Anjeun bisa jadi kudu sababaraha Utiliti, sahenteuna llvm/clang kalayan rojongan bpf na bpftool. Dina bagian Pakakas Pangwangunan Anjeun tiasa maca parentah pikeun assembling utiliti, kitu ogé kernel Anjeun. Bagian ieu disimpen di handap supados henteu ngaganggu kaharmonisan presentasi urang.

BPF Mesin Virtual ngadaptar sarta Sistem Parentah

Arsitéktur sareng sistem paréntah BPF dikembangkeun kalayan merhatikeun kanyataan yén program bakal ditulis dina basa C sareng, saatos dimuat kana kernel, ditarjamahkeun kana kode asli. Ku alatan éta, jumlah registers jeung susunan paréntah dipilih kalawan panon ka simpang, dina rasa matematik, tina kamampuhan mesin modern. Sajaba ti éta, rupa-rupa larangan anu ditumpukeun dina program, contona, nepi ka ayeuna teu mungkin nulis puteran jeung subrutin, sarta jumlah parentah ieu dugi ka 4096 (ayeuna program husus bisa muka nepi ka sajuta parentah).

BPF boga sabelas registers 64-bit pamaké-diaksés r0-r10 sarta counter program. Ngadaptar r10 ngandung pointer pigura jeung dibaca wungkul. Program ngagaduhan aksés kana tumpukan 512-bait dina waktos jalan sareng jumlah mémori anu henteu terbatas dina bentuk peta.

Program BPF diidinan ngajalankeun sakumpulan spésifik program-jenis pembantu kernel sareng, langkung énggal, fungsi biasa. Unggal fungsi disebut bisa nyandak nepi ka lima argumen, diliwatan dina registers r1-r5, sarta nilai balik disalurkeun ka r0. Ieu dijamin yén sanggeus balik ti fungsi, eusi registers r6-r9 Moal robah.

Pikeun tarjamahan program efisien, registers r0-r11 pikeun sakabéh arsitéktur dirojong unik dipetakeun kana registers nyata, nyokot kana akun fitur ABI arsitektur ayeuna. Contona, pikeun x86_64 ngadaptar r1-r5, dipaké pikeun lulus parameter fungsi, dipintonkeun dina rdi, rsi, rdx, rcx, r8, nu dipaké pikeun ngalirkeun parameter kana fungsi dina x86_64. Contona, kode di kénca ditarjamahkeun kana kode di katuhu kawas kieu:

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

Ngadaptar r0 ogé dipaké pikeun mulangkeun hasil palaksanaan program, sarta dina register r1 program disalurkeun pointer kana konteks - gumantung kana jenis program, ieu bisa jadi, contona, struktur struct xdp_md (pikeun XDP) atawa struktur struct __sk_buff (pikeun program jaringan béda) atawa struktur struct pt_regs (pikeun tipena béda program tracing), jsb.

Janten, urang ngagaduhan sakumpulan register, pembantu kernel, tumpukan, panunjuk kontéks sareng mémori anu dibagi dina bentuk peta. Henteu sadayana ieu leres-leres diperyogikeun dina perjalanan, tapi ...

Hayu urang neruskeun pedaran tur ngobrol ngeunaan sistem paréntah pikeun gawé bareng objék ieu. sadayana (Méh kabéh) Parentah BPF boga ukuran 64-bit tetep. Lamun nempo hiji instruksi dina mesin Big Endian 64-bit anjeun bakal nempo

BPF keur leutik, bagian hiji: BPF nambahan

Ieu téh Code - ieu mangrupikeun encoding instruksi, Dst/Src nyaéta encodings tina panarima jeung sumber, masing-masing, Off - 16-bit ditandatanganan indentation, jeung Imm mangrupakeun 32-bit ditandatanganan integer dipaké dina sababaraha parentah (sarupa jeung cBPF konstanta K). Encoding Code boga salah sahiji dua jenis:

BPF keur leutik, bagian hiji: BPF nambahan

Kelas instruksi 0, 1, 2, 3 nangtukeun paréntah pikeun gawé bareng memori. aranjeunna disebut, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, masing-masing. Kelas 4, 7 (BPF_ALU, BPF_ALU64) mangrupakeun sakumpulan parentah ALU. Kelas 5, 6 (BPF_JMP, BPF_JMP32) ngandung parentah luncat.

Rencana satuluyna pikeun ngulik sistem instruksi BPF nyaéta kieu: tinimbang sacara saksama daptar sadaya paréntah sareng parameterna, urang bakal ningali sababaraha conto dina bagian ieu sareng ti aranjeunna bakal écés kumaha paréntahna leres-leres jalanna sareng kumaha carana. sacara manual ngabongkar file binér pikeun BPF. Pikeun ngahijikeun bahan engké dina tulisan, urang ogé bakal pendak sareng petunjuk individu dina bagian ngeunaan Verifier, kompiler JIT, tarjamahan BPF klasik, ogé nalika diajar peta, fungsi nelepon, jsb.

Lamun urang ngobrol ngeunaan parentah individu, urang bakal tingal file inti bpf.h и bpf_common.h, nu nangtukeun Konci numeris parentah BPF. Nalika ngulik arsitéktur sorangan sareng / atanapi nga-parsing binari, anjeun tiasa mendakan semantik dina sumber-sumber di handap ieu, diurutkeun dina pajeulitna: spésifikasi eBPF henteu resmi, BPF na XDP Rujukan Guide, instruksi Set, Dokuméntasi/jaringan/filter.txt jeung, tangtu, dina kode sumber Linux Ubuntu - verifier, JIT, BPF juru.

Conto: ngabongkar BPF dina sirah anjeun

Hayu urang tingali conto dimana urang nyusun program readelf-example.c jeung kasampak di binér hasilna. Urang bakal nembongkeun eusi aslina readelf-example.c handap, sanggeus urang mulangkeun logika na tina kode binér:

$ 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 munggaran dina kaluaran readelf nyaéta indentation sareng program kami diwangun ku opat paréntah:

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

Kodeu paréntah sarua b7, 15, b7 и 95. Émut yén tilu bit anu paling henteu signifikan nyaéta kelas instruksi. Dina kasus urang, bit kaopat sadaya parentah kosong, jadi kelas instruksi masing-masing 7, 5, 7, 5. Kelas 7 nyaeta BPF_ALU64,jeung 5 BPF_JMP. Pikeun duanana kelas, format instruksi sami (tingali di luhur) sareng urang tiasa nyerat deui program sapertos kieu (dina waktos anu sami urang bakal nyerat deui kolom sésana dina wujud manusa):

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 класса ALU64 - eta BPF_MOV. Ieu nangtukeun nilai ka register tujuan. Lamun bit diatur s (sumber), teras nilaina dicandak tina sumber register, sareng upami, sapertos dina kasus urang, éta henteu diatur, maka nilaina dicandak tina lapangan. Imm. Janten dina paréntah kahiji sareng katilu urang ngalaksanakeun operasi r0 = Imm. Salajengna, JMP kelas 1 operasi nyaéta BPF_JEQ (luncat lamun sarua). Dina kasus urang, saprak bit S nyaeta enol, eta compares nilai register sumber kalawan sawah Imm. Lamun nilai coincide, teras transisi lumangsung ka PC + Offdimana PC, sakumaha biasa, ngandung alamat instruksi salajengna. Tungtungna, JMP Kelas 9 Operasi nyaeta BPF_EXIT. Parentah ieu ngeureunkeun program, balik deui ka kernel r0. Hayu urang tambahkeun kolom anyar kana méja kami:

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

Urang tiasa nyerat deui ieu dina bentuk anu langkung merenah:

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

Lamun urang apal naon dina register r1 program ieu diliwatan pointer kana konteks ti kernel, sarta dina register r0 nilaina dipulangkeun ka kernel, teras urang tiasa ningali yén upami pointer kana kontéks éta nol, teras urang uih deui 1, sareng sanés - 2. Hayu urang pariksa yén kami leres ku ningali sumberna:

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

Leres, éta mangrupikeun program anu teu aya artina, tapi ditarjamahkeun kana ngan ukur opat paréntah saderhana.

conto iwal: instruksi 16-bait

Urang disebutkeun tadi yén sababaraha parentah nyandak up leuwih ti 64 bit. Ieu lumaku, contona, pikeun parentah lddw (Kode = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - muatkeun kecap ganda tina widang kana register Imm. Kanyataanna éta Imm ngabogaan ukuran 32, sarta kecap ganda 64 bit, jadi loading a 64-bit nilai saharita kana register dina hiji instruksi 64-bit moal jalan. Jang ngalampahkeun ieu, dua parentah padeukeut dipaké pikeun nyimpen bagian kadua nilai 64-bit dina widang. Imm... Conto:

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

Aya ngan dua parentah dina program binér:

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

Urang bakal papanggih deui jeung parentah lddw, nalika urang ngobrol ngeunaan relokasi sareng damel sareng peta.

Conto: ngabongkar BPF nganggo alat standar

Janten, kami parantos diajar maca kode binér BPF sareng siap ngémutan instruksi naon waé upami diperyogikeun. Nanging, éta patut nyarios yén dina prakna éta langkung gampang sareng langkung gancang ngabongkar program nganggo alat standar, contona:

$ 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

Daur hirup objék BPF, sistem file bpffs

(Kuring mimiti diajar sababaraha rinci anu dijelaskeun dina subseksi ieu tina pos Alexei Starovoitov dina BPF Blog.)

Objék BPF - program sareng peta - didamel tina rohangan pangguna nganggo paréntah BPF_PROG_LOAD и BPF_MAP_CREATE panggero sistem bpf(2), urang bakal ngobrol ngeunaan persis kumaha ieu kajadian dina bagian salajengna. Ieu nyiptakeun struktur data kernel sareng masing-masing refcount (count rujukan) disetel ka hiji, sarta descriptor file ngarah ka obyék dipulangkeun ka pamaké. Sanggeus gagangna ditutup refcount obyék diréduksi ku hiji, sarta lamun ngahontal enol, obyék ancur.

Upami program nganggo peta, teras refcount peta ieu ngaronjat ku hiji sanggeus loading program, i.e. descriptors file maranéhanana bisa ditutup tina prosés pamaké sarta masih refcount moal jadi nol:

BPF keur leutik, bagian hiji: BPF nambahan

Saatos junun ngamuat program, urang biasana ngagantelkeun kana sababaraha jinis generator acara. Contona, urang bisa nempatkeun eta dina panganteur jaringan pikeun ngolah pakét asup atawa nyambung ka sababaraha tracepoint dina inti. Dina titik ieu, counter rujukan ogé bakal ningkat ku hiji sareng urang bakal tiasa nutup deskriptor file dina program loader.

Naon anu lumangsung upami urang ayeuna mareuman bootloader? Ieu gumantung kana jenis generator acara (hook). Sadaya kait jaringan bakal aya saatos loader réngsé, ieu mangrupikeun anu disebut kait global. Sareng, contona, program ngalacak bakal dileupaskeun saatos prosés anu nyiptakeunana ditungtungan (ku kituna disebut lokal, tina "lokal ka prosés"). Téhnisna, kait lokal sok gaduh deskriptor file anu cocog dina rohangan pangguna sareng ku kituna nutup nalika prosésna ditutup, tapi kait global henteu. Dina gambar di handap ieu, ngagunakeun crosses beureum, Kuring nyoba némbongkeun kumaha terminasi tina program loader mangaruhan hirupna objék dina kasus hook lokal jeung global.

BPF keur leutik, bagian hiji: BPF nambahan

Naha aya bédana antara hook lokal jeung global? Ngajalankeun sababaraha jenis program jaringan asup akal tanpa spasi pamaké, contona, ngabayangkeun panyalindungan DDoS - bootloader nulis aturan jeung nyambungkeun program BPF ka panganteur jaringan, nu satutasna bootloader nu bisa balik sarta maéhan sorangan. Di sisi anu sanés, bayangkeun program ngalacak debugging anu anjeun tulis dina tuur anjeun dina sapuluh menit - nalika réngsé, anjeun hoyong teu aya sampah anu tinggaleun dina sistem, sareng kait lokal bakal mastikeun éta.

Di sisi anu sanésna, bayangkeun yén anjeun hoyong nyambung ka titik palacak dina kernel sareng ngumpulkeun statistik salami mangtaun-taun. Dina hal ieu, anjeun bakal hoyong ngalengkepan bagian pamaké sarta balik deui ka statistik ti jaman ka jaman. Sistem file bpf nyayogikeun kasempetan ieu. Ieu mangrupikeun sistem pseudo-file ngan ukur dina mémori anu ngamungkinkeun nyiptakeun file anu ngarujuk objék BPF sareng ku kituna ningkatkeun refcount objék. Saatos ieu, loader tiasa kaluar, sareng objék anu diciptakeun bakal tetep hirup.

BPF keur leutik, bagian hiji: BPF nambahan

Nyiptakeun file dina bpffs anu ngarujuk kana objék BPF disebut "pinning" (sapertos dina frasa ieu: "Prosés tiasa pin program atanapi peta BPF"). Nyiptakeun objék file pikeun objék BPF asup akal henteu ngan ukur pikeun manjangkeun umur objék lokal, tapi ogé pikeun kagunaan objék global - balik deui ka conto sareng program perlindungan DDoS global, urang hoyong tiasa sumping sareng ningali statistik. ti jaman ka jaman.

Sistem file BPF biasana dipasang dina /sys/fs/bpf, tapi ogé tiasa dipasang sacara lokal, contona, sapertos kieu:

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

Ngaran sistem file dijieun maké paréntah BPF_OBJ_PIN Telepon sistem BPF. Pikeun ngagambarkeun, hayu urang nyandak program, kompilasi, unggah, sareng pin ka bpffs. Program kami henteu ngalakukeun nanaon mangpaat, kami ngan ukur nampilkeun kodeu supados anjeun tiasa ngahasilkeun deui conto:

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

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

Hayu urang kompilasi program ieu sareng jieun salinan lokal tina sistem file bpffs:

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

Ayeuna hayu urang unduh program kami nganggo utilitas bpftool jeung kasampak dina telepon sistem nu dibéré bareng bpf(2) (sababaraha garis anu teu relevan dipiceun tina kaluaran 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

Di dieu kami geus dimuat program ngagunakeun BPF_PROG_LOAD, nampi deskriptor file tina kernel 3 sarta ngagunakeun paréntah BPF_OBJ_PIN pinned deskriptor file ieu salaku file "bpf-mountpoint/test". Saatos ieu program bootloader bpftool réngsé ngajalankeun, tapi program urang tetep dina kernel, sanajan urang teu ngagantelkeun kana sagala panganteur jaringan:

$ 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

Urang tiasa ngahapus obyék file sacara normal unlink(2) sareng saatos éta program anu saluyu bakal dipupus:

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

Mupus objék

Diomongkeun ngeunaan mupus objék, perlu pikeun netelakeun yén sanggeus urang dipegatkeun sambungan program ti hook nu (generator acara), teu hiji acara anyar bakal pemicu peluncuran na, kumaha oge, sakabeh instansi ayeuna program bakal réngsé dina urutan normal. .

Sababaraha jenis program BPF ngidinan Anjeun pikeun ngaganti program dina laleur nu, i.e. nyadiakeun atomicity runtuyan replace = detach old program, attach new program. Dina hal ieu, sadaya instansi aktip tina versi heubeul program bakal rengse karya maranéhanana, sarta pawang acara anyar bakal dijieun tina program anyar, sarta "atomicity" didieu hartina teu hiji acara tunggal bakal lasut.

Ngagantelkeun program kana sumber acara

Dina artikel ieu, urang moal misahkeun ngajelaskeun program nyambungkeun kana sumber acara, sabab asup akal pikeun diajar ieu dina konteks tipe husus tina program. Cm. conto di handap, nu urang némbongkeun kumaha program kawas XDP disambungkeun.

Manipulasi Objék Ngagunakeun bpf System Call

program BPF

Sadaya objék BPF didamel sareng dikokolakeun tina rohangan pangguna nganggo telepon sistem bpf, ngabogaan prototipe handap:

#include <linux/bpf.h>

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

Ieu tim cmd mangrupa salah sahiji nilai tina tipe enum bpf_cmd, attr - pointer kana parameter pikeun program husus sarta size - ukuran obyék nurutkeun pointer, i.e. biasana ieu sizeof(*attr). Dina kernel 5.8 nelepon sistem bpf ngarojong 34 Paréntah béda, jeung ngartikeun union bpf_attr nempatan 200 garis. Tapi urang henteu kedah sieun ku ieu, sabab urang bakal familiarizing diri sareng paréntah sareng parameter salami sababaraha tulisan.

Hayu urang mimitian ku tim BPF_PROG_LOAD, anu nyiptakeun program BPF - nyandak sakumpulan paréntah BPF sareng ngamuat kana kernel. Dina momen loading, verifier diluncurkeun, teras kompiler JIT sareng, saatos palaksanaan suksés, deskriptor file program dipulangkeun ka pangguna. Urang nempo naon kajadian ka anjeunna salajengna dina bagian saméméhna ngeunaan daur hirup objék BPF.

Urang ayeuna bakal nyerat program khusus anu bakal ngamuat program BPF saderhana, tapi mimitina urang kedah mutuskeun program naon anu urang hoyong muat - urang kedah milih ngetik sarta dina kerangka tipe ieu, nulis program anu bakal lulus test verifier. Nanging, supados henteu ngahesekeun prosésna, ieu mangrupikeun solusi anu siap-siap: kami bakal nyandak program sapertos kitu BPF_PROG_TYPE_XDP, nu bakal balik nilai XDP_PASS (skip sadayana pakét). Dina assembler BPF katingalina saderhana pisan:

r0 = 2
exit

Sanggeus kami geus mutuskeun dina yen kami bakal unggah, kami tiasa nyarioskeun ka anjeun kumaha kami bakal ngalakukeunana:

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

Kajadian anu pikaresepeun dina program dimimitian ku definisi susunan insns - program BPF kami dina kode mesin. Dina hal ieu, unggal instruksi program BPF dipak kana struktur bpf_insn. Unsur kahiji insns sasuai jeung parentah r0 = 2, kadua - exit.

Mundur. Kernel ngahartikeun makro anu langkung merenah pikeun nyerat kode mesin, sareng nganggo file lulugu kernel tools/include/linux/filter.h urang bisa nulis

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

Tapi saprak nulis program BPF dina kode asli ngan diperlukeun pikeun nulis tés dina kernel jeung artikel ngeunaan BPF, henteuna macros ieu teu bener ngahesekeun kahirupan pamekar urang.

Saatos nangtukeun program BPF, urang ngaléngkah ka loading kana kernel. Set parameter minimalis kami attr ngawengku tipe program, susunan jeung jumlah parentah, lisénsi diperlukeun, jeung ngaran "woo", anu kami anggo pikeun milarian program kami dina sistem saatos diunduh. Program, sakumaha anu dijanjikeun, dimuat kana sistem nganggo telepon sistem bpf.

Dina ahir program urang mungkas nepi dina loop taya nu simulates payload nu. Tanpa éta, program bakal dipaéhan ku kernel nalika deskriptor file anu nelepon sistem balik ka kami ditutup bpf, sarta kami moal ningali eta dina sistem.

Muhun, urang siap pikeun nguji. Hayu urang ngumpul jeung ngajalankeun program dina stracepikeun pariksa yen sagalana jalan sakumaha sakuduna:

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

Sadayana saé, bpf(2) balik cecekelan 3 ka urang jeung urang indit kana hiji loop tanpa wates jeung pause(). Hayu urang cobaan pikeun manggihan program urang dina sistem. Jang ngalampahkeun ieu kami bakal angkat ka terminal anu sanés sareng nganggo utilitas 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)

Urang nempo yén aya program dimuat dina sistem woo nu ID global nyaeta 390 sarta ayeuna lumangsung simple-prog aya deskriptor file kabuka anu nunjuk ka program (sareng upami simple-prog bakal rengse pakasaban, lajeng woo bakal leungit). Saperti nu diharapkeun, program woo nyokot 16 bait - dua parentah - tina kode binér dina arsitektur BPF, tapi dina formulir asli na (x86_64) geus 40 bait. Hayu urang tingali program urang dina bentuk aslina:

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

euweuh kejutan. Ayeuna hayu urang tingali kode anu dihasilkeun ku 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

teu pisan éféktif pikeun exit(2), Tapi dina fairness, program urang teuing basajan, sarta pikeun program non-trivial prolog jeung epilog ditambahkeun ku compiler JIT, tangtosna, diperlukeun.

Maps

Program BPF tiasa nganggo daérah mémori terstruktur anu tiasa diaksés ku program BPF sanés sareng program dina rohangan pangguna. Objék ieu disebut peta sareng dina bagian ieu kami bakal nunjukkeun kumaha ngamanipulasi aranjeunna nganggo telepon sistem bpf.

Hayu urang langsung nyarios yén kamampuan peta henteu dugi ka aksés ka mémori anu dibagikeun. Aya peta husus-tujuan ngandung, contona, pointers kana program BPF atanapi pointers ka interfaces jaringan, peta pikeun gawé bareng acara perf, jsb. Kami moal ngobrol ngeunaan aranjeunna di dieu, supados henteu ngabingungkeun pamaca. Salian ti ieu, urang teu malire masalah sinkronisasi, sabab ieu teu penting pikeun conto urang. Daptar lengkep jinis peta anu sayogi tiasa dipendakan dina <linux/bpf.h>, sarta dina bagian ieu urang bakal nyandak sabagé conto tipe sajarah munggaran, tabel hash BPF_MAP_TYPE_HASH.

Lamun nyieun tabel hash di, nyebutkeun, C ++, anjeun bakal nyebutkeun unordered_map<int,long> woo, nu dina basa Rusia hartina “Abdi peryogi méja woo ukuran taya, nu kenop tipe int, sareng nilaina mangrupikeun jinisna long" Dina raraga nyieun tabel hash BPF, urang kudu ngalakukeun loba hal anu sarua, iwal ti urang kudu nangtukeun ukuran maksimum tabel, sarta tinimbang nangtukeun jenis konci na nilai, urang kudu nangtukeun ukuran maranéhanana dina bait. . Pikeun nyieun peta nganggo paréntah BPF_MAP_CREATE panggero sistem bpf. Hayu urang tingali program anu kirang langkung minimal anu nyiptakeun peta. Saatos program sateuacana anu ngamuat program BPF, ieu sigana saderhana pikeun anjeun:

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

Di dieu urang nangtukeun sakumpulan parameter attr, dimana urang nyarios "Kuring peryogi méja hash kalayan konci sareng nilai ukuran sizeof(int), dimana kuring tiasa nempatkeun maksimal opat unsur." Nalika nyieun peta BPF, anjeun tiasa netepkeun parameter anu sanés, contona, dina cara anu sami sareng dina conto program, kami netepkeun nami obyék salaku "woo".

Hayu urang nyusun sareng ngajalankeun 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(

Ieu sistem panggero bpf(2) dipulangkeun kami nomer peta deskriptor 3 lajeng program, saperti nu diharapkeun, ngantosan parentah salajengna dina panggero sistem pause(2).

Ayeuna hayu urang ngirim program urang ka latar tukang atawa buka terminal sejen tur tingal objék urang ngagunakeun utiliti bpftool (urang bisa ngabedakeun peta urang ti batur ku ngaranna):

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

Angka 114 mangrupikeun ID global objék urang. Program naon waé dina sistem tiasa nganggo ID ieu pikeun muka peta anu tos aya nganggo paréntah BPF_MAP_GET_FD_BY_ID panggero sistem bpf.

Ayeuna urang tiasa maén sareng méja hash urang. Hayu urang tingali eusina:

$ sudo bpftool map dump id 114
Found 0 elements

Kosong. Hayu urang nempatkeun nilai di dinya hash[1] = 1:

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

Hayu urang tingali tabel deui:

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

Horeeee! Urang junun nambahkeun hiji unsur. Catet yén urang kudu dianggo dina tingkat bait pikeun ngalakukeun ieu, saprak bptftool henteu terang naon jinis nilai dina tabel hash. (Kaweruh ieu tiasa ditransfer ka anjeunna nganggo BTF, tapi langkung seueur ngeunaan éta ayeuna.)

Kumaha kahayang bpftool maca sareng nambihan elemen? Hayu urang tingali handapeun tiung:

$ 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

Mimiti urang muka peta ku ID global na nganggo paréntah BPF_MAP_GET_FD_BY_ID и bpf(2) dipulangkeun deskriptor 3 ka urang.. Satuluyna maké paréntah BPF_MAP_GET_NEXT_KEY kami kapanggih konci munggaran dina tabél ku lulus NULL salaku pointer kana konci "saméméhna". Lamun urang boga konci urang bisa ngalakukeun BPF_MAP_LOOKUP_ELEMnu mulih nilai ka pointer a value. Lengkah saterusna nyaeta urang cobaan pikeun manggihan unsur salajengna ku ngalirkeun pointer ka konci ayeuna, tapi tabel urang ngan ngandung hiji unsur jeung paréntah. BPF_MAP_GET_NEXT_KEY mulih ENOENT.

Oké, hayu urang ngarobah nilai ku konci 1, hayu urang nyebutkeun logika bisnis urang merlukeun ngadaptar 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

Saperti nu diharapkeun, éta basajan pisan: paréntah BPF_MAP_GET_FD_BY_ID muka peta urang ku ID, sarta paréntah BPF_MAP_UPDATE_ELEM nimpa unsur.

Janten, saatos ngadamel tabel hash tina hiji program, urang tiasa maca sareng nyerat eusina tina program anu sanés. Catet yén upami urang tiasa ngalakukeun ieu tina garis paréntah, maka program anu sanés dina sistem tiasa ngalakukeun éta. Salian paréntah anu dijelaskeun di luhur, pikeun damel sareng peta tina rohangan pangguna, следующие:

  • BPF_MAP_LOOKUP_ELEM: manggihan nilai ku konci
  • BPF_MAP_UPDATE_ELEM: update / nyieun nilai
  • BPF_MAP_DELETE_ELEM: miceun konci
  • BPF_MAP_GET_NEXT_KEY: manggihan salajengna (atawa kahiji) konci
  • BPF_MAP_GET_NEXT_ID: ngidinan Anjeun pikeun ngaliwatan sagala peta aya, éta kumaha gawéna bpftool map
  • BPF_MAP_GET_FD_BY_ID: muka peta nu aya ku ID global na
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: sacara atom ngamutahirkeun nilai hiji obyék sarta balikkeun nu heubeul
  • BPF_MAP_FREEZE: Jieun peta immutable tina userspace (operasi ieu teu bisa dibolaykeun)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: operasi masal. Salaku conto, BPF_MAP_LOOKUP_AND_DELETE_BATCH - ieu mangrupikeun hiji-hijina cara anu tiasa dipercaya pikeun maca sareng ngareset sadaya nilai tina peta

Henteu sakabéh paréntah ieu dianggo pikeun sakabéh jenis peta, tapi sacara umum gawé bareng tipe séjén peta ti spasi pamaké Sigana persis sarua jeung gawé bareng tabel Hash.

Demi pesenan, hayu urang réngsé ékspérimén tabel hash. Inget yen urang dijieun tabel nu bisa ngandung nepi ka opat kenop? Hayu urang tambahkeun sababaraha elemen deui:

$ 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

Dugi ayeuna mah saé:

$ 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

Hayu urang coba tambahkeun hiji deui:

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

Sapertos anu dipiharep, urang henteu hasil. Hayu urang nempo kasalahan dina leuwih jéntré:

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

Sagalana oke: saperti nu diharapkeun, tim BPF_MAP_UPDATE_ELEM nyoba nyieun anyar, kalima, konci, tapi ngadat E2BIG.

Janten, urang tiasa nyiptakeun sareng ngamuat program BPF, ogé nyiptakeun sareng ngatur peta tina rohangan pangguna. Ayeuna éta logis ningali kumaha urang tiasa nganggo peta tina program BPF sorangan. Urang bisa ngobrol ngeunaan ieu dina basa program teuas-to-baca dina kode makro mesin, tapi dina kanyataanana geus waktuna pikeun némbongkeun kumaha program BPF sabenerna ditulis tur dijaga - ngagunakeun. libbpf.

(Pikeun pamiarsa anu henteu sugema ku kurangna conto tingkat handap: urang bakal nganalisis sacara rinci program anu nganggo peta sareng fungsi pembantu anu diciptakeun nganggo libbpf sarta ngabejaan Anjeun naon kajadian di tingkat instruksi. Pikeun pamiarsa anu sugema pisan, urang ditambahkeun conto dina tempat anu luyu dina artikel.)

Nulis program BPF ngagunakeun libbpf

Nulis program BPF ngagunakeun kode mesin tiasa metot ngan kahiji waktos, lajeng set satiety di. Dina momen ieu anjeun kudu ngahurungkeun perhatian Anjeun llvm, nu boga backend pikeun kode generating pikeun arsitektur BPF, kitu ogé perpustakaan libbpf, anu ngamungkinkeun anjeun nyerat sisi pangguna aplikasi BPF sareng ngamuat kode program BPF anu didamel nganggo llvm/clang.

Nyatana, sakumaha anu bakal urang tingali dina ieu sareng tulisan saterasna, libbpf ngalakukeun seueur padamelan tanpa éta (atanapi alat anu sami - iproute2, libbcc, libbpf-go, jeung sajabana) teu mungkin hirup. Salah sahiji fitur killer proyék libbpf nyaéta BPF CO-RE (Compile Once, Run Everywhere) - proyék anu ngamungkinkeun anjeun nyerat program BPF anu portabel tina hiji kernel ka anu sanés, kalayan kamampuan ngajalankeun dina API anu béda (contona, nalika struktur kernel robih tina versi. kana versi). Pikeun tiasa damel sareng CO-RE, kernel anjeun kedah disusun sareng dukungan BTF (urang ngajelaskeun kumaha ngalakukeun ieu dina bagian Pakakas Pangwangunan. Anjeun tiasa pariksa naha kernel anjeun diwangun ku BTF atanapi henteu saderhana - ku ayana file ieu:

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

Berkas ieu nyimpen inpormasi ngeunaan sadaya jinis data anu dianggo dina kernel sareng dianggo dina sadaya conto kami nganggo libbpf. Urang bakal ngobrol di jéntré ngeunaan CO-RE dina artikel salajengna, tapi dina hiji ieu - ngan ngawangun diri kernel a CONFIG_DEBUG_INFO_BTF.

taman pustaka libbpf hirup katuhu dina diréktori tools/lib/bpf kernel sarta pangwangunanana dilaksanakeun ngaliwatan milis [email protected]. Sanajan kitu, gudang misah dijaga pikeun kaperluan aplikasi hirup di luar kernel https://github.com/libbpf/libbpf nu perpustakaan kernel ieu mirrored pikeun aksés dibaca leuwih atawa kurang sakumaha anu kasebut.

Dina bagian ieu kami bakal ningali kumaha anjeun tiasa nyiptakeun proyék anu nganggo libbpf, hayu urang nulis sababaraha (leuwih atawa kurang hartina) program test jeung nganalisis di jéntré kumaha eta sadayana jalan. Ieu bakal ngidinan urang pikeun leuwih gampang ngajelaskeun dina bagian handap persis kumaha program BPF berinteraksi sareng peta, helpers kernel, BTF, jsb.

Biasana proyék ngagunakeun libbpf tambahkeun Repository GitHub salaku submodule git, kami bakal ngalakukeun anu sami:

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

Badé ka libbpf basajan pisan:

$ 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 kami salajengna dina bagian ieu nyaéta kieu: urang bakal nulis program BPF kawas BPF_PROG_TYPE_XDP, sarua jeung dina conto saméméhna, tapi dina C, urang compile eta ngagunakeun clang, sareng nyerat program pembantu anu bakal ngamuat kana kernel. Dina bagian di handap ieu kami bakal ngalegaan kamampuan program BPF sareng program asisten.

Conto: nyieun aplikasi pinuh ngagunakeun libbpf

Pikeun mimitian, urang nganggo file /sys/kernel/btf/vmlinux, anu disebatkeun di luhur, sareng jieun sarimbagna dina bentuk file lulugu:

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

Berkas ieu bakal nyimpen sadaya struktur data anu aya dina kernel urang, contona, ieu kumaha header IPv4 didefinisikeun dina 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;
};

Ayeuna kami bakal nyerat program BPF kami dina 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";

Sanaos program kami tétéla saderhana pisan, kami tetep kedah nengetan seueur detil. Kahiji, file lulugu munggaran urang kaasup vmlinux.h, nu urang ngan dihasilkeun maké bpftool btf dump - ayeuna urang henteu kedah masang paket kernel-headers pikeun milarian kumaha struktur kernel. File lulugu di handap datang ka kami ti perpustakaan libbpf. Ayeuna urang ngan ukur peryogi pikeun ngartikeun makro SEC, nu ngirimkeun karakter ka bagian luyu tina file obyék ELF. Program kami dikandung dina bagian xdp/simple, Dimana saméméh slash urang nangtukeun jenis program BPF - ieu téh konvénsi dipaké dina libbpf, dumasar kana ngaran bagian eta bakal ngagantikeun tipe bener dina ngamimitian bpf(2). Program BPF sorangan C - pisan basajan tur diwangun ku hiji garis return XDP_PASS. Tungtungna, bagian misah "license" ngandung ngaran lisénsi.

Urang tiasa nyusun program urang nganggo llvm/clang, versi>= 10.0.0, atanapi langkung saé, langkung ageung (tingali bagian Pakakas Pangwangunan):

$ 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

Diantara fitur metot: urang nunjukkeun arsitektur target -target bpf jeung jalur ka headers libbpf, anu nembe kami pasang. Ogé, ulah poho ngeunaan -O2, Tanpa pilihan ieu anjeun bisa jadi dina keur kejutan dina mangsa nu bakal datang. Hayu urang tingali kode urang, naha urang junun nyerat program anu dipikahoyong?

$ 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

Sumuhun, éta digawé! Ayeuna, urang gaduh file binér sareng program éta, sareng urang badé nyiptakeun aplikasi anu bakal ngamuat kana kernel. Pikeun tujuan ieu perpustakaan libbpf nawiskeun kami dua pilihan - nganggo API tingkat handap atanapi API tingkat luhur. Urang bakal balik cara kadua, sabab urang hayang diajar nulis, ngamuat tur sambungkeun program BPF kalawan usaha minimal keur ulikan saterusna maranéhanana.

Mimiti, urang kedah ngahasilkeun "rangka" program urang tina binér na nganggo utilitas anu sami bpftool - péso Swiss dunya BPF (anu tiasa sacara harfiah, sabab Daniel Borkman, salah sahiji panyipta sareng pangropéa BPF, nyaéta Swiss):

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

Dina file xdp-simple.skel.h ngandung kode binér program urang jeung fungsi pikeun ngatur - loading, ngagantelkeun, mupus objék urang. Dina kasus basajan urang ieu Sigana mah overkill, tapi ogé jalan dina kasus dimana file obyék ngandung loba program BPF jeung peta sarta pikeun ngamuat ELF raksasa ieu urang ngan perlu ngahasilkeun rorongkong jeung nelepon hiji atawa dua fungsi tina aplikasi custom kami. nu nulis Hayu urang ngaléngkah ayeuna.

Tegesna, program loader kami teu 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);
}

Ieu téh struct xdp_simple_bpf didefinisikeun dina file xdp-simple.skel.h sareng ngajelaskeun file obyék kami:

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

Urang tiasa ningali jejak API tingkat rendah di dieu: strukturna struct bpf_program *simple и struct bpf_link *simple. Struktur kahiji husus ngajelaskeun program urang, ditulis dina bagian xdp/simple, sarta kadua ngajelaskeun kumaha program nyambung ka sumber acara.

fungsi xdp_simple_bpf__open_and_load, muka hiji objek ELF, parses eta, nyieun sakabéh struktur jeung substructures (salain program, ELF ogé ngandung bagian séjén - data, readonly data, debugging informasi, lisénsi, jsb), lajeng muka kana kernel ngagunakeun sistem. nelepon bpf, anu tiasa urang parios ku nyusun sareng ngajalankeun 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

Hayu urang ayeuna ningali program urang ngagunakeun bpftool. Hayu urang milarian ID na:

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

sareng dump (urang nganggo bentuk paréntah anu disingkat bpftool prog dump xlated):

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

Aya nu anyar! Program dicitak sakumpulan file sumber C kami. Hal ieu dilakukeun ku perpustakaan libbpf, nu manggihan bagian debug dina binér, disusun kana objék BTF, dimuat kana kernel ngagunakeun BPF_BTF_LOAD, teras netepkeun deskriptor file anu dihasilkeun nalika ngamuat program nganggo paréntah BPG_PROG_LOAD.

Kernel Helpers

Program BPF tiasa ngajalankeun fungsi "éksternal" - pembantu kernel. Fungsi helper ieu ngamungkinkeun program BPF ngaksés struktur kernel, ngatur peta, sareng ogé komunikasi sareng "dunya nyata" - nyiptakeun acara perf, ngontrol hardware (contona, alihan pakét), jsb.

Conto: bpf_get_smp_processor_id

Dina kerangka paradigma "diajar ku conto", hayu urang nganggap salah sahiji fungsi pembantu, bpf_get_smp_processor_id(), tangtu dina file kernel/bpf/helpers.c. Ngabalikeun jumlah prosésor anu dijalankeun ku program BPF anu disebutna. Tapi urang teu jadi kabetot dina semantik na sakumaha dina kanyataan yén palaksanaan na nyokot hiji baris:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

Definisi fungsi helper BPF sami sareng definisi panggero sistem Linux. Di dieu, contona, hiji fungsi diartikeun nu teu boga argumen. (A fungsi nu nyokot, sebutkeun, tilu argumen diartikeun maké macro BPF_CALL_3. Jumlah maksimum argumen lima.) Sanajan kitu, ieu téh ngan bagian kahiji tina harti. Bagian kadua pikeun nangtukeun jenis struktur struct bpf_func_proto, nu ngandung pedaran fungsi helper nu verifier understands:

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

Ngadaptarkeun Fungsi Helper

Supados program BPF tina tipe nu tangtu ngagunakeun fungsi ieu, aranjeunna kedah ngadaptar eta, contona pikeun jenis BPF_PROG_TYPE_XDP hiji fungsi diartikeun dina kernel xdp_func_proto, nu nangtukeun tina fungsi helper ID naha XDP ngarojong fungsi ieu atanapi henteu. fungsi urang téh ngadukung:

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

Jenis program BPF anyar "ditetepkeun" dina file include/linux/bpf_types.h ngagunakeun makro BPF_PROG_TYPE. Ditetepkeun dina tanda petik sabab mangrupa harti logis, sarta dina istilah basa C harti sakabeh susunan struktur beton lumangsung di tempat séjén. Khususna, dina file kernel/bpf/verifier.c sadaya definisi tina file bpf_types.h dipaké pikeun nyieun susunan 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
};

Hartina, pikeun tiap jenis program BPF, hiji pointer kana struktur data tina tipena ditetepkeun struct bpf_verifier_ops, nu ieu initialized kalawan nilai _name ## _verifier_ops, nyaéta, xdp_verifier_ops keur xdp. Struktur xdp_verifier_ops ditangtukeun dina file net/core/filter.c saperti kieu:

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

Di dieu urang ningali fungsi akrab urang xdp_func_proto, nu bakal ngajalankeun verifier unggal waktos eta encounters tangtangan sababaraha fungsi jero program BPF, tingali verifier.c.

Hayu urang tingali kumaha program BPF hypothetical ngagunakeun fungsi bpf_get_smp_processor_id. Jang ngalampahkeun ieu, urang nulis ulang program ti bagian saméméhna urang saperti kieu:

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

simbul bpf_get_smp_processor_id ditangtukeun в <bpf/bpf_helper_defs.h> perpustakaan libbpf kumaha

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

nyaeta, bpf_get_smp_processor_id mangrupakeun pointer fungsi anu nilaina 8, dimana 8 mangrupa nilai BPF_FUNC_get_smp_processor_id ngetik enum bpf_fun_id, anu ditetepkeun pikeun urang dina file vmlinux.h (file bpf_helper_defs.h dina kernel dihasilkeun ku naskah, jadi angka "magic" ok). Fungsi ieu henteu nyandak argumen sareng ngabalikeun nilai jinis __u32. Nalika urang ngajalankeun éta dina program urang, clang ngahasilkeun instruksi BPF_CALL "jenis anu bener" Hayu urang nyusun program sareng ningali bagian éta 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

Dina baris kahiji urang ningali parentah call, parameter IMM nu sarua jeung 8, jeung SRC_REG - nol. Numutkeun perjanjian ABI anu dianggo ku verifier, ieu mangrupikeun panggero pikeun fungsi pembantu nomer dalapan. Sakali diluncurkeun, logikana saderhana. Ngabalikeun nilai tina register r0 disalin ka r1 sarta dina garis 2,3 eta dirobah jadi tipe u32 - luhur 32 bit diberesihan. Dina garis 4,5,6,7 urang balikkeun 2 (XDP_PASS) atawa 1 (XDP_DROP) gumantung kana naha fungsi helper tina garis 0 balik hiji nilai enol atawa non-enol.

Hayu urang nguji diri: muatkeun program sareng tingali kaluaran 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 mendakan kernel-helper anu leres.

Conto: ngalirkeun argumen sareng tungtungna ngajalankeun program!

Sadaya fungsi helper tingkat run gaduh prototipe

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

Parameter pikeun fungsi helper diliwatan dina registers r1-r5, sarta nilaina dipulangkeun dina register r0. Henteu aya fungsi anu nyandak langkung ti lima argumen, sareng dukungan pikeun aranjeunna henteu dipiharep bakal ditambah dina waktos anu bakal datang.

Hayu urang tingali dina helper kernel anyar sareng kumaha BPF ngalangkungan parameter. Hayu urang nulis deui xdp-simple.bpf.c kieu (sésana garis teu robah):

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

Program kami nyitak jumlah CPU anu dijalankeun. Hayu urang kompilasi sareng tingali kodeu:

$ 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

Dina garis 0-7 urang nulis string running on CPU%un, lajeng dina garis 8 urang ngajalankeun hiji akrab bpf_get_smp_processor_id. Dina baris 9-12 urang nyiapkeun argumen helper bpf_printk - ngadaptar r1, r2, r3. Naha aya tilu sareng henteu dua? Sabab bpf_printkieu bungkus makro sabudeureun nulungan nyata bpf_trace_printk, nu kudu lulus ukuran string format.

Ayeuna hayu urang tambahkeun sababaraha garis xdp-simple.cku kituna program urang nyambung ka panganteur lo tur bener dimimitian!

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

Di dieu urang ngagunakeun fungsi bpf_set_link_xdp_fd, nu nyambungkeun XDP-tipe program BPF ka interfaces jaringan. Urang hardcoded angka panganteur lo, nu salawasna 1. Urang ngajalankeun fungsi dua kali pikeun kahiji coplokkeun program heubeul lamun ieu napel. Perhatikeun yén ayeuna urang henteu peryogi tangtangan pause atanapi loop anu teu aya watesna: program loader kami bakal kaluar, tapi program BPF moal tiwas sabab disambungkeun ka sumber acara. Saatos undeuran sareng sambungan anu suksés, program bakal diluncurkeun pikeun unggal pakét jaringan anu sumping lo.

Hayu urang ngundeur program jeung kasampak di interface 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 anu kami unduh ngagaduhan ID 669 sareng urang ningali ID anu sami dina antarmuka lo. Kami bakal ngirim sababaraha bungkusan ka 127.0.0.1 (paménta + balesan):

$ ping -c1 localhost

sareng ayeuna hayu urang tingali eusi file virtual debug /sys/kernel/debug/tracing/trace_pipe, di mana bpf_printk nyerat pesenna:

# 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

Dua bungkusan kasawang lo sareng diolah dina CPU0 - program BPF anu teu aya artina pinuh munggaran kami damel!

Peryogi diperhatoskeun éta bpf_printk Henteu kanggo nanaon yén éta nyerat kana file debug: ieu sanés pembantu anu paling suksés pikeun dianggo dina produksi, tapi tujuan kami nyaéta pikeun nunjukkeun anu saderhana.

Ngaksés peta tina program BPF

Conto: ngagunakeun peta tina program BPF

Dina bagian saméméhna urang diajar kumaha carana nyieun sarta ngagunakeun peta ti spasi pamaké, sarta ayeuna hayu urang nempo bagian kernel. Hayu urang mimitian, sakumaha biasa, ku conto. Hayu urang nulis ulang program urang xdp-simple.bpf.c saperti kieu:

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

Dina awal program urang ditambahkeun harti peta woo: Ieu susunan 8-unsur nu nyimpen nilai kawas u64 (dina C urang bakal nangtukeun Asép Sunandar Sunarya saperti u64 woo[8]). Dina hiji program "xdp/simple" urang meunang jumlah processor ayeuna kana variabel key lajeng ngagunakeun fungsi helper bpf_map_lookup_element urang meunang pointer ka éntri pakait dina Asép Sunandar Sunarya, nu urang ningkatkeun ku hiji. Ditarjamahkeun kana Rusia: urang ngitung statistik nu CPU ngolah pakét asup. Hayu urang coba ngajalankeun 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

Hayu urang pariksa yen manehna geus hooked nepi ka lo sareng kirimkeun sababaraha pakét:

$ 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

Ayeuna hayu urang nempo eusi Asép Sunandar Sunarya:

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

Ampir kabéh prosés diolah dina CPU7. Ieu henteu penting pikeun kami, anu utama nyaéta program éta jalan sareng urang ngartos kumaha ngaksés peta tina program BPF - nganggo хелперов bpf_mp_*.

Indéks mistis

Janten, urang tiasa ngaksés peta tina program BPF nganggo telepon sapertos

val = bpf_map_lookup_elem(&woo, &key);

dimana fungsi helper Sigana mah

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

tapi urang ngaliwat pointer &woo kana struktur anu henteu namina struct { ... }...

Lamun urang tingali dina assembler program, urang tingali yen nilai &woo teu sabenerna didefinisikeun (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
...

sarta dikandung dina 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

Tapi upami urang ningali program anu parantos dimuat, urang ningali penunjuk kana peta anu leres (garis 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]
...

Ku kituna, urang bisa nyimpulkeun yén dina waktu launching program loader urang, link ka &woo ieu diganti ku hal kalawan perpustakaan libbpf. Kahiji urang bakal nempo kaluaran 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

Urang tingali éta libbpf dijieun peta woo teras unduh program kami simple. Hayu urang tingali kumaha urang ngamuat program:

  • nelepon xdp_simple_bpf__open_and_load tina file xdp-simple.skel.h
  • nu ngabalukarkeun xdp_simple_bpf__load tina file xdp-simple.skel.h
  • nu ngabalukarkeun bpf_object__load_skeleton tina file libbpf/src/libbpf.c
  • nu ngabalukarkeun bpf_object__load_xattr ti libbpf/src/libbpf.c

Fungsi panungtungan, antara séjén, bakal nelepon bpf_object__create_maps, nu nyieun atawa muka peta nu geus aya, ngarobahna jadi deskriptor file. (Ieu dimana urang ningali BPF_MAP_CREATE dina kaluaran strace.) Salajengna fungsi disebut bpf_object__relocate Sareng anjeunna anu dipikaresep ku urang, sabab urang émut naon anu urang tingali woo dina tabel relokasi. Ngajalajah éta, antukna urang mendakan diri dina fungsina bpf_program__relocate, anu nguruskeun relokasi peta:

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

Ku kituna urang nyandak parentah kami

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

tur ngaganti sumber register dina eta kalawan BPF_PSEUDO_MAP_FD, sareng IMM munggaran kana file deskriptor peta kami sareng, upami sami sareng, contona, 0xdeadbeef, lajeng salaku hasilna urang bakal nampa instruksi

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

Ieu kumaha informasi peta ditransfer ka program BPF dimuat husus. Dina hal ieu, peta bisa dijieun maké BPF_MAP_CREATE, tur dibuka ku ID maké BPF_MAP_GET_FD_BY_ID.

Total, nalika ngagunakeun libbpf algoritma nyaéta kieu:

  • salila kompilasi, rékaman dijieun dina tabel relokasi pikeun tumbu ka peta
  • libbpf muka buku objék ELF, manggihan sagala peta dipaké sarta nyieun deskriptor file pikeun aranjeunna
  • deskriptor file dimuat kana kernel salaku bagian tina instruksi LD64

Sakumaha anjeun tiasa bayangkeun, aya deui anu bakal datang sareng urang kedah ningali kana inti. Untungna, urang boga clue - kami geus ditulis handap hartina BPF_PSEUDO_MAP_FD kana register sumber jeung urang bisa ngubur eta, nu bakal ngakibatkeun urang ka suci sadaya wali - kernel/bpf/verifier.c, dimana hiji fungsi kalawan ngaran has ngagantikeun file descriptor kalawan alamat struktur tipe 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 lengkep tiasa dipendakan link). Janten urang tiasa ngalegaan algoritma kami:

  • bari ngamuat program, verifier pariksa pamakéan bener tina peta tur nulis alamat struktur pakait struct bpf_map

Nalika ngaunduh binér ELF nganggo libbpf Aya seueur deui anu lumangsung, tapi urang bakal ngabahas éta dina tulisan anu sanés.

Ngamuat program sareng peta tanpa libbpf

Sakumaha anu dijanjikeun, ieu mangrupikeun conto pikeun pamiarsa anu hoyong terang kumaha cara nyiptakeun sareng ngamuat program anu nganggo peta, tanpa bantosan. libbpf. Ieu tiasa mangpaat nalika anjeun damel di lingkungan anu anjeun teu tiasa ngawangun katergantungan, atanapi nyimpen unggal bit, atanapi nyerat program sapertos ply, nu dibangkitkeun BPF kode binér on laleur nu.

Pikeun ngagampangkeun nuturkeun logika, urang bakal nyerat deui conto urang pikeun tujuan ieu xdp-simple. Kode lengkep sareng rada dimekarkeun tina program anu dibahas dina conto ieu tiasa dipendakan dina ieu saréat.

Logika aplikasi kami nyaéta kieu:

  • nyieun peta tipe BPF_MAP_TYPE_ARRAY ngagunakeun paréntah BPF_MAP_CREATE,
  • nyieun program anu ngagunakeun peta ieu,
  • sambungkeun program ka panganteur lo,

nu ditarjamahkeun kana manusa salaku

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

Ieu téh map_create nyiptakeun peta dina cara nu sarua salaku urang teu di conto munggaran ngeunaan panggero sistem bpf - "kernel, punten ngadamel abdi peta anyar dina wangun susunan 8 elemen kawas __u64 sareng pasihan abdi deui 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));
}

Programna ogé 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));
}

Bagian tricky prog_load nyaeta harti program BPF urang salaku Asép Sunandar Sunarya ti struktur struct bpf_insn insns[]. Tapi kumargi urang nganggo program anu aya dina C, urang tiasa curang sakedik:

$ 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

Dina total, urang kudu nulis 14 parentah dina bentuk struktur kawas struct bpf_insn (naséhat: nyandak dump ti luhur, ulang baca bagian parentah, muka linux/bpf.h и linux/bpf_common.h jeung nyoba nangtukeun struct bpf_insn insns[] sorangan):

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

Hiji latihan pikeun maranéhanana anu teu nulis ieu sorangan - manggihan map_fd.

Aya hiji deui bagian anu teu diungkabkeun dina program kami - xdp_attach. Hanjakalna, program sapertos XDP teu tiasa disambungkeun nganggo telepon sistem bpf. Jalma anu nyiptakeun BPF sareng XDP asalna tina komunitas Linux online, anu hartosna aranjeunna ngagunakeun anu paling akrab pikeun aranjeunna (tapi henteu biasa jalma) antarmuka pikeun berinteraksi sareng kernel: sockets netlink, tingali ogé RFC3549. Cara pangbasajanna pikeun nerapkeun xdp_attach nyalin kode tina libbpf, nyaéta, tina file netlink.c, anu kami lakukeun, pondokkeun sakedik:

Wilujeng sumping di dunya sockets netlink

Buka tipe stop kontak 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;
}

Urang maca tina stop kontak ieu:

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

Tungtungna, ieu mangrupikeun fungsi kami anu muka stop kontak sareng ngirim pesen khusus ka dinya anu ngandung 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;
}

Janten, sadayana siap pikeun diuji:

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

Hayu urang tingali upami program urang parantos nyambung ka 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

Hayu urang kirimkeun ping sareng tingali 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, sagalana jalan. Catet, ku jalan kitu, peta urang dipintonkeun deui dina bentuk bait. Ieu alatan kanyataan yén, teu saperti libbpf kami henteu ngamuat inpormasi jinis (BTF). Tapi urang bakal ngobrol langkung seueur ngeunaan ieu waktos salajengna.

Pakakas Pangwangunan

Dina bagian ieu, urang bakal ningali toolkit pamekar BPF minimum.

Sacara umum, anjeun henteu peryogi nanaon khusus pikeun ngembangkeun program BPF - BPF dijalankeun dina kernel distribusi anu santun, sareng program diwangun nganggo clang, anu tiasa disayogikeun tina bungkusan. Nanging, kusabab kanyataan yén BPF nuju dikembangkeun, kernel sareng alat-alat terus robih, upami anjeun henteu hoyong nyerat program BPF nganggo metode kuno ti 2019, maka anjeun kedah nyusun.

  • llvm/clang
  • pahole
  • inti na
  • bpftool

(Pikeun rujukan, bagian ieu sareng sadaya conto dina tulisan dijalankeun dina Debian 10.)

llvm/clang

BPF ramah sareng LLVM sareng, sanaos ayeuna program pikeun BPF tiasa disusun nganggo gcc, sadaya pamekaran ayeuna dilaksanakeun pikeun LLVM. Ku alatan éta, mimiti sagala, urang bakal ngawangun versi ayeuna clang ti 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
... много времени спустя
$

Ayeuna urang tiasa pariksa naha sadayana parantos leres:

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

(Parentah Majelis clang dicandak ku kuring ti bpf_devel_QA.)

Kami moal masang program anu karek diwangun, tapi ngan ukur nambihanana PATHContona:

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

(Ieu bisa ditambahkeun kana .bashrc atawa ka file misah. Pribadi, kuring nambihan hal sapertos kieu ~/bin/activate-llvm.sh jeung lamun perlu kuring ngalakukeun eta . activate-llvm.sh.)

Pahole jeung BTF

Utiliti pahole dipaké nalika ngawangun kernel pikeun nyieun informasi debugging dina format BTF. Kami moal ngajentrekeun dina tulisan ieu ngeunaan detil téknologi BTF, lian ti kanyataan yén éta merenah sareng urang hoyong dianggo. Janten upami anjeun badé ngawangun kernel anjeun, ngawangun heula pahole (tanpa pahole anjeun moal tiasa ngawangun kernel kalayan 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 pikeun ékspérimén sareng BPF

Nalika Ngajalajah kemungkinan BPF, Abdi hoyong ngumpul inti kuring sorangan. Ieu, sacara umum, henteu diperyogikeun, sabab anjeun bakal tiasa nyusun sareng ngamuat program BPF dina kernel distribusi, kumaha oge, gaduh kernel anjeun nyalira ngamungkinkeun anjeun ngagunakeun fitur BPF panganyarna, anu bakal muncul dina distribusi anjeun dina sababaraha bulan pangsaéna , atawa, sakumaha dina kasus sababaraha parabot debugging moal rangkep pisan dina mangsa nu bakal datang foreseeable. Ogé, inti sorangan ngajadikeun eta ngarasa penting pikeun ékspérimén kalawan kode.

Pikeun ngawangun kernel anjeun peryogi, kahiji, kernel sorangan, sareng kadua, file konfigurasi kernel. Pikeun ékspérimén sareng BPF urang tiasa nganggo anu biasa vanili kernel atawa salah sahiji kernels ngembangkeun. Dina sajarahna, pamekaran BPF lumangsung dina komunitas jaringan Linux sareng ku kituna sadaya parobihan gancang atanapi engké ngalangkungan David Miller, pangurus jaringan Linux. Gumantung kana sifatna - éditan atanapi fitur anyar - parobahan jaringan digolongkeun kana salah sahiji dua inti - net atawa net-next. Parobahan pikeun BPF disebarkeun dina cara nu sarua antara bpf и bpf-next, nu lajeng pooled kana net jeung net-hareup, masing-masing. Pikeun leuwih rinci, tingali bpf_devel_QA и netdev-FAQ. Janten pilih kernel dumasar kana rasa anjeun sareng kabutuhan stabilitas sistem anu anjeun uji (*-next kernels anu paling teu stabil tina anu didaptarkeun).

Éta saluareun ruang lingkup tulisan ieu pikeun ngobrol ngeunaan kumaha carana ngatur file konfigurasi kernel - dianggap yén anjeun parantos terang kumaha ngalakukeun ieu, atanapi siap diajar sorangan. Tapi, parentah di handap ieu kudu leuwih atawa kurang cukup pikeun masihan anjeun sistem BPF-diaktipkeun.

Unduh salah sahiji kernels di luhur:

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

Ngawangun konfigurasi kernel anu tiasa dianggo minimal:

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

Aktipkeun pilihan BPF dina file .config pilihan anjeun sorangan (paling dipikaresep CONFIG_BPF parantos diaktipkeun saprak systemd ngagunakeunana). Ieu daptar pilihan tina kernel anu dianggo pikeun tulisan ieu:

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

Teras urang tiasa gampang ngumpul sareng masang modul sareng kernel (ku jalan kitu, anjeun tiasa ngumpul kernel nganggo anu nembé dirakit. clangku nambahkeun CC=clang):

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

sareng reboot sareng kernel énggal (kuring dianggo pikeun ieu kexec ti iket 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

Utiliti anu paling sering dianggo dina tulisan bakal janten utilitas bpftool, disayogikeun salaku bagian tina kernel Linux. Ditulis sareng dijaga ku pamekar BPF pikeun pamekar BPF sareng tiasa dianggo pikeun ngatur sadaya jinis objék BPF - ngamuat program, nyiptakeun sareng ngédit peta, ngajalajah kahirupan ekosistem BPF, jsb. Dokuméntasi dina bentuk kode sumber pikeun halaman manual tiasa dipendakan dina inti atawa, geus disusun, dina bersih.

Dina waktu tulisan ieu bpftool siap-siap ngan ukur pikeun RHEL, Fedora sareng Ubuntu (tingali, contona, thread ieu, nu ngabejaan carita tacan beres bungkusan bpftool dina Debian). Tapi upami anjeun parantos ngawangun kernel anjeun, teras ngawangun bpftool sagampang pai:

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

$

(Ieuh ${linux} - ieu téh diréktori kernel Anjeun.) Saatos executing paréntah ieu bpftool bakal dikumpulkeun dina diréktori ${linux}/tools/bpf/bpftool tur éta bisa ditambahkeun kana jalur (mimiti ka pamaké root) atawa ngan nyalin kana /usr/local/sbin.

Ngumpulkeun bpftool Hadé pisan mun éta ngagunakeun dimungkinkeun clang, dirakit sakumaha ditétélakeun di luhur, jeung pariksa naha éta geus dirakit neuleu - ngagunakeun, contona, paréntah

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

anu bakal nunjukkeun fitur BPF mana anu diaktipkeun dina kernel anjeun.

Ku jalan kitu, paréntah saméméhna bisa dijalankeun salaku

# bpftool f p k

Hal ieu dilakukeun ku analogi sareng utilitas tina pakét iproute2, dimana urang tiasa, contona, sebutkeun ip a s eth0 tibatan ip addr show dev eth0.

kacindekan

BPF ngidinan Anjeun pikeun sapatu kutu pikeun éféktif ngukur na on-the-fly ngarobah fungsionalitas inti. Sistem tétéla suksés pisan, dina tradisi pangalusna UNIX: mékanisme basajan nu ngidinan Anjeun pikeun (deui) program kernel diwenangkeun sajumlah badag jalma jeung organisasi pikeun ékspérimén. Sareng, sanaos ékspérimén, ogé pamekaran infrastruktur BPF nyalira, teu acan réngsé, sistemna parantos gaduh ABI anu stabil anu ngamungkinkeun anjeun ngawangun logika bisnis anu dipercaya, sareng anu paling penting.

Abdi hoyong dicatet yén, dina pamanggih kuring, téhnologi nu geus jadi jadi populér sabab, di hiji sisi, bisa играть (arsitektur mesin bisa ngarti leuwih atawa kurang dina hiji sore), sarta di sisi séjén, pikeun ngajawab masalah anu teu bisa direngsekeun (geulis) saméméh penampilan na. Dua komponén ieu babarengan maksakeun jalma pikeun ékspérimén sareng impian, anu nyababkeun munculna solusi anu langkung inovatif.

Artikel ieu, sanajan teu utamana pondok, ngan hiji bubuka ka dunya BPF sarta henteu ngajelaskeun fitur "maju" jeung bagian penting arsitektur. Rencana anu bakal maju sapertos kieu: artikel salajengna bakal tinjauan jinis program BPF (aya 5.8 jinis program anu dirojong dina kernel 30), teras urang tungtungna ningali kumaha cara nyerat aplikasi BPF nyata nganggo program ngalacak kernel. salaku conto, lajeng éta waktu pikeun kursus leuwih teleb ngeunaan arsitektur BPF, dituturkeun ku conto BPF jaringan sarta aplikasi kaamanan.

Artikel saméméhna dina séri ieu

  1. BPF keur leutik, bagian enol: BPF Palasik

Tumbu

  1. BPF jeung XDP Rujukan Guide - dokuméntasi on BPF ti cilium, atawa leuwih tepat ti Daniel Borkman, salah sahiji panyipta sarta maintainers of BPF. Ieu mangrupikeun salah sahiji pedaran serius anu munggaran, anu béda ti anu sanés yén Daniel terang persis naon anu anjeunna nyerat sareng teu aya kasalahan. Khususna, dokumén ieu ngajelaskeun kumaha cara damel sareng program BPF tina jinis XDP sareng TC nganggo utilitas anu terkenal. ip ti iket iproute2.

  2. Dokuméntasi/jaringan/filter.txt - file asli sareng dokuméntasi pikeun BPF klasik teras diperpanjang. Bacaan anu saé upami anjeun hoyong neuleuman basa rakitan sareng detil arsitéktur téknis.

  3. Blog ngeunaan BPF tina facebook. Jarang diropéa, tapi pas, sakumaha Alexei Starovoitov (panulis eBPF) sareng Andrii Nakryiko - (maintainer) nyerat di dinya. libbpf).

  4. Rahasia bpftool. Utas twitter anu ngahibur ti Quentin Monnet kalayan conto sareng rahasia ngagunakeun bpftool.

  5. Teuleum ka BPF: daptar bahan bacaan. A raksasa (jeung masih dijaga) daptar Tumbu ka dokuméntasi BPF ti Quentin Monnet.

sumber: www.habr.com

Tambahkeun komentar