Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Boshida texnologiya bor edi va u BPF deb ataldi. Biz unga qaradik oldingi, Ushbu turkumdagi Eski Ahd maqolasi. 2013 yilda Aleksey Starovoitov va Daniel Borkmanning sa'y-harakatlari bilan uning zamonaviy 64-bitli mashinalar uchun optimallashtirilgan takomillashtirilgan versiyasi ishlab chiqildi va Linux yadrosiga kiritildi. Ushbu yangi texnologiya qisqacha Ichki BPF deb nomlandi, keyin Kengaytirilgan BPF deb o'zgartirildi va endi, bir necha yil o'tgach, hamma uni oddiygina BPF deb ataydi.

Taxminan aytganda, BPF sizga Linux yadro maydonida ixtiyoriy foydalanuvchi tomonidan taqdim etilgan kodni ishlatish imkonini beradi va yangi arxitektura shu qadar muvaffaqiyatli bo'ldiki, uning barcha ilovalarini tavsiflash uchun bizga yana o'nlab maqolalar kerak bo'ladi. (Quyidagi ishlash kodida ko'rib turganingizdek, ishlab chiquvchilar yaxshi ish qilmagan yagona narsa bu munosib logotip yaratish edi.)

Ushbu maqolada BPF virtual mashinasining tuzilishi, BPF bilan ishlash uchun yadro interfeyslari, ishlab chiqish vositalari, shuningdek, mavjud imkoniyatlarning qisqacha, juda qisqacha tavsifi, ya'ni. BPF ning amaliy qo'llanilishini chuqurroq o'rganish uchun kelajakda bizga kerak bo'lgan hamma narsa.
Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Maqolaning qisqacha mazmuni

BPF arxitekturasiga kirish. Birinchidan, biz BPF arxitekturasini qushning nazari bilan ko'rib chiqamiz va asosiy tarkibiy qismlarni belgilaymiz.

BPF virtual mashinasining registrlari va buyruqlar tizimi. Umuman olganda, arxitektura haqida allaqachon tasavvurga ega bo'lgan holda, biz BPF virtual mashinasining tuzilishini tasvirlaymiz.

BPF obyektlarining hayot aylanishi, bpffs fayl tizimi. Ushbu bo'limda biz BPF ob'ektlari - dasturlar va xaritalarning hayot aylanishini batafsil ko'rib chiqamiz.

bpf tizim chaqiruvi yordamida ob'ektlarni boshqarish. Tizim allaqachon mavjud bo'lganligi sababli, biz nihoyat maxsus tizim chaqiruvi yordamida foydalanuvchi maydonidan ob'ektlarni qanday yaratish va boshqarishni ko'rib chiqamiz - bpf(2).

Пишем программы BPF с помощью libbpf. Albatta, tizim chaqiruvi yordamida dasturlar yozishingiz mumkin. Lekin bu qiyin. Haqiqiyroq stsenariy uchun yadro dasturchilari kutubxona yaratdilar libbpf. Biz asosiy BPF ilova skeletini yaratamiz, undan keyingi misollarda foydalanamiz.

Yadro yordamchilari. Bu erda biz BPF dasturlari yadro yordamchi funksiyalariga qanday kirishi mumkinligini bilib olamiz - bu vosita, xaritalar bilan bir qatorda, klassik bilan solishtirganda yangi BPF imkoniyatlarini tubdan kengaytiradi.

BPF dasturlaridan xaritalarga kirish. Shu nuqtaga kelib, biz xaritalardan foydalanadigan dasturlarni qanday yaratishimiz mumkinligini aniq tushunish uchun etarli ma'lumotga ega bo'lamiz. Va keling, hatto buyuk va qudratli tekshiruvchiga tez nazar tashlaylik.

Rivojlanish vositalari. Tajribalar uchun kerakli yordamchi dasturlar va yadrolarni yig'ish bo'yicha yordam bo'limi.

Xulosa. Maqolaning oxirida, hozirgacha o'qiganlar, keyingi maqolalarda rag'batlantiruvchi so'zlar va nima sodir bo'lishining qisqacha tavsifini topadilar. Davomini kutish istagi yoki qobiliyatiga ega bo'lmaganlar uchun o'z-o'zini o'rganish uchun bir qator havolalarni ham sanab o'tamiz.

BPF arxitekturasiga kirish

BPF arxitekturasini ko'rib chiqishni boshlashdan oldin, biz oxirgi marta (oh) ga murojaat qilamiz klassik BPF, bu RISC mashinalarining paydo bo'lishiga javob sifatida ishlab chiqilgan va paketlarni samarali filtrlash muammosini hal qilgan. Arxitektura shunchalik muvaffaqiyatli bo'ldiki, XNUMX-yillarda Berkeley UNIX-da tug'ilib, u mavjud operatsion tizimlarning ko'pchiligiga o'tkazildi, aqldan ozgan yigirmanchi yillargacha saqlanib qoldi va hali ham yangi ilovalarni topmoqda.

Yangi BPF 64-bitli mashinalarning keng tarqalganligiga, bulutli xizmatlarga va SDN yaratish vositalariga ehtiyojning ortishiga javob sifatida ishlab chiqilgan (Stez-tez -daniqlangan nishlash). Klassik BPF o'rnini bosuvchi yadro tarmog'i muhandislari tomonidan ishlab chiqilgan yangi BPF tom ma'noda olti oy o'tgach, Linux tizimlarini kuzatish qiyin vazifada ilovalarni topdi va endi, paydo bo'lganidan olti yil o'tgach, bizga keyingi maqola kerak bo'ladi. har xil turdagi dasturlarni sanab o'ting.

Qiziqarli rasmlar

Asosiysi, BPF xavfsizlikni buzmasdan yadro maydonida "o'zboshimchalik" kodini ishlatish imkonini beruvchi sandbox virtual mashinasidir. BPF dasturlari foydalanuvchi maydonida yaratiladi, yadroga yuklanadi va ba'zi voqea manbalariga ulanadi. Hodisa, masalan, paketni tarmoq interfeysiga yetkazib berish, yadroning ba'zi funksiyalarini ishga tushirish va hokazo bo'lishi mumkin. Paket holatida BPF dasturi paketning ma'lumotlari va metama'lumotlariga (dastur turiga qarab o'qish va, ehtimol, yozish uchun) kirish huquqiga ega bo'ladi; yadro funktsiyasini ishga tushirganda, argumentlar. funktsiya, shu jumladan yadro xotirasiga ko'rsatgichlar va boshqalar.

Keling, ushbu jarayonni batafsil ko'rib chiqaylik. Boshlash uchun keling, klassik BPF dan birinchi farqi haqida gapiraylik, ular uchun dasturlar assemblerda yozilgan. Yangi versiyada arxitektura kengaytirildi, shunda dasturlar yuqori darajadagi tillarda, birinchi navbatda, albatta, C tilida yozilishi mumkin edi. Buning uchun BPF arxitekturasi uchun bayt-kod yaratish imkonini beruvchi llvm uchun backend ishlab chiqilgan.

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

BPF arxitekturasi qisman zamonaviy mashinalarda samarali ishlash uchun ishlab chiqilgan. Buni amalda bajarish uchun yadroga yuklangan BPF baytkodi JIT kompilyatori deb ataladigan komponent yordamida mahalliy kodga tarjima qilinadi (JUst In Time). Keyinchalik, esingizda bo'lsa, klassik BPF-da dastur yadroga yuklangan va voqea manbasiga atomik ravishda biriktirilgan - yagona tizim chaqiruvi kontekstida. Yangi arxitekturada bu ikki bosqichda sodir bo'ladi - birinchi navbatda kod tizim chaqiruvi yordamida yadroga yuklanadi. bpf(2)va keyinchalik, dastur turiga qarab o'zgarib turadigan boshqa mexanizmlar orqali dastur voqea manbasiga biriktiriladi.

Bu erda o'quvchi savol berishi mumkin: bu mumkinmi? Bunday kodning bajarilishi xavfsizligi qanday kafolatlanadi? Bajarish xavfsizligi bizga verifier deb nomlangan BPF dasturlarini yuklash bosqichida kafolatlanadi (ingliz tilida bu bosqich verifier deb ataladi va men inglizcha so'zdan foydalanishda davom etaman):

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Verifier - bu dastur yadroning normal ishlashini buzmasligini ta'minlaydigan statik analizator. Aytgancha, bu dastur tizimning ishlashiga xalaqit bera olmaydi degani emas - BPF dasturlari turiga qarab yadro xotirasi bo'limlarini o'qishi va qayta yozishi, funktsiyalar qiymatlarini qaytarishi, kesish, qo'shish, qayta yozish mumkin. va hatto tarmoq paketlarini yuborish. Verifier, BPF dasturini ishga tushirish yadroni buzmasligini va qoidalarga ko'ra, yozish huquqiga ega bo'lgan dastur, masalan, chiquvchi paketning ma'lumotlari, paketdan tashqari yadro xotirasini qayta yoza olmasligini kafolatlaydi. BPF ning boshqa barcha tarkibiy qismlari bilan tanishganimizdan so'ng, biz tegishli bo'limda verifierni biroz batafsilroq ko'rib chiqamiz.

Xo'sh, biz hozirgacha nimani o'rgandik? Foydalanuvchi C tilida dastur yozadi, tizim chaqiruvi yordamida uni yadroga yuklaydi bpf(2), bu erda u tekshiruvchi tomonidan tekshiriladi va mahalliy baytekodga tarjima qilinadi. Keyin o'sha yoki boshqa foydalanuvchi dasturni voqea manbasiga ulaydi va u bajarila boshlaydi. Yuklash va ulanishni ajratish bir necha sabablarga ko'ra zarur. Birinchidan, verifierni ishlatish nisbatan qimmat va bir xil dasturni bir necha marta yuklab olish orqali biz kompyuter vaqtini behuda sarflaymiz. Ikkinchidan, dasturning aynan qanday ulanishi uning turiga bog'liq va bir yil oldin ishlab chiqilgan bitta "universal" interfeys yangi turdagi dasturlar uchun mos kelmasligi mumkin. (Hozir arxitektura etuklashib borayotgan bo'lsa-da, ushbu interfeysni darajada birlashtirish g'oyasi mavjud. libbpf.)

Diqqatli o'quvchi hali rasmlar bilan tugamaganimizni sezishi mumkin. Haqiqatan ham, yuqorida aytilganlarning barchasi nima uchun BPF klassik BPF bilan solishtirganda rasmni tubdan o'zgartirayotganini tushuntirmaydi. Qo'llash doirasini sezilarli darajada kengaytiradigan ikkita yangilik umumiy xotira va yadro yordamchi funktsiyalaridan foydalanish qobiliyatidir. BPF-da umumiy xotira xaritalar deb ataladigan - ma'lum bir API bilan umumiy ma'lumotlar tuzilmalari yordamida amalga oshiriladi. Ehtimol, ular bu nomni olishgan, chunki birinchi turdagi xaritalar xesh jadvali paydo bo'lgan. Keyin massivlar paydo bo'ldi, mahalliy (protsessor uchun) xesh jadvallari va mahalliy massivlar, qidiruv daraxtlari, BPF dasturlariga ko'rsatgichlarni o'z ichiga olgan xaritalar va boshqalar. Bizni qiziqtirgani shundaki, BPF dasturlari endi qo'ng'iroqlar orasidagi holatni saqlab turish va uni boshqa dasturlar va foydalanuvchi maydoni bilan baham ko'rish imkoniyatiga ega.

