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.
Maqolaning qisqacha mazmuni
BPF arxitekturasiga kirish. Birinchidan, biz BPF arxitekturasini qushning nazari bilan ko'rib chiqamiz va asosiy tarkibiy qismlarni belgilaymiz.
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.
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):
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.
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:
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
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:
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.
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:
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:
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:
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:
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:
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.
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.
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:
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):
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 aniqlashunion 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:
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
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:
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:
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:
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:
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:
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:
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:
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
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:
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:
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:
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):
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?
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:
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:
# 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 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:
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:
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[]:
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_opsaniqlandi faylda net/core/filter.c quyida bayon qilinganidek:
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:
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:
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:
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_printk - bu 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!
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:
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:
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
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
$ 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):
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:
Biz buni ko'ramiz libbpf xaritasini yaratdi woo va keyin dasturimizni yuklab oldik simple. Keling, dasturni qanday yuklashimizni batafsil ko'rib chiqaylik:
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
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.
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:
(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":
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:
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):
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:
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
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:
(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.
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.
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.
Facebook-dan BPF haqida blog. U kamdan-kam hollarda yangilanadi, lekin Aleksey Starovoitov (eBPF muallifi) va Andriy Nakryiko - (xo'jayin) yozganidek. libbpf).
Bpftool sirlari. Kventin Monnetdan bpftooldan foydalanishning misollari va sirlari bilan qiziqarli twitter to'plami.