Xaritalarga tizim chaqiruvi yordamida foydalanuvchi jarayonlaridan kirish mumkin bpf(2), va yordamchi funksiyalar yordamida yadroda ishlaydigan BPF dasturlaridan. Bundan tashqari, yordamchilar nafaqat xaritalar bilan ishlash, balki yadroning boshqa imkoniyatlariga kirish uchun ham mavjud. Masalan, BPF dasturlari paketlarni boshqa interfeyslarga yo'naltirish, perf hodisalarini yaratish, yadro tuzilmalariga kirish va hokazolar uchun yordamchi funktsiyalardan foydalanishi mumkin.

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Xulosa qilib aytganda, BPF yadro maydoniga o'zboshimchalik bilan, ya'ni tekshirgich tomonidan sinovdan o'tgan foydalanuvchi kodini yuklash imkoniyatini beradi. Ushbu kod qo'ng'iroqlar orasidagi holatni saqlashi va foydalanuvchi maydoni bilan ma'lumot almashishi mumkin, shuningdek, ushbu turdagi dastur tomonidan ruxsat etilgan yadro quyi tizimlariga kirish huquqiga ega.

Bu allaqachon yadro modullari tomonidan taqdim etilgan imkoniyatlarga o'xshaydi, ular bilan solishtirganda BPF ba'zi afzalliklarga ega (albatta, siz faqat shunga o'xshash ilovalarni solishtirishingiz mumkin, masalan, tizimni kuzatish - BPF bilan o'zboshimchalik bilan drayverni yoza olmaysiz). Siz kirishning pastki chegarasini (BPF-dan foydalanadigan ba'zi yordam dasturlari foydalanuvchidan yadro dasturlash ko'nikmalarini yoki umuman dasturlash ko'nikmalarini talab qilmaydi), ish vaqti xavfsizligini (yozishda tizimni buzmaganlar uchun sharhlarda qo'lingizni ko'taring) qayd etishingiz mumkin. yoki sinov modullari), atomiklik - modullarni qayta yuklashda uzilishlar mavjud va BPF quyi tizimi hech qanday voqea o'tkazib yuborilmasligini ta'minlaydi (adolatli bo'lsak, bu BPF dasturlarining barcha turlari uchun to'g'ri kelmaydi).

Bunday imkoniyatlarning mavjudligi BPF-ni yadroni kengaytirish uchun universal vositaga aylantiradi, bu amalda tasdiqlangan: BPF-ga tobora ko'proq yangi turdagi dasturlar qo'shilmoqda, tobora ko'proq yirik kompaniyalar BPF-ni 24 × 7 jangovar serverlarda ishlatmoqdalar. startaplar o'z bizneslarini BPFga asoslangan yechimlar asosida quradilar. BPF hamma joyda qo'llaniladi: DDoS hujumlaridan himoya qilish, SDN yaratish (masalan, kubernetlar uchun tarmoqlarni amalga oshirish), asosiy tizimni kuzatish vositasi va statistika kollektori sifatida, hujumni aniqlash tizimlari va sandbox tizimlarida va hokazo.

Keling, maqolaning umumiy qismini shu yerda tugatamiz va virtual mashina va BPF ekotizimini batafsil ko'rib chiqamiz.

Digressiya: kommunal xizmatlar

Quyidagi bo'limlardagi misollarni ishga tushirish uchun sizga kamida bir nechta yordamchi dasturlar kerak bo'lishi mumkin. llvm/clang bpf yordami bilan va bpftool. Bo'limda Rivojlanish vositalari Utilitlarni yig'ish bo'yicha ko'rsatmalarni, shuningdek, yadroingizni o'qishingiz mumkin. Ushbu bo'lim taqdimotimiz uyg'unligini buzmaslik uchun quyida joylashtirilgan.

BPF virtual mashina registrlari va ko'rsatmalar tizimi

BPF arxitekturasi va buyruqlar tizimi dasturlar C tilida yozilishi va yadroga yuklangandan so'ng mahalliy kodga tarjima qilinishini hisobga olgan holda ishlab chiqilgan. Shuning uchun registrlar soni va buyruqlar to'plami matematik ma'noda zamonaviy mashinalar imkoniyatlarining kesishishini hisobga olgan holda tanlangan. Bundan tashqari, dasturlarga turli cheklovlar qo'yildi, masalan, yaqin vaqtgacha tsikllar va pastki dasturlarni yozish mumkin emas edi va ko'rsatmalar soni 4096 tagacha cheklangan edi (hozirda imtiyozli dasturlar million ko'rsatmalarni yuklashi mumkin).

BPF o'n bitta foydalanuvchi kirishi mumkin bo'lgan 64 bitli registrlarga ega r0-r10 va dastur hisoblagichi. Roʻyxatdan oʻtish r10 ramka ko'rsatgichini o'z ichiga oladi va faqat o'qish uchun mo'ljallangan. Dasturlar ish vaqtida 512 baytli stekga va xaritalar ko'rinishidagi cheksiz miqdordagi umumiy xotiraga kirish huquqiga ega.

BPF dasturlariga dastur tipidagi yadro yordamchilarining ma'lum bir to'plamini va yaqinda muntazam funktsiyalarni ishga tushirishga ruxsat berilgan. Har bir chaqirilgan funktsiya registrlarda uzatiladigan beshtagacha argumentni qabul qilishi mumkin r1-r5, va qaytish qiymati ga uzatiladi r0. Funktsiyadan qaytgandan so'ng registrlarning mazmuni kafolatlanadi r6-r9 O'zgarmaydi.

Dasturni samarali tarjima qilish uchun registrlar r0-r11 barcha qo'llab-quvvatlanadigan arxitekturalar uchun joriy arxitekturaning ABI xususiyatlarini hisobga olgan holda haqiqiy registrlar bilan noyob tarzda taqqoslanadi. Masalan, uchun x86_64 registrlar r1-r5, funksiya parametrlarini o'tkazish uchun ishlatiladi, yoniq ko'rsatiladi rdi, rsi, rdx, rcx, r8, bu funksiyalarga parametrlarni uzatish uchun ishlatiladi x86_64. Misol uchun, chapdagi kod o'ngdagi kodga quyidagicha tarjima qilinadi:

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

Ro'yxatga olish r0 dasturning bajarilishi natijasini va registrda qaytarish uchun ham ishlatiladi r1 dastur kontekstga ko'rsatgich uzatiladi - dastur turiga qarab, bu, masalan, struktura bo'lishi mumkin. struct xdp_md (XDP uchun) yoki tuzilish struct __sk_buff (turli xil tarmoq dasturlari uchun) yoki tuzilish struct pt_regs (turli turdagi kuzatuv dasturlari uchun) va boshqalar.

Shunday qilib, bizda registrlar to'plami, yadro yordamchilari, stek, kontekst ko'rsatkichi va xaritalar ko'rinishidagi umumiy xotira mavjud edi. Bularning barchasi sayohatda mutlaqo kerak emas, lekin ...

Keling, tavsifni davom ettiramiz va ushbu ob'ektlar bilan ishlash uchun buyruqlar tizimi haqida gapiramiz. Hammasi (deyarli hammasi) BPF ko'rsatmalari qattiq 64 bitli o'lchamga ega. Agar siz 64 bitli Big Endian mashinasida bitta ko'rsatmalarga qarasangiz, ko'rasiz

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

u Code - bu ko'rsatmaning kodlanishi, Dst/Src mos ravishda qabul qiluvchi va manbaning kodlashlari, Off - 16-bitli imzo chekinish va Imm ba'zi ko'rsatmalarda qo'llaniladigan 32 bitli imzolangan butun son (cBPF doimiysi K ga o'xshash). Kodlash Code ikki turdan biriga ega:

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Yo'riqnoma sinflari 0, 1, 2, 3 xotira bilan ishlash buyruqlarini belgilaydi. Ular deyiladi, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, mos ravishda. 4, 7-sinflar (BPF_ALU, BPF_ALU64) ALU ko'rsatmalari to'plamini tashkil qiladi. 5, 6-sinflar (BPF_JMP, BPF_JMP32) sakrash ko'rsatmalarini o'z ichiga oladi.

BPF yo'riqnoma tizimini o'rganishning keyingi rejasi quyidagicha: barcha ko'rsatmalar va ularning parametrlarini sinchkovlik bilan sanab o'tish o'rniga, biz ushbu bo'limda bir nechta misollarni ko'rib chiqamiz va ulardan ko'rsatmalar aslida qanday ishlashi va qanday ishlashi aniq bo'ladi. BPF uchun har qanday ikkilik faylni qo'lda qismlarga ajratish. Maqolaning keyingi qismida materialni birlashtirish uchun biz Verifier, JIT kompilyatori, klassik BPF tarjimasi, shuningdek, xaritalarni o'rganish, funktsiyalarni chaqirish va h.k. bo'limlarida individual ko'rsatmalar bilan tanishamiz.

Shaxsiy ko'rsatmalar haqida gapirganda, biz asosiy fayllarga murojaat qilamiz bpf.h и bpf_common.h, BPF ko'rsatmalarining raqamli kodlarini belgilaydi. Arxitekturani mustaqil ravishda o'rganish va/yoki ikkilik fayllarni tahlil qilishda siz murakkablik bo'yicha tartiblangan quyidagi manbalarda semantikani topishingiz mumkin: Norasmiy eBPF spetsifikatsiyasi, BPF va XDP Yo'riqnomasi, Yo'riqnomalar to'plami, Documentation/networking/filter.txt va, albatta, Linux manba kodida - verifier, JIT, BPF interpretator.

Misol: boshingizdagi BPF ni demontaj qilish

Keling, dasturni kompilyatsiya qiladigan misolni ko'rib chiqaylik readelf-example.c va natijada olingan binarga qarang. Asl tarkibni ochib beramiz readelf-example.c Quyida, uning mantiqini ikkilik kodlardan tiklaganimizdan so'ng:

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

Chiqishdagi birinchi ustun readelf - bu chekinish va bizning dasturimiz to'rtta buyruqdan iborat:

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

Buyruq kodlari teng b7, 15, b7 и 95. Eslatib o'tamiz, eng kam muhim uchta bit ko'rsatmalar sinfidir. Bizning holatda, barcha ko'rsatmalarning to'rtinchi biti bo'sh, shuning uchun ko'rsatmalar sinflari mos ravishda 7, 5, 7, 5. 7-sinf. BPF_ALU64, va 5 BPF_JMP. Ikkala sinf uchun ham ko'rsatmalar formati bir xil (yuqoriga qarang) va biz dasturimizni shunday qayta yozishimiz mumkin (shu bilan birga biz qolgan ustunlarni inson shaklida qayta yozamiz):

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

Operatsiya b daraja ALU64 Ismi? BPF_MOV. U maqsad registriga qiymat beradi. Bit o'rnatilgan bo'lsa s (manba), keyin qiymat manba registridan olinadi va agar bizning holatimizda bo'lgani kabi o'rnatilmagan bo'lsa, u holda qiymat maydondan olinadi. Imm. Shunday qilib, birinchi va uchinchi ko'rsatmalarda biz operatsiyani bajaramiz r0 = Imm. Bundan tashqari, JMP 1-sinf operatsiyasi BPF_JEQ (agar teng bo'lsa, sakrash). Bizning holatda, bitdan beri S nolga teng, u manba registrining qiymatini maydon bilan taqqoslaydi Imm. Agar qiymatlar mos kelsa, o'tish sodir bo'ladi PC + Offqayerda PC, odatdagidek, keyingi ko'rsatmaning manzilini o'z ichiga oladi. Nihoyat, JMP 9-sinf operatsiyasi BPF_EXIT. Ushbu ko'rsatma dasturni tugatadi va yadroga qaytadi r0. Keling, jadvalimizga yangi ustun qo'shamiz:

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

Buni qulayroq shaklda qayta yozishimiz mumkin:

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

Agar registrda nima borligini eslasak r1 dastur yadrodan kontekstga ko'rsatgich va registrda uzatiladi r0 qiymat yadroga qaytariladi, u holda kontekstga ko'rsatgich nolga teng bo'lsa, biz 1 ni, aks holda - 2 ni qaytaramiz. Keling, manbaga qarab, biz haq ekanligimizni tekshiramiz:

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

Ha, bu ma'nosiz dastur, lekin u faqat to'rtta oddiy ko'rsatmalarga tarjima qilinadi.

Istisno misol: 16 baytli ko'rsatma

Yuqorida aytib o'tganimizdek, ba'zi ko'rsatmalar 64 bitdan ko'proq vaqtni oladi. Bu, masalan, ko'rsatmalarga tegishli lddw (Kod = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — maydonlardan qo'sh so'zni registrga yuklash Imm. Gap shundaki Imm 32 o'lchamga ega va qo'sh so'z 64 bit, shuning uchun bitta 64 bitli ko'rsatmada 64 bitli darhol qiymatni registrga yuklash ishlamaydi. Buning uchun 64 bitli qiymatning ikkinchi qismini maydonda saqlash uchun ikkita qo'shni ko'rsatmalar ishlatiladi Imm. Misol:

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

Ikkilik dasturda faqat ikkita ko'rsatmalar mavjud:

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

Biz ko'rsatmalar bilan yana uchrashamiz lddw, biz ko'chirish va xaritalar bilan ishlash haqida gapirganda.

Misol: standart asboblar yordamida BPFni demontaj qilish

Shunday qilib, biz BPF ikkilik kodlarini o'qishni o'rgandik va agar kerak bo'lsa, har qanday ko'rsatmani tahlil qilishga tayyormiz. Biroq, shuni aytish kerakki, amalda standart vositalar yordamida dasturlarni qismlarga ajratish qulayroq va tezroq, masalan:

$ 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

BPF obyektlarining hayot aylanishi, bpffs fayl tizimi

(Men ushbu kichik bo'limda tasvirlangan ba'zi tafsilotlarni birinchi bo'lib o'rgandim post Aleksey Starovoytov BPF blogi.)

BPF ob'ektlari - dasturlar va xaritalar - buyruqlar yordamida foydalanuvchi maydonidan yaratiladi BPF_PROG_LOAD и BPF_MAP_CREATE tizim chaqiruvi bpf(2), bu qanday sodir bo'lishi haqida keyingi bo'limda gaplashamiz. Bu yadro ma'lumotlar tuzilmalarini va ularning har biri uchun yaratadi refcount (mos yozuvlar soni) bittaga o'rnatiladi va ob'ektga ishora qiluvchi fayl deskriptori foydalanuvchiga qaytariladi. Tutqich yopilgandan keyin refcount ob'ekt bittaga kamayadi va u nolga yetganda, ob'ekt yo'q qilinadi.

Agar dastur xaritalardan foydalansa, u holda refcount bu xaritalar dasturni yuklagandan so'ng bittaga oshiriladi, ya'ni. ularning fayl deskriptorlari foydalanuvchi jarayonidan yopilishi mumkin va hali ham refcount nolga aylanmaydi:

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Dasturni muvaffaqiyatli yuklaganimizdan so'ng, biz odatda uni qandaydir hodisa generatoriga biriktiramiz. Misol uchun, biz uni kiruvchi paketlarni qayta ishlash yoki ba'zilariga ulash uchun tarmoq interfeysiga qo'yishimiz mumkin tracepoint yadroda. Bu vaqtda mos yozuvlar hisoblagichi ham bittaga ko'payadi va biz yuklovchi dasturidagi fayl deskriptorini yopishimiz mumkin bo'ladi.

Agar yuklovchini endi o'chirsak nima bo'ladi? Bu hodisa generatori (kanca) turiga bog'liq. Barcha tarmoq ilgaklari yuklagich tugallangandan so'ng mavjud bo'ladi, bular global ilgaklar deb ataladi. Va, masalan, kuzatuv dasturlari ularni yaratgan jarayon tugagandan so'ng chiqariladi (va shuning uchun "mahalliy jarayonga" mahalliy deb ataladi). Texnik jihatdan, mahalliy ilgaklar har doim foydalanuvchi maydonida mos keladigan fayl identifikatoriga ega va shuning uchun jarayon yopilganda yopiladi, lekin global ilgaklar yo'q. Quyidagi rasmda qizil xochlardan foydalanib, men yuklash dasturining tugatilishi mahalliy va global ilgaklar holatida ob'ektlarning ishlash muddatiga qanday ta'sir qilishini ko'rsatishga harakat qilaman.

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

Nima uchun mahalliy va global ilgaklar o'rtasida farq bor? Ba'zi turdagi tarmoq dasturlarini ishga tushirish foydalanuvchi maydonisiz mantiqan to'g'ri keladi, masalan, DDoS himoyasini tasavvur qiling - yuklovchi qoidalarni yozadi va BPF dasturini tarmoq interfeysiga ulaydi, shundan so'ng bootloader borib o'zini o'ldirishi mumkin. Boshqa tomondan, o'n daqiqada tizzangizga yozgan disk raskadrovka dasturini tasavvur qiling - u tugagach, tizimda axlat qolmasligini xohlaysiz va mahalliy ilgaklar buni ta'minlaydi.

Boshqa tomondan, yadrodagi kuzatuv nuqtasiga ulanishni va ko'p yillar davomida statistik ma'lumotlarni to'plashni xohlayotganingizni tasavvur qiling. Bunday holda, siz foydalanuvchi qismini to'ldirishni va vaqti-vaqti bilan statistikaga qaytishni xohlaysiz. bpf fayl tizimi bu imkoniyatni beradi. Bu faqat xotirada bo'lgan soxta fayl tizimi bo'lib, u BPF ob'ektlariga havola qiluvchi fayllarni yaratishga va shu bilan oshirishga imkon beradi. refcount ob'ektlar. Shundan so'ng, yuklovchi chiqishi mumkin va u yaratgan ob'ektlar tirik qoladi.

Kichkintoylar uchun BPF, birinchi qism: kengaytirilgan BPF

BPF ob'ektlariga havola qiluvchi bpfflarda fayllarni yaratish "pinning" deb ataladi (quyidagi iborada bo'lgani kabi: "jarayon BPF dasturini yoki xaritasini qo'yishi mumkin"). BPF ob'ektlari uchun fayl ob'ektlarini yaratish nafaqat mahalliy ob'ektlarning ishlash muddatini uzaytirish uchun, balki global ob'ektlardan foydalanish qulayligi uchun ham mantiqiydir - global DDoS himoya dasturi misoliga qaytsak, biz kelib statistik ma'lumotlarni ko'rishni xohlaymiz. vaqti-vaqti bilan.

BPF fayl tizimi odatda o'rnatilgan /sys/fs/bpf, lekin u mahalliy sifatida ham o'rnatilishi mumkin, masalan, quyidagicha:

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

Fayl tizimi nomlari buyruq yordamida yaratiladi BPF_OBJ_PIN BPF tizimi chaqiruvi. Tasavvur qilish uchun, keling, dasturni olaylik, uni kompilyatsiya qilamiz, yuklaymiz va uni bog'laymiz bpffs. Bizning dasturimiz hech qanday foydali ish qilmaydi, biz faqat kodni taqdim etmoqdamiz, shunda siz misolni takrorlashingiz mumkin:

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

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

Keling, ushbu dasturni kompilyatsiya qilamiz va fayl tizimining mahalliy nusxasini yaratamiz bpffs:

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

Endi yordamchi dastur yordamida dasturimizni yuklab olamiz bpftool va unga hamroh bo'lgan tizim qo'ng'iroqlariga qarang bpf(2) (ba'zi ahamiyatsiz satrlar chiziq chiqishidan olib tashlandi):

$ 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

Bu erda biz dasturni foydalanib yukladik BPF_PROG_LOAD, yadrodan fayl deskriptorini oldi 3 va buyruq yordamida BPF_OBJ_PIN bu fayl deskriptorini fayl sifatida mahkamladi "bpf-mountpoint/test". Shundan so'ng bootloader dasturi bpftool ishni tugatdi, lekin dasturimiz yadroda qoldi, garchi biz uni hech qanday tarmoq interfeysiga biriktirmagan bo'lsak ham:

$ 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

Biz fayl ob'ektini odatdagidek o'chirib tashlashimiz mumkin unlink(2) va shundan so'ng tegishli dastur o'chiriladi:

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

Ob'ektlarni o'chirish

Ob'ektlarni o'chirish haqida gapiradigan bo'lsak, shuni aniqlashtirish kerakki, biz dasturni ilgakdan (voqea generatori) uzganimizdan so'ng, biron bir yangi hodisa uni ishga tushirishga turtki bo'lmaydi, ammo dasturning barcha joriy nusxalari odatdagi tartibda yakunlanadi. .

BPF dasturlarining ayrim turlari dasturni tezda almashtirish imkonini beradi, ya'ni. ketma-ketlik atomligini ta'minlash replace = detach old program, attach new program. Bunday holda, dasturning eski versiyasining barcha faol nusxalari o'z ishini tugatadi va yangi dasturdan yangi hodisa ishlov beruvchilari yaratiladi va bu erda "atomlik" birorta ham hodisa o'tkazib yuborilmasligini bildiradi.

Dasturlarni voqea manbalariga biriktirish

Ushbu maqolada biz dasturlarni voqea manbalariga ulashni alohida ta'riflamaymiz, chunki buni ma'lum bir turdagi dastur kontekstida o'rganish mantiqan. Sm. misol quyida XDP kabi dasturlar qanday ulanganligini ko'rsatamiz.

bpf tizimi qo'ng'irog'i yordamida ob'ektlarni boshqarish

BPF dasturlari

Barcha BPF ob'ektlari tizim chaqiruvi yordamida foydalanuvchi maydonidan yaratiladi va boshqariladi bpf, quyidagi prototipga ega:

#include <linux/bpf.h>

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

Mana jamoa cmd tip qiymatlaridan biri hisoblanadi enum bpf_cmd, attr — maʼlum bir dastur uchun parametrlarga koʻrsatgich va size — ko'rsatkichga ko'ra ob'ekt o'lchami, ya'ni. odatda bu sizeof(*attr). 5.8 yadrosida tizim chaqiruvi bpf 34 xil buyruqlarni qo'llab-quvvatlaydi va aniqlash union bpf_attr 200 qatorni egallaydi. Ammo biz bundan qo'rqmasligimiz kerak, chunki biz bir nechta maqolalar davomida buyruqlar va parametrlar bilan tanishamiz.

Keling, jamoadan boshlaylik BPF_PROG_LOAD, bu BPF dasturlarini yaratadi - BPF ko'rsatmalari to'plamini oladi va uni yadroga yuklaydi. Yuklash vaqtida tekshirgich ishga tushiriladi, so'ngra JIT kompilyatori va muvaffaqiyatli bajarilgandan so'ng dastur fayli identifikatori foydalanuvchiga qaytariladi. Keyingi bo'limda u bilan nima sodir bo'lishini ko'rdik BPF ob'ektlarining hayot aylanishi haqida.

Endi biz oddiy BPF dasturini yuklaydigan maxsus dastur yozamiz, lekin avval biz qanday dasturni yuklamoqchi ekanligimizni hal qilishimiz kerak - biz tanlashimiz kerak. Turi va ushbu turdagi doirasida, tekshirish testidan o'tadigan dastur yozing. Biroq, jarayonni murakkablashtirmaslik uchun, bu erda tayyor echim bor: biz shunga o'xshash dasturni olamiz BPF_PROG_TYPE_XDP, bu qiymatni qaytaradi XDP_PASS (barcha paketlarni o'tkazib yuboring). BPF assemblerda bu juda oddiy ko'rinadi:

r0 = 2
exit

Biz qaror qilganimizdan keyin ekan biz yuklaymiz, buni qanday qilishni sizga ayta olamiz:

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

Dasturdagi qiziqarli voqealar massivni aniqlashdan boshlanadi insns - mashina kodidagi BPF dasturimiz. Bunday holda, BPF dasturining har bir ko'rsatmasi tuzilishga o'ralgan bpf_insn. Birinchi element insns ko'rsatmalarga mos keladi r0 = 2, ikkinchisi - exit.

Chekinish. Yadro mashina kodlarini yozish va yadro sarlavhasi faylidan foydalanish uchun qulayroq makrolarni belgilaydi tools/include/linux/filter.h yozishimiz mumkin edi

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

Ammo BPF dasturlarini mahalliy kodda yozish faqat yadroda testlar va BPF haqidagi maqolalarni yozish uchun zarur bo'lganligi sababli, bu makrolarning yo'qligi ishlab chiquvchining hayotini qiyinlashtirmaydi.

BPF dasturini aniqlagandan so'ng, biz uni yadroga yuklashga o'tamiz. Bizning minimalist parametrlar to'plamimiz attr dastur turi, ko'rsatmalar to'plami va soni, talab qilinadigan litsenziya va nomni o'z ichiga oladi "woo", biz yuklab olgandan so'ng dasturimizni tizimda topish uchun foydalanamiz. Dastur, va'da qilinganidek, tizim chaqiruvi yordamida tizimga yuklanadi bpf.

Dastur oxirida biz foydali yukni simulyatsiya qiladigan cheksiz pastadirga tushamiz. Busiz, tizim qo'ng'irog'i bizga qaytarilgan fayl deskriptori yopilganda dastur yadro tomonidan o'chiriladi. bpf, va biz buni tizimda ko'rmaymiz.

Xo'sh, biz sinovga tayyormiz. Keling, dasturni yig'amiz va ishga tushiramiz stracehamma narsa kerakli darajada ishlayotganligini tekshirish uchun:

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

Hammasi joyida, bpf(2) bizga dastagi 3ni qaytardi va biz bilan cheksiz tsiklga kirdik pause(). Keling, dasturimizni tizimda topishga harakat qilaylik. Buning uchun biz boshqa terminalga o'tamiz va yordamchi dasturdan foydalanamiz 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)

Tizimda yuklangan dastur mavjudligini ko'ramiz woo uning global identifikatori 390 va hozirda davom etmoqda simple-prog dasturga ishora qiluvchi ochiq fayl deskriptori mavjud (va agar simple-prog keyin ishni tugatadi woo yo'qoladi). Kutilganidek, dastur woo BPF arxitekturasida ikkilik kodlarning 16 baytini oladi - ikkita ko'rsatmalar, lekin uning asl shaklida (x86_64) u allaqachon 40 baytni tashkil qiladi. Keling, dasturimizni asl shaklida ko'rib chiqaylik:

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

kutilmagan hodisalar yo'q. Endi JIT kompilyatori tomonidan yaratilgan kodni ko'rib chiqamiz:

# 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

uchun unchalik samarali emas exit(2), lekin adolat uchun, bizning dasturimiz juda oddiy va ahamiyatsiz dasturlar uchun JIT kompilyatori tomonidan qo'shilgan prolog va epilog, albatta, kerak.

Xaritalar

BPF dasturlari boshqa BPF dasturlari uchun ham, foydalanuvchi maydonidagi dasturlar uchun ham kirish mumkin bo'lgan tuzilgan xotira maydonlaridan foydalanishi mumkin. Ushbu ob'ektlar xaritalar deb ataladi va ushbu bo'limda biz ularni tizim chaqiruvi yordamida qanday boshqarishni ko'rsatamiz bpf.

Darhol aytaylik, xaritalarning imkoniyatlari faqat umumiy xotiraga kirish bilan cheklanmaydi. Maxsus maqsadli xaritalar mavjud, masalan, BPF dasturlariga ko'rsatgichlar yoki tarmoq interfeyslariga ko'rsatgichlar, perf hodisalari bilan ishlash uchun xaritalar va boshqalar. O'quvchini chalg'itmaslik uchun biz bu erda ular haqida gapirmaymiz. Bundan tashqari, biz sinxronizatsiya masalalariga e'tibor bermaymiz, chunki bu bizning misollarimiz uchun muhim emas. Mavjud xarita turlarining to'liq ro'yxatini quyidagi havolada topishingiz mumkin <linux/bpf.h>, va bu bo'limda biz misol sifatida tarixan birinchi tur, hash jadvalini olamiz BPF_MAP_TYPE_HASH.

Agar siz C++ da xesh-jadval yaratsangiz, deysiz unordered_map<int,long> woo, bu ruschada “Menga stol kerak woo cheksiz o'lcham, ularning kalitlari turdagi int, va qiymatlar turi hisoblanadi long" BPF xesh-jadvalini yaratish uchun biz xuddi shu narsani qilishimiz kerak, faqat jadvalning maksimal hajmini belgilashimiz kerak va kalitlar va qiymatlar turlarini ko'rsatish o'rniga ularning o'lchamlarini baytlarda ko'rsatishimiz kerak. . Xaritalar yaratish uchun buyruqdan foydalaning BPF_MAP_CREATE tizim chaqiruvi bpf. Keling, xaritani yaratadigan ko'proq yoki kamroq minimal dasturni ko'rib chiqaylik. BPF dasturlarini yuklaydigan oldingi dasturdan so'ng, bu sizga oddiy bo'lib tuyulishi kerak:

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

Bu erda biz parametrlar to'plamini aniqlaymiz attr, unda biz “Menga kalitlar va o'lcham qiymatlari bilan hash jadvali kerak sizeof(int), unda men maksimal to'rtta elementni qo'yishim mumkin." BPF xaritalarini yaratishda siz boshqa parametrlarni belgilashingiz mumkin, masalan, dastur misolida bo'lgani kabi, biz ob'ekt nomini shunday ko'rsatdik. "woo".

Dasturni kompilyatsiya qilamiz va ishga tushiramiz:

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

Mana tizim chaqiruvi bpf(2) bizga deskriptor xarita raqamini qaytardi 3 va keyin dastur, kutilganidek, tizim chaqiruvida qo'shimcha ko'rsatmalarni kutadi pause(2).

Endi dasturimizni fonga yuboramiz yoki boshqa terminalni ochamiz va yordamchi dastur yordamida ob'ektimizni ko'rib chiqamiz bpftool (biz o'z xaritamizni nomi bilan boshqalardan ajrata olamiz):

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

114 raqami bizning ob'ektimizning global identifikatoridir. Tizimdagi har qanday dastur ushbu identifikatordan buyruq yordamida mavjud xaritani ochish uchun foydalanishi mumkin BPF_MAP_GET_FD_BY_ID tizim chaqiruvi bpf.

Endi biz hash jadvalimiz bilan o'ynashimiz mumkin. Keling, uning mazmunini ko'rib chiqaylik:

$ sudo bpftool map dump id 114
Found 0 elements

Bo'sh. Keling, unga qiymat qo'yaylik hash[1] = 1:

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

Jadvalga yana qaraylik:

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

Xayr! Biz bitta element qo'shishga muvaffaq bo'ldik. Shuni yodda tutingki, biz buni amalga oshirish uchun bayt darajasida ishlashimiz kerak, chunki bptftool xesh jadvalidagi qiymatlar qanday turdagi ekanligini bilmaydi. (Ushbu bilimni unga BTF yordamida o'tkazish mumkin, ammo hozir bu haqda ko'proq.)

Qanday qilib bpftool elementlarni o'qiydi va qo'shadi? Keling, kaput ostiga qaraylik:

$ 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

Avval buyruq yordamida xaritani global identifikatori bo'yicha ochdik BPF_MAP_GET_FD_BY_ID и bpf(2) Bizga 3-deskriptorni qaytardi.Keyingi buyruq yordamida BPF_MAP_GET_NEXT_KEY jadvaldagi birinchi kalitni o'tish orqali topdik NULL "oldingi" kalitga ko'rsatgich sifatida. Agar bizda kalit bo'lsa, qila olamiz BPF_MAP_LOOKUP_ELEMbu ko'rsatgichga qiymat qaytaradi value. Keyingi qadam, biz ko'rsatgichni joriy kalitga o'tkazish orqali keyingi elementni topishga harakat qilamiz, ammo bizning jadvalimizda faqat bitta element va buyruq mavjud. BPF_MAP_GET_NEXT_KEY qaytadi ENOENT.

Mayli, keling, qiymatni 1-kalit orqali o'zgartiraylik, deylik, biznes mantiqimiz ro'yxatdan o'tishni talab qiladi 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

Kutilganidek, bu juda oddiy: buyruq BPF_MAP_GET_FD_BY_ID ID va buyruq bo'yicha xaritamizni ochadi BPF_MAP_UPDATE_ELEM elementning ustiga yozadi.

Shunday qilib, bir dasturdan xesh-jadvalni yaratganimizdan so'ng, biz uning mazmunini boshqasidan o'qishimiz va yozishimiz mumkin. E'tibor bering, agar biz buni buyruq satridan qila olsak, tizimdagi boshqa har qanday dastur buni amalga oshirishi mumkin. Yuqorida tavsiflangan buyruqlarga qo'shimcha ravishda, foydalanuvchi maydonidan xaritalar bilan ishlash uchun, Quyidagi:

  • BPF_MAP_LOOKUP_ELEM: kalit bo'yicha qiymatni toping
  • BPF_MAP_UPDATE_ELEM: yangilash/qiymat yaratish
  • BPF_MAP_DELETE_ELEM: kalitni olib tashlang
  • BPF_MAP_GET_NEXT_KEY: keyingi (yoki birinchi) kalitni toping
  • BPF_MAP_GET_NEXT_ID: barcha mavjud xaritalarni ko'rib chiqishga imkon beradi, u shunday ishlaydi bpftool map
  • BPF_MAP_GET_FD_BY_ID: mavjud xaritani global identifikatori orqali oching
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: ob'ekt qiymatini atomik ravishda yangilang va eskisini qaytaring
  • BPF_MAP_FREEZE: xaritani foydalanuvchilar maydonidan o'zgarmas holga keltiring (bu amalni bekor qilib bo'lmaydi)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: ommaviy operatsiyalar. Masalan, BPF_MAP_LOOKUP_AND_DELETE_BATCH - bu xaritadagi barcha qiymatlarni o'qish va tiklashning yagona ishonchli usuli

Bu buyruqlarning hammasi ham barcha xarita turlari uchun ishlamaydi, lekin umuman foydalanuvchi maydonidan boshqa turdagi xaritalar bilan ishlash xesh-jadvallar bilan ishlash bilan bir xil ko'rinadi.

Buyurtma uchun, keling, hash-jadval tajribalarimizni tugatamiz. Esingizda bo'lsin, biz to'rttagacha kalitni o'z ichiga oladigan jadval yaratdik? Keling, yana bir nechta elementlarni qo'shamiz:

$ 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

Hozirgacha juda yaxshi:

$ 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

Keling, yana bir narsani qo'shishga harakat qilaylik:

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

Kutilganidek, muvaffaqiyatga erisha olmadik. Keling, xatoni batafsil ko'rib chiqaylik:

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

Hammasi yaxshi: kutilganidek, jamoa BPF_MAP_UPDATE_ELEM yangi, beshinchi, kalit yaratishga harakat qiladi, lekin qulab tushadi E2BIG.

Shunday qilib, biz BPF dasturlarini yaratishimiz va yuklashimiz, shuningdek, foydalanuvchi maydonidan xaritalarni yaratishimiz va boshqarishimiz mumkin. Endi BPF dasturlarining xaritalaridan qanday foydalanishimiz mumkinligini ko'rib chiqish mantiqan. Biz bu haqda mashinaning makro kodlarida o'qish qiyin bo'lgan dasturlar tilida gapirishimiz mumkin edi, lekin aslida BPF dasturlari qanday yozilishi va saqlanishini ko'rsatish vaqti keldi. libbpf.

(Past darajadagi misol yo'qligidan norozi bo'lgan o'quvchilar uchun: biz xaritalar va yordamchi funktsiyalar yordamida yaratilgan dasturlarni batafsil tahlil qilamiz. libbpf va sizga ko'rsatma darajasida nima sodir bo'lishini aytib bering. Norozi o'quvchilar uchun juda ko'p, qo'shdik misol maqolaning tegishli joyida.)

libbpf yordamida BPF dasturlarini yozish

Mashina kodlari yordamida BPF dasturlarini yozish birinchi marta qiziqarli bo'lishi mumkin, keyin esa to'yinganlik boshlanadi. Ayni paytda siz e'tiboringizni qaratishingiz kerak llvm, BPF arxitekturasi uchun kod yaratish uchun backend va kutubxonaga ega libbpf, bu sizga BPF ilovalarining foydalanuvchi tomonini yozish va yordamida yaratilgan BPF dasturlari kodini yuklash imkonini beradi. llvm/clang.

Aslida, biz ushbu va keyingi maqolalarda ko'rib turganimizdek, libbpf usiz juda ko'p ish qiladi (yoki shunga o'xshash vositalar - iproute2, libbcc, libbpf-gova hokazo) yashash mumkin emas. Loyihaning qotil xususiyatlaridan biri libbpf bu BPF CO-RE (Bir marta kompilyatsiya qiling, hamma joyda ishga tushiring) - turli API-larda ishlash qobiliyatiga ega (masalan, yadro tuzilishi versiyadan o'zgarganda) bir yadrodan boshqasiga ko'chma bo'lgan BPF dasturlarini yozish imkonini beruvchi loyiha. versiyaga). CO-RE bilan ishlash uchun sizning yadroingiz BTF qo'llab-quvvatlashi bilan kompilyatsiya qilinishi kerak (buni qanday qilishni biz bo'limda tasvirlab beramiz. Rivojlanish vositalari. Yadrongiz BTF bilan qurilganmi yoki oddiy emasligini quyidagi fayl mavjudligi bilan tekshirishingiz mumkin:

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

Ushbu fayl yadroda ishlatiladigan barcha ma'lumotlar turlari haqidagi ma'lumotlarni saqlaydi va bizning barcha misollarimizda foydalaniladi libbpf. Keyingi maqolada CO-RE haqida batafsil gaplashamiz, ammo bu maqolada - o'zingizga yadro yarating. CONFIG_DEBUG_INFO_BTF.

kutubxona libbpf to'g'ridan-to'g'ri katalogda yashaydi tools/lib/bpf yadro va uning rivojlanishi pochta ro'yxati orqali amalga oshiriladi [email protected]. Biroq, yadrodan tashqarida yashovchi ilovalar ehtiyojlari uchun alohida ombor saqlanadi https://github.com/libbpf/libbpf unda yadro kutubxonasi ko'proq yoki kamroq o'qishga kirish uchun aks ettirilgan.

Ushbu bo'limda biz foydalanadigan loyihani qanday yaratishingiz mumkinligini ko'rib chiqamiz libbpf, keling, bir nechta (ko'proq yoki kamroq ma'nosiz) test dasturlarini yozamiz va barchasi qanday ishlashini batafsil tahlil qilamiz. Bu bizga keyingi bo'limlarda BPF dasturlari xaritalar, yadro yordamchilari, BTF va boshqalar bilan qanday aloqada bo'lishini aniqroq tushuntirishga imkon beradi.

Odatda loyihalar yordamida libbpf git submodul sifatida GitHub omborini qo'shing, biz ham xuddi shunday qilamiz:

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

Borish libbpf juda oddiy:

$ 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

Ushbu bo'limdagi keyingi rejamiz quyidagicha: biz BPF dasturini yozamiz BPF_PROG_TYPE_XDP, oldingi misolda bo'lgani kabi, lekin C tilida biz uni ishlatib kompilyatsiya qilamiz clang, va uni yadroga yuklaydigan yordamchi dastur yozing. Keyingi bo'limlarda biz BPF dasturi va yordamchi dasturning imkoniyatlarini kengaytiramiz.

Misol: libbpf yordamida to'liq huquqli dastur yaratish

Boshlash uchun biz fayldan foydalanamiz /sys/kernel/btf/vmlinux, yuqorida aytib o'tilgan va uning ekvivalentini sarlavha fayli shaklida yarating:

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

Ushbu fayl yadromizda mavjud bo'lgan barcha ma'lumotlar tuzilmalarini saqlaydi, masalan, yadroda IPv4 sarlavhasi shunday aniqlanadi:

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

Endi biz BPF dasturimizni C tilida yozamiz:

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

Bizning dasturimiz juda oddiy bo'lib chiqqan bo'lsa-da, biz hali ham ko'p tafsilotlarga e'tibor qaratishimiz kerak. Birinchidan, biz kiritgan birinchi sarlavha fayli vmlinux.h, biz hozirgina foydalangan holda yaratdik bpftool btf dump - endi yadro tuzilmalari qanday ko'rinishini bilish uchun yadro sarlavhalari paketini o'rnatishimiz shart emas. Quyidagi sarlavha fayli bizga kutubxonadan keladi libbpf. Endi bizga faqat makroni aniqlash uchun kerak SEC, bu belgini ELF ob'ekt faylining tegishli bo'limiga yuboradi. Bizning dasturimiz bo'limda joylashgan xdp/simple, bu erda slashdan oldin biz BPF dastur turini aniqlaymiz - bu ishlatiladigan konventsiya libbpf, bo'lim nomiga asoslanib, u ishga tushirilganda to'g'ri turni almashtiradi bpf(2). BPF dasturining o'zi C - juda oddiy va bir qatordan iborat return XDP_PASS. Va nihoyat, alohida bo'lim "license" litsenziyaning nomini o'z ichiga oladi.

Biz o'z dasturimizni llvm/clang, >= 10.0.0 yoki undan kattaroq versiyadan foydalanib kompilyatsiya qilishimiz mumkin (bo'limga qarang). Rivojlanish vositalari):

$ 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

Qiziqarli xususiyatlar orasida: biz maqsadli arxitekturani ko'rsatamiz -target bpf va sarlavhalarga yo'l libbpf, biz yaqinda o'rnatdik. Bundan tashqari, unutmang -O2, bu variantsiz siz kelajakda kutilmagan hodisalarga duch kelishingiz mumkin. Keling, kodimizni ko'rib chiqaylik, biz xohlagan dasturni yoza oldikmi?

$ 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

Ha, ishladi! Endi bizda dastur bilan ikkilik fayl bor va biz uni yadroga yuklaydigan dastur yaratmoqchimiz. Shu maqsadda kutubxona libbpf bizga ikkita variantni taklif qiladi - past darajadagi API yoki yuqori darajadagi API dan foydalaning. Biz ikkinchi yo'lga boramiz, chunki biz BPF dasturlarini keyingi o'rganish uchun minimal kuch bilan yozish, yuklash va ulashni o'rganmoqchimiz.

Birinchidan, biz bir xil yordam dasturidan foydalanib, dasturimizning "skeletini" ikkilikdan yaratishimiz kerak bpftool - BPF dunyosining shveytsariyalik pichog'i (buni tom ma'noda qabul qilish mumkin, chunki BPF yaratuvchilari va qo'llab-quvvatlovchilaridan biri Daniel Borkman shveytsariyalik):

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

Fayl ichida xdp-simple.skel.h bizning dasturimizning ikkilik kodini va boshqarish funktsiyalarini o'z ichiga oladi - ob'ektimizni yuklash, biriktirish, o'chirish. Bizning oddiy holatimizda bu ortiqcha ish kabi ko'rinadi, lekin u ob'ekt faylida ko'plab BPF dasturlari va xaritalari mavjud bo'lganda ham ishlaydi va bu ulkan ELFni yuklash uchun biz shunchaki skelet yaratishimiz va maxsus dasturdan bir yoki ikkita funktsiyani chaqirishimiz kerak. yozmoqdalar Keling, endi davom etamiz.

To'g'ri aytganda, bizning yuklovchi dasturimiz ahamiyatsiz:

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

u struct xdp_simple_bpf faylda belgilangan xdp-simple.skel.h va bizning ob'ekt faylimizni tavsiflaydi:

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

Bu erda biz past darajadagi API izlarini ko'rishimiz mumkin: struktura struct bpf_program *simple и struct bpf_link *simple. Birinchi tuzilma bo'limda yozilgan dasturimizni maxsus tavsiflaydi xdp/simple, ikkinchisi esa dasturning voqea manbasiga qanday ulanishini tasvirlaydi.

vazifa xdp_simple_bpf__open_and_load, ELF ob'ektini ochadi, uni tahlil qiladi, barcha tuzilmalar va quyi tuzilmalarni yaratadi (dasturdan tashqari, ELF boshqa bo'limlarni ham o'z ichiga oladi - ma'lumotlar, faqat o'qish ma'lumotlari, disk raskadrovka ma'lumotlari, litsenziya va boshqalar), so'ngra uni tizim yordamida yadroga yuklaydi. qo'ng'iroq qiling bpf, biz dasturni kompilyatsiya qilish va ishga tushirish orqali tekshirishimiz mumkin:

$ 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

Keling, dasturimizdan foydalanishni ko'rib chiqaylik bpftool. Keling, uning identifikatorini topamiz:

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

va dump (biz buyruqning qisqartirilgan shaklidan foydalanamiz bpftool prog dump xlated):

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

Yangi narsa! Dastur bizning C manba faylimiz qismlarini chop etdi.Bu kutubxona tomonidan amalga oshirildi libbpf, ikkilik faylda disk raskadrovka qismini topdi, uni BTF ob'ektiga kompilyatsiya qildi va yadroga yukladi. BPF_BTF_LOAD, va keyin buyruq bilan dasturni yuklashda natijada olingan fayl deskriptorini ko'rsating BPG_PROG_LOAD.

Yadro yordamchilari

BPF dasturlari "tashqi" funktsiyalarni - yadro yordamchilarini ishlatishi mumkin. Ushbu yordamchi funktsiyalar BPF dasturlariga yadro tuzilmalariga kirishga, xaritalarni boshqarishga, shuningdek, "haqiqiy dunyo" bilan bog'lanishga imkon beradi - perf hodisalarini yaratish, apparatni boshqarish (masalan, paketlarni qayta yo'naltirish) va hokazo.

Misol: bpf_get_smp_processor_id

"Misol orqali o'rganish" paradigmasi doirasida yordamchi funktsiyalardan birini ko'rib chiqaylik, bpf_get_smp_processor_id(), aniq faylda kernel/bpf/helpers.c. U chaqirgan BPF dasturi ishlayotgan protsessor raqamini qaytaradi. Ammo biz uning semantikasi bilan unchalik qiziqmaymiz, chunki uni amalga oshirish bir qatorni oladi:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

BPF yordamchi funksiyasi taʼriflari Linux tizimi chaqiruv taʼriflariga oʻxshaydi. Bu erda, masalan, hech qanday argumentga ega bo'lmagan funksiya aniqlangan. (Makro yordamida aytaylik, uchta argumentni oladigan funksiya aniqlanadi BPF_CALL_3. Argumentlarning maksimal soni - beshta.) Biroq, bu ta'rifning faqat birinchi qismi. Ikkinchi qism - bu turdagi strukturani aniqlash struct bpf_func_proto, unda tekshiruvchi tushunadigan yordamchi funksiyaning tavsifi mavjud:

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

Yordamchi funktsiyalarni ro'yxatdan o'tkazish

Muayyan turdagi BPF dasturlari ushbu funktsiyadan foydalanishi uchun uni, masalan, tur uchun ro'yxatdan o'tkazishlari kerak BPF_PROG_TYPE_XDP funktsiya yadroda aniqlanadi xdp_func_proto, bu yordamchi funksiya identifikatoridan XDP ushbu funktsiyani qo'llab-quvvatlaydimi yoki yo'qligini aniqlaydi. Bizning funktsiyamiz qo'llab-quvvatlash:

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

Yangi BPF dastur turlari faylda "belgilangan" include/linux/bpf_types.h makro yordamida BPF_PROG_TYPE. Qo'shtirnoq ichida ta'riflangan, chunki bu mantiqiy ta'rifdir va C tili shartlarida beton konstruktsiyalarning butun majmuasining ta'rifi boshqa joylarda uchraydi. Xususan, faylda kernel/bpf/verifier.c fayldagi barcha ta'riflar bpf_types.h bir qator tuzilmalarni yaratish uchun ishlatiladi 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
};

Ya'ni, BPF dasturining har bir turi uchun turdagi ma'lumotlar strukturasiga ko'rsatgich aniqlanadi struct bpf_verifier_opsqiymati bilan ishga tushirilgan _name ## _verifier_ops, ya'ni, xdp_verifier_ops uchun xdp. Tuzilishi xdp_verifier_ops aniqlandi faylda net/core/filter.c quyida bayon qilinganidek:

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

Bu erda biz tanish funksiyamizni ko'ramiz xdp_func_proto, bu har safar muammoga duch kelganida tekshirgichni ishga tushiradi biroz BPF dasturi ichidagi funktsiyalar, qarang verifier.c.

Keling, faraziy BPF dasturi funksiyadan qanday foydalanishini ko'rib chiqaylik bpf_get_smp_processor_id. Buning uchun biz avvalgi bo'limimizdagi dasturni quyidagicha qayta yozamiz:

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

Ramz bpf_get_smp_processor_id aniqlandi в <bpf/bpf_helper_defs.h> kutubxonalar libbpf qanday

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

ya'ni, bpf_get_smp_processor_id qiymati 8 bo'lgan funktsiya ko'rsatkichi, bu erda 8 qiymat BPF_FUNC_get_smp_processor_id turi enum bpf_fun_id, bu faylda biz uchun belgilangan vmlinux.h (fayl bpf_helper_defs.h yadroda skript tomonidan yaratilgan, shuning uchun "sehrli" raqamlar yaxshi). Bu funktsiya argumentlarni qabul qilmaydi va turdagi qiymatni qaytaradi __u32. Biz uni dasturimizda ishga tushirganimizda, clang ko‘rsatma hosil qiladi BPF_CALL "to'g'ri turdagi" Keling, dasturni kompilyatsiya qilamiz va bo'limni ko'rib chiqamiz 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

Birinchi qatorda biz ko'rsatmalarni ko'ramiz call, parametr IMM bu 8 ga teng va SRC_REG - nol. Verifier tomonidan qo'llaniladigan ABI kelishuviga ko'ra, bu sakkizinchi raqamli yordamchi funktsiyaga qo'ng'iroqdir. U ishga tushirilgach, mantiq oddiy. Registrdan qiymatni qaytaring r0 ga ko'chirildi r1 va 2,3-satrlarda u tipga aylantiriladi u32 — yuqori 32 bit tozalanadi. 4,5,6,7 qatorlarda biz 2 ni qaytaramiz (XDP_PASS) yoki 1 (XDP_DROP) 0-satrdagi yordamchi funktsiya nol yoki nolga teng qiymatni qaytarganiga qarab.

Keling, o'zimizni sinab ko'raylik: dasturni yuklang va chiqishga qarang 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, tekshirgich to'g'ri yadro yordamchisini topdi.

Misol: argumentlarni topshirish va nihoyat dasturni ishga tushirish!

Ishlash darajasidagi barcha yordamchi funksiyalar prototipga ega

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

Yordamchi funksiyalarga parametrlar registrlarda uzatiladi r1-r5, va qiymat registrda qaytariladi r0. Beshdan ortiq argumentni talab qiladigan funksiyalar mavjud emas va kelajakda ularni qo'llab-quvvatlash qo'shilishi kutilmaydi.

Keling, yangi yadro yordamchisini va BPF parametrlarni qanday o'tkazishini ko'rib chiqaylik. Keling, qayta yozamiz xdp-simple.bpf.c quyidagicha (qolgan qatorlar o'zgarmagan):

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

Bizning dasturimiz u ishlayotgan protsessor raqamini chop etadi. Keling, uni kompilyatsiya qilamiz va kodni ko'rib chiqamiz:

$ 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

0-7 qatorlarda biz satrni yozamiz running on CPU%un, va keyin 8-qatorda biz tanishni ishga tushiramiz bpf_get_smp_processor_id. 9-12-qatorlarda yordamchi argumentlarni tayyorlaymiz bpf_printk - registrlar r1, r2, r3. Nega ular ikkita emas, uchtasi bor? Chunki bpf_printkbu makro o'ram haqiqiy yordamchining atrofida bpf_trace_printk, bu format satrining o'lchamini o'tkazishi kerak.

Keling, bir-ikki qator qo'shamiz xdp-simple.cdasturimiz interfeysga ulanishi uchun lo va haqiqatan ham boshlandi!

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

Bu erda biz funktsiyadan foydalanamiz bpf_set_link_xdp_fd, bu XDP tipidagi BPF dasturlarini tarmoq interfeyslariga ulaydi. Biz interfeys raqamini qattiq kodladik lo, bu har doim 1. Agar eski dastur biriktirilgan bo'lsa, avval uni ajratib olish uchun funksiyani ikki marta bajaramiz. E'tibor bering, endi bizga qiyinchilik kerak emas pause yoki cheksiz tsikl: bizning yuklovchi dasturimiz chiqadi, lekin BPF dasturi voqea manbasiga ulanganligi sababli o'chirilmaydi. Muvaffaqiyatli yuklab olish va ulanishdan so'ng, dastur kelgan har bir tarmoq paketi uchun ishga tushiriladi lo.

Keling, dasturni yuklab oling va interfeysni ko'rib chiqaylik 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

Biz yuklab olgan dasturda ID 669 mavjud va biz interfeysda bir xil identifikatorni ko'ramiz lo. Biz bir nechta paketlarni yuboramiz 127.0.0.1 (so'rov + javob):

$ ping -c1 localhost

va endi disk raskadrovka virtual faylining mazmunini ko'rib chiqamiz /sys/kernel/debug/tracing/trace_pipe, unda bpf_printk xabarlarini yozadi:

# 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

Ikkita paket ko'rindi lo va CPU0 da qayta ishlangan - bizning birinchi to'liq ma'nosiz BPF dasturimiz ishladi!

Shuni ta'kidlash joizki bpf_printk Bu disk raskadrovka fayliga yozishi bejiz emas: bu ishlab chiqarishda foydalanish uchun eng muvaffaqiyatli yordamchi emas, lekin bizning maqsadimiz oddiy narsani ko'rsatish edi.

BPF dasturlaridan xaritalarga kirish

Misol: BPF dasturidan xaritadan foydalanish

Oldingi bo'limlarda biz foydalanuvchi maydonidan xaritalarni qanday yaratish va ulardan foydalanishni o'rgangan edik, endi esa yadro qismini ko'rib chiqamiz. Keling, odatdagidek, misol bilan boshlaylik. Keling, dasturimizni qayta yozamiz xdp-simple.bpf.c quyida bayon qilinganidek:

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

Dasturning boshida biz xarita ta'rifini qo'shdik woo: Bu kabi qiymatlarni saqlaydigan 8 elementli massiv u64 (Cda biz bunday massivni aniqlaymiz u64 woo[8]). Bir dasturda "xdp/simple" biz joriy protsessor raqamini o'zgaruvchiga olamiz key va keyin yordamchi funksiyadan foydalaning bpf_map_lookup_element massivdagi mos yozuvga ko'rsatgich olamiz, biz uni bittaga oshiramiz. Rus tiliga tarjima qilingan: biz qaysi protsessor kelgan paketlarni qayta ishlaganligi haqidagi statistikani hisoblaymiz. Keling, dasturni ishga tushirishga harakat qilaylik:

$ 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

Keling, u bog'langanligini tekshirib ko'raylik lo va bir nechta paketlarni yuboring:

$ 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

Endi massiv tarkibiga qaraylik:

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

Deyarli barcha jarayonlar CPU7 da qayta ishlangan. Bu biz uchun muhim emas, asosiysi dastur ishlaydi va biz BPF dasturlari yordamida xaritalarga qanday kirishni tushunamiz. хелперов bpf_mp_*.

Mistik indeks

Shunday qilib, biz kabi qo'ng'iroqlar yordamida BPF dasturidan xaritaga kirishimiz mumkin

val = bpf_map_lookup_elem(&woo, &key);

yordamchi funksiya qayerga o'xshaydi

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

lekin biz ko'rsatgichdan o'tmoqdamiz &woo nomsiz tuzilishga struct { ... }...

Agar dastur assembleriga qarasak, qiymat ekanligini ko'ramiz &woo aslida aniqlanmagan (4-qator):

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

va ko'chirishlarda mavjud:

$ 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

Ammo allaqachon yuklangan dasturga qarasak, biz to'g'ri xaritaga ko'rsatgichni ko'ramiz (4-qator):

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

Shunday qilib, biz yuklovchi dasturimizni ishga tushirish vaqtida havola degan xulosaga kelishimiz mumkin &woo kutubxonasi bo'lgan narsa bilan almashtirildi libbpf. Avval biz chiqishni ko'rib chiqamiz 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

Biz buni ko'ramiz libbpf xaritasini yaratdi woo va keyin dasturimizni yuklab oldik simple. Keling, dasturni qanday yuklashimizni batafsil ko'rib chiqaylik:

  • qo'ng'iroq qiling xdp_simple_bpf__open_and_load fayldan xdp-simple.skel.h
  • qaysi sabab bo'ladi xdp_simple_bpf__load fayldan xdp-simple.skel.h
  • qaysi sabab bo'ladi bpf_object__load_skeleton fayldan libbpf/src/libbpf.c
  • qaysi sabab bo'ladi bpf_object__load_xattr dan libbpf/src/libbpf.c

Oxirgi funktsiya, boshqa narsalar qatorida, chaqiradi bpf_object__create_maps, mavjud xaritalarni yaratadi yoki ochadi, ularni fayl identifikatorlariga aylantiradi. (Bu erda biz ko'ramiz BPF_MAP_CREATE chiqishda strace.) Keyin funksiya chaqiriladi bpf_object__relocate U bizni qiziqtiradi, chunki biz ko'rganlarimizni eslaymiz woo ko'chirish jadvalida. Uni o'rganib, biz oxir-oqibat o'zimizni funktsiyada topamiz bpf_program__relocate, qaysi xaritalarni ko'chirish bilan shug'ullanadi:

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

Shunday qilib, biz ko'rsatmalarimizni qabul qilamiz

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

va undagi manba registrini bilan almashtiring BPF_PSEUDO_MAP_FD, va xaritamizning fayl deskriptoriga birinchi IMM va agar u teng bo'lsa, masalan, 0xdeadbeef, natijada biz ko'rsatma olamiz

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

Xarita ma'lumotlari ma'lum bir yuklangan BPF dasturiga shunday uzatiladi. Bunday holda, xarita yordamida yaratilishi mumkin BPF_MAP_CREATE, va ID yordamida ochiladi BPF_MAP_GET_FD_BY_ID.

Jami, foydalanilganda libbpf algoritmi quyidagicha:

  • kompilyatsiya paytida xaritalarga havolalar uchun ko'chirish jadvalida yozuvlar yaratiladi
  • libbpf ELF ob'ektlar kitobini ochadi, barcha ishlatilgan xaritalarni topadi va ular uchun fayl identifikatorlarini yaratadi
  • fayl deskriptorlari ko'rsatmalarning bir qismi sifatida yadroga yuklanadi LD64

O'zingiz tasavvur qilganingizdek, oldinda yana ko'p narsalar bor va biz yadroga qarashimiz kerak. Yaxshiyamki, bizda bir maslahat bor - biz ma'noni yozdik BPF_PSEUDO_MAP_FD manba registriga kiriting va biz uni dafn qilishimiz mumkin, bu bizni barcha azizlarning muqaddasligiga olib boradi - kernel/bpf/verifier.c, bu erda o'ziga xos nomga ega bo'lgan funksiya fayl deskriptorini tipdagi strukturaning manzili bilan almashtiradi 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;

(to'liq kodni topish mumkin aloqa). Shunday qilib, biz algoritmimizni kengaytirishimiz mumkin:

  • dasturni yuklashda verifier xaritadan to'g'ri foydalanishni tekshiradi va tegishli tuzilmaning manzilini yozadi struct bpf_map

ELF ikkilik faylini yuklab olayotganda libbpf Yana ko'p narsa bor, lekin biz buni boshqa maqolalarda muhokama qilamiz.

Dasturlar va xaritalarni libbpf holda yuklash

Va'da qilinganidek, xaritalardan foydalanadigan dasturni yordamisiz qanday yaratish va yuklashni bilmoqchi bo'lgan o'quvchilar uchun misol. libbpf. Bu siz bog'liqlik yarata olmaydigan muhitda ishlayotganingizda yoki har bir bitni saqlay olmaysiz yoki shunga o'xshash dastur yozsangiz foydali bo'lishi mumkin. ply, u tezda BPF ikkilik kodini yaratadi.

Mantiqqa amal qilishni osonlashtirish uchun biz ushbu maqsadlar uchun misolimizni qayta yozamiz xdp-simple. Ushbu misolda muhokama qilingan dasturning to'liq va biroz kengaytirilgan kodini bu erda topish mumkin mohiyat.

Ilovamizning mantig'i quyidagicha:

  • turlari xaritasini yarating BPF_MAP_TYPE_ARRAY buyrug'i yordamida BPF_MAP_CREATE,
  • ushbu xaritadan foydalanadigan dastur yaratish,
  • dasturni interfeysga ulang lo,

inson tiliga tarjima qilinadi

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

u map_create tizim chaqiruvi haqidagi birinchi misolda qilganimiz kabi xaritani yaratadi bpf - “yadro, iltimos, menga 8 ta elementdan iborat massiv shaklida yangi xarita yarating __u64 va menga fayl deskriptorini qaytarib bering":

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

Dasturni yuklash ham oson:

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

Qiyin qism prog_load bu bizning BPF dasturimizning tuzilmalar massivi sifatidagi ta'rifidir struct bpf_insn insns[]. Ammo biz C tilida mavjud bo'lgan dasturdan foydalanayotganimiz sababli, biz biroz aldashimiz mumkin:

$ 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

Hammasi bo'lib, biz kabi tuzilmalar shaklida 14 ta ko'rsatmalar yozishimiz kerak struct bpf_insn (maslahat: yuqoridan axlatni oling, ko'rsatmalar qismini qayta o'qing, oching linux/bpf.h и linux/bpf_common.h va aniqlashga harakat qiling struct bpf_insn insns[] o'z-o'zidan):

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

Buni o'zlari yozmaganlar uchun mashq - toping map_fd.

Dasturimizda yana bir oshkor qilinmagan qism qoldi - xdp_attach. Afsuski, XDP kabi dasturlarni tizim chaqiruvi yordamida ulab bo'lmaydi bpf. BPF va XDP ni yaratgan odamlar onlayn Linux hamjamiyatidan edilar, ya'ni ular o'zlariga eng tanishlaridan foydalanganlar (lekin normal odamlar) yadro bilan ishlash uchun interfeys: netlink rozetkalari, Shuningdek qarang RFC3549. Amalga oshirishning eng oddiy usuli xdp_attach dan kodni nusxalashtirmoqda libbpf, ya'ni fayldan netlink.c, biz buni qildik, uni biroz qisqartirdik:

Netlink soketlari dunyosiga xush kelibsiz

Netlink soket turini oching 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;
}

Biz ushbu rozetkadan o'qiymiz:

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

Va nihoyat, bizning funksiyamiz rozetkani ochadi va unga fayl identifikatorini o'z ichiga olgan maxsus xabar yuboradi:

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

Shunday qilib, hamma narsa sinovga tayyor:

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

Keling, dasturimiz ulanganmi yoki yo'qligini bilib olaylik 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

Keling, pinglarni yuboramiz va xaritaga qaraymiz:

$ 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

Huray, hammasi ishlaydi. Aytgancha, bizning xaritamiz yana bayt shaklida ko'rsatilganiga e'tibor bering. Bu farqli o'laroq, aslida tufaylidir libbpf Biz turdagi ma'lumotlarni (BTF) yuklamadik. Ammo keyingi safar bu haqda ko'proq gaplashamiz.

Rivojlanish vositalari

Ushbu bo'limda biz minimal BPF ishlab chiquvchi asboblar to'plamini ko'rib chiqamiz.

Umuman olganda, BPF dasturlarini ishlab chiqish uchun sizga maxsus hech narsa kerak emas - BPF har qanday munosib tarqatish yadrosida ishlaydi va dasturlar ushbu dastur yordamida tuziladi. clang, bu paketdan etkazib berilishi mumkin. Biroq, BPF ishlab chiqilayotganligi sababli, yadro va vositalar doimiy ravishda o'zgarib turadi, agar siz 2019 yildan boshlab eski uslublardan foydalangan holda BPF dasturlarini yozishni xohlamasangiz, unda siz kompilyatsiya qilishingiz kerak bo'ladi.

  • llvm/clang
  • pahole
  • uning yadrosi
  • bpftool

(Ma'lumot uchun, ushbu bo'lim va maqoladagi barcha misollar Debian 10 da ishga tushirilgan.)

llvm/clang

BPF LLVM bilan do'stona va yaqinda BPF uchun dasturlar gcc yordamida kompilyatsiya qilinishi mumkin bo'lsa-da, barcha joriy ishlanmalar LLVM uchun amalga oshiriladi. Shuning uchun, birinchi navbatda, biz joriy versiyani quramiz clang git dan:

$ 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
... много времени спустя
$

Endi biz hamma narsa to'g'ri kelganligini tekshirishimiz mumkin:

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

(Yig'ish bo'yicha ko'rsatmalar clang mendan olingan bpf_devel_QA.)

Biz hozirgina yaratgan dasturlarni o'rnatmaymiz, aksincha ularni qo'shamiz PATH, masalan:

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

(Buni qo'shish mumkin .bashrc yoki alohida faylga. Shaxsan men shunga o'xshash narsalarni qo'shaman ~/bin/activate-llvm.sh va kerak bo'lganda men buni qilaman . activate-llvm.sh.)

Pahole va BTF

Qulaylik pahole BTF formatida disk raskadrovka ma'lumotlarini yaratish uchun yadro yaratishda foydalaniladi. Biz ushbu maqolada BTF texnologiyasining tafsilotlari haqida batafsil ma'lumot bermaymiz, bundan tashqari, bu qulay va biz undan foydalanishni xohlaymiz. Shunday qilib, agar siz yadroni yaratmoqchi bo'lsangiz, avval uni yarating pahole (yo'q pahole variant bilan yadro qura olmaysiz 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

BPF bilan tajriba o'tkazish uchun yadrolar

BPF imkoniyatlarini o'rganayotganda, men o'z yadroimni yig'ishni xohlayman. Umuman olganda, bu shart emas, chunki siz tarqatish yadrosida BPF dasturlarini kompilyatsiya qilish va yuklash imkoniyatiga ega bo'lasiz, ammo o'z yadrosiga ega bo'lish sizga tarqatishda eng yaxshi oylarda paydo bo'ladigan so'nggi BPF xususiyatlaridan foydalanishga imkon beradi. , yoki, ba'zi disk raskadrovka vositalarida bo'lgani kabi, yaqin kelajakda umuman paketlanmaydi. Bundan tashqari, o'z yadrosi kod bilan tajriba o'tkazish muhimligini his qiladi.

Yadro yaratish uchun, birinchidan, yadroning o'zi, ikkinchidan, yadro konfiguratsiya fayli kerak. BPF bilan tajriba qilish uchun biz odatdagidan foydalanishimiz mumkin vanilya yadro yoki ishlab chiqish yadrolaridan biri. Tarixiy jihatdan, BPF rivojlanishi Linux tarmoq hamjamiyatida sodir bo'ladi va shuning uchun barcha o'zgarishlar ertami-kechmi Linux tarmog'ini ta'minlovchi Devid Miller orqali amalga oshiriladi. Ularning tabiatiga qarab - tahrirlar yoki yangi xususiyatlar - tarmoq o'zgarishlari ikkita yadrodan biriga kiradi - net yoki net-next. BPF uchun o'zgarishlar o'rtasida bir xil tarzda taqsimlanadi bpf и bpf-next, keyinchalik ular mos ravishda net va net-keyingi guruhlarga birlashtiriladi. Batafsil ma'lumot uchun qarang bpf_devel_QA и netdev-FAQ. Shunday qilib, o'zingizning didingiz va sinovdan o'tayotgan tizimning barqarorligi ehtiyojlariga qarab yadroni tanlang (*-next yadrolari sanab o'tilganlarning eng beqaroridir).

Yadro konfiguratsiya fayllarini qanday boshqarish haqida gapirish ushbu maqola doirasidan tashqarida - siz buni qanday qilishni allaqachon bilasiz deb taxmin qilinadi yoki o'rganishga tayyor o'z-o'zidan. Biroq, quyidagi ko'rsatmalar sizga ishlaydigan BPF-ni yoqish uchun etarli yoki kamroq bo'lishi kerak.

Yuqoridagi yadrolardan birini yuklab oling:

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

Minimal ishlaydigan yadro konfiguratsiyasini yarating:

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

Faylda BPF parametrlarini yoqing .config o'zingiz tanlagan (ehtimol CONFIG_BPF systemd uni ishlatganligi sababli allaqachon yoqilgan bo'ladi). Ushbu maqola uchun yadrodan foydalanilgan variantlar ro'yxati:

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

Keyin biz modullarni va yadroni osongina yig'ishimiz va o'rnatishimiz mumkin (darvoqe, siz yadroni yangi yig'ilgandan foydalanib yig'ishingiz mumkin. clangqo'shish orqali CC=clang):

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

va yangi yadro bilan qayta ishga tushiring (men buning uchun foydalanaman kexec paketdan 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

Maqolada eng ko'p ishlatiladigan yordamchi dastur yordamchi dastur bo'ladi bpftool, Linux yadrosining bir qismi sifatida taqdim etilgan. U BPF ishlab chiquvchilari tomonidan BPF ishlab chiquvchilari uchun yozilgan va yuritiladi va barcha turdagi BPF ob'ektlarini boshqarish uchun ishlatilishi mumkin - dasturlarni yuklash, xaritalarni yaratish va tahrirlash, BPF ekotizimining hayotini o'rganish va hokazo. Man sahifalari uchun manba kodlari ko'rinishidagi hujjatlarni topish mumkin yadroda yoki allaqachon tuzilgan, Tarmoq.

Ushbu yozish paytida bpftool faqat RHEL, Fedora va Ubuntu uchun tayyor bo'ladi (qarang, masalan, bu mavzu, qadoqlashning tugallanmagan hikoyasini aytadi bpftool Debian da). Ammo agar siz allaqachon yadrongizni yaratgan bo'lsangiz, unda quring bpftool pirog kabi oson:

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

$

(Bu yerga ${linux} - bu sizning yadro katalogingiz.) Ushbu buyruqlarni bajargandan so'ng bpftool katalogda to'planadi ${linux}/tools/bpf/bpftool va u yo'lga qo'shilishi mumkin (birinchi navbatda foydalanuvchiga root) yoki shunchaki nusxa oling /usr/local/sbin.

Yig'ish bpftool ikkinchisidan foydalanish yaxshidir clang, yuqorida ta'riflanganidek yig'ilgan va to'g'ri yig'ilganligini tekshiring - masalan, buyruq yordamida

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

Bu sizning yadrongizda qaysi BPF funksiyalari yoqilganligini ko'rsatadi.

Aytgancha, oldingi buyruq kabi ishga tushirilishi mumkin

# bpftool f p k

Bu paketdagi yordamchi dasturlarga o'xshashlik bilan amalga oshiriladi iproute2, qaerda, masalan, aytishimiz mumkin ip a s eth0 o'rniga ip addr show dev eth0.

xulosa

BPF yadro funksionalligini samarali o'lchash va o'z vaqtida o'zgartirish uchun burga kiyish imkonini beradi. Tizim UNIXning eng yaxshi an'analarida juda muvaffaqiyatli bo'ldi: yadroni qayta dasturlash imkonini beruvchi oddiy mexanizm ko'plab odamlar va tashkilotlarga tajriba o'tkazish imkonini berdi. Garchi tajribalar, shuningdek, BPF infratuzilmasini rivojlantirish tugallanmagan bo'lsa-da, tizim allaqachon ishonchli va eng muhimi, samarali biznes mantig'ini yaratishga imkon beruvchi barqaror ABIga ega.

Shuni ta'kidlashni istardimki, mening fikrimcha, texnologiya juda mashhur bo'ldi, chunki, bir tomondan, mumkin igrat (mashinaning arxitekturasini bir oqshomda ozmi-koʻpmi tushunish mumkin), ikkinchi tomondan, uning paydo boʻlishidan oldin (chiroyli) hal qilib boʻlmaydigan muammolarni hal qilish. Ushbu ikki komponent birgalikda odamlarni tajriba va orzu qilishga majbur qiladi, bu esa tobora ko'proq innovatsion echimlarning paydo bo'lishiga olib keladi.

Ushbu maqola, ayniqsa qisqa bo'lmasa-da, faqat BPF dunyosiga kirish bo'lib, "ilg'or" xususiyatlar va arxitekturaning muhim qismlarini tasvirlamaydi. Oldinga boradigan reja shunday: keyingi maqolada BPF dastur turlari haqida umumiy ma'lumot bo'ladi (5.8 yadrosida 30 ta dastur turi qo'llab-quvvatlanadi), so'ngra yadrolarni kuzatish dasturlari yordamida haqiqiy BPF ilovalarini qanday yozishni ko'rib chiqamiz. Misol tariqasida, BPF arxitekturasi bo'yicha chuqurroq kurs, so'ngra BPF tarmog'i va xavfsizlik ilovalari misollari uchun vaqt keldi.

Ushbu turkumdagi oldingi maqolalar

  1. Kichkintoylar uchun BPF, nol qism: klassik BPF

Havolalar

  1. BPF va XDP uchun qo'llanma — ciliumdan olingan BPF boʻyicha hujjatlar, aniqrogʻi BPF yaratuvchilari va taʼminotchilaridan biri Daniel Borkmandan. Bu birinchi jiddiy ta'riflardan biri bo'lib, boshqalardan farqi shundaki, Doniyor nima haqida yozayotganini aniq biladi va u erda hech qanday xato yo'q. Xususan, ushbu hujjat taniqli yordamchi dastur yordamida XDP va TC turdagi BPF dasturlari bilan qanday ishlashni tavsiflaydi. ip paketdan iproute2.

  2. Documentation/networking/filter.txt — klassik va keyin kengaytirilgan BPF uchun hujjatlar bilan asl fayl. Assambleya tili va texnik arxitektura tafsilotlarini o'rganmoqchi bo'lsangiz, yaxshi o'qing.

  3. Facebook-dan BPF haqida blog. U kamdan-kam hollarda yangilanadi, lekin Aleksey Starovoitov (eBPF muallifi) va Andriy Nakryiko - (xo'jayin) yozganidek. libbpf).

  4. Bpftool sirlari. Kventin Monnetdan bpftooldan foydalanishning misollari va sirlari bilan qiziqarli twitter to'plami.

  5. BPFga sho'ng'ish: o'qish materiallari ro'yxati. Kventin Monnetdan BPF hujjatlariga havolalarning ulkan (va hali ham saqlanib qolgan) ro'yxati.

Manba: www.habr.com

a Izoh qo'shish