ProHoster > Blog > İdarə > Kiçiklər üçün BPF, birinci hissə: uzadılmış BPF
Kiçiklər üçün BPF, birinci hissə: uzadılmış BPF
Başlanğıcda texnologiya var idi və onun adı BPF idi. Biz ona baxdıq əvvəlki, Bu seriyadakı Əhdi-Ətiq məqaləsi. 2013-cü ildə Aleksey Starovoitov və Daniel Borkmanın səyləri ilə onun müasir 64 bitlik maşınlar üçün optimallaşdırılmış təkmilləşdirilmiş versiyası hazırlanmış və Linux nüvəsinə daxil edilmişdir. Bu yeni texnologiya qısaca Daxili BPF adlanırdı, sonra Genişləndirilmiş BPF adlandırıldı və indi, bir neçə il sonra hamı onu sadəcə olaraq BPF adlandırır.
Təxminən desək, BPF sizə Linux nüvə məkanında ixtiyari istifadəçi tərəfindən təmin edilən kodu işlətməyə imkan verir və yeni arxitektura o qədər uğurlu oldu ki, onun bütün tətbiqlərini təsvir etmək üçün bizə daha onlarla məqalə lazım olacaq. (Aşağıdakı performans kodunda gördüyünüz kimi, tərtibatçıların yaxşı etmədiyi yeganə şey, layiqli bir loqo yaratmaq idi.)
Bu məqalədə BPF virtual maşınının strukturu, BPF ilə işləmək üçün nüvə interfeysləri, inkişaf alətləri, həmçinin mövcud imkanların qısa, çox qısa icmalı, yəni. BPF-nin praktik tətbiqlərini daha dərindən öyrənmək üçün gələcəkdə ehtiyac duyacağımız hər şey.
Məqalənin xülasəsi
BPF arxitekturasına giriş. Birincisi, biz BPF arxitekturasına quş baxışı ilə baxacağıq və əsas komponentləri təsvir edəcəyik.
Bpf sistem çağırışından istifadə edərək obyektlərin idarə edilməsi. Artıq mövcud olan sistem haqqında bir qədər anlayışla, nəhayət, xüsusi sistem çağırışından istifadə edərək istifadəçi məkanından obyektlərin necə yaradılması və manipulyasiya ediləcəyinə baxacağıq - bpf(2).
Пишем программы BPF с помощью libbpf. Əlbəttə ki, sistem çağırışından istifadə edərək proqramlar yaza bilərsiniz. Amma çətindir. Daha real ssenari üçün nüvə proqramçıları kitabxana hazırladılar libbpf. Biz sonrakı nümunələrdə istifadə edəcəyimiz əsas BPF tətbiqi skeletini yaradacağıq.
Kernel Köməkçiləri. Burada biz BPF proqramlarının kernel köməkçi funksiyalarına necə daxil ola biləcəyini öyrənəcəyik - bu alət xəritələrlə birlikdə klassik proqramla müqayisədə yeni BPF-nin imkanlarını əsaslı şəkildə genişləndirir.
BPF proqramlarından xəritələrə giriş. Bu nöqtəyə qədər xəritələrdən istifadə edən proqramları necə yarada biləcəyimizi dəqiq başa düşmək üçün kifayət qədər məlumat əldə edəcəyik. Və hətta böyük və qüdrətli yoxlayıcıya tez nəzər salaq.
İnkişaf vasitələri. Təcrübələr üçün tələb olunan utilitləri və nüvəni necə yığmaq barədə kömək bölməsi.
Nəticə. Məqalənin sonunda bura qədər oxuyanlar növbəti yazılarda həvəsləndirici sözlər və baş verəcəklərin qısa təsvirini tapacaqlar. Davamını gözləmək arzusu və ya imkanı olmayanlar üçün öz-özünə təhsil almaq üçün bir sıra bağlantıları da sadalayacağıq.
BPF Arxitekturasına giriş
BPF arxitekturasını nəzərdən keçirməyə başlamazdan əvvəl sonuncu dəfə (oh) müraciət edəcəyik klassik BPFRISC maşınlarının meydana gəlməsinə cavab olaraq hazırlanmış və effektiv paket filtrləmə problemini həll etmişdir. Arxitektura o qədər uğurlu oldu ki, XNUMX-cı illərdə Berkeley UNIX-də doğulduqdan sonra o, mövcud əməliyyat sistemlərinin əksəriyyətinə köçürüldü, çılğın iyirminci illərə qədər sağ qaldı və hələ də yeni tətbiqlər tapmaqdadır.
Yeni BPF 64 bitlik maşınların, bulud xidmətlərinin və SDN yaratmaq üçün alətlərə artan ehtiyacın hər yerdə olmasına cavab olaraq hazırlanmışdır (Stez-tezddəqiqləşdirilib nişləmə). Klassik BPF-nin təkmilləşdirilmiş əvəzedicisi kimi nüvə şəbəkəsi mühəndisləri tərəfindən hazırlanmış yeni BPF, sözün əsl mənasında, altı aydan sonra Linux sistemlərini izləmək kimi çətin tapşırıqda tətbiqlər tapdı və indi, ortaya çıxdıqdan altı il sonra, bizə sadəcə olaraq bütün növbəti məqaləyə ehtiyacımız olacaq. müxtəlif proqram növlərini sadalayın.
Gülməli şəkillər
Özündə, BPF təhlükəsizliyə xələl gətirmədən nüvə məkanında “ixtiyari” kodu işlətməyə imkan verən bir sandbox virtual maşınıdır. BPF proqramları istifadəçi məkanında yaradılır, nüvəyə yüklənir və bəzi hadisə mənbəyinə qoşulur. Hadisə, məsələn, bir paketin şəbəkə interfeysinə çatdırılması, bəzi nüvə funksiyasının işə salınması və s. ola bilər. Paket halında, BPF proqramı paketin məlumatlarına və metaməlumatlarına (proqramın növündən asılı olaraq oxumaq və bəlkə də yazmaq üçün) çıxış əldə edəcək; nüvə funksiyasını işə saldıqda, arqumentlər funksiya, o cümlədən nüvə yaddaşına göstəricilər və s.
Gəlin bu prosesə daha yaxından nəzər salaq. Başlamaq üçün, proqramları assemblerdə yazılmış klassik BPF-dən ilk fərq haqqında danışaq. Yeni versiyada proqramların yüksək səviyyəli dillərdə, ilk növbədə, əlbəttə ki, C dilində yazılması üçün arxitektura genişləndirilmişdir. Bunun üçün BPF arxitekturası üçün bayt kodu yaratmağa imkan verən llvm üçün backend hazırlanmışdır.
BPF arxitekturası qismən müasir maşınlarda səmərəli işləmək üçün nəzərdə tutulmuşdur. Bunun praktikada işləməsi üçün nüvəyə yükləndikdən sonra BPF bayt kodu JIT kompilyatoru adlanan komponentdən istifadə edərək yerli koda çevrilir (Just In Time). Sonra, xatırlayırsınızsa, klassik BPF-də proqram nüvəyə yükləndi və hadisə mənbəyinə atomik şəkildə - vahid sistem çağırışı kontekstində əlavə edildi. Yeni arxitekturada bu, iki mərhələdə baş verir - birincisi, kod sistem çağırışından istifadə edərək nüvəyə yüklənir. bpf(2)sonra isə proqramın növündən asılı olaraq dəyişən digər mexanizmlər vasitəsilə proqram hadisə mənbəyinə qoşulur.
Burada oxucunun sualı ola bilər: mümkün idimi? Belə kodun icra təhlükəsizliyinə necə təminat verilir? İcra təhlükəsizliyi bizə verifier adlı BPF proqramlarının yüklənməsi mərhələsi ilə təmin edilir (ingilis dilində bu mərhələ verifier adlanır və mən ingilis sözündən istifadə etməyə davam edəcəyəm):
Verifier proqramın nüvənin normal işini pozmamasını təmin edən statik analizatordur. Bu, yeri gəlmişkən, proqramın sistemin işinə mane ola bilməyəcəyi anlamına gəlmir - BPF proqramları, növündən asılı olaraq, nüvə yaddaşının bölmələrini oxuya və yenidən yaza, funksiyaların dəyərlərini qaytara, kəsə, əlavə edə, yenidən yaza bilər. və hətta şəbəkə paketlərini yönləndirir. Verifier zəmanət verir ki, BPF proqramının işlədilməsi kernelin qəzaya uğramayacağına və qaydalara uyğun olaraq yazma imkanı olan proqramın, məsələn, gedən paketin məlumatlarının paketdən kənar nüvə yaddaşının üzərinə yaza bilməyəcək. BPF-nin bütün digər komponentləri ilə tanış olduqdan sonra, müvafiq bölmədə doğrulayıcıya bir az daha ətraflı baxacağıq.
Beləliklə, indiyə qədər nə öyrəndik? İstifadəçi C dilində proqram yazır, sistem çağırışından istifadə edərək onu nüvəyə yükləyir bpf(2), burada o, yoxlayıcı tərəfindən yoxlanılır və yerli bayt koduna çevrilir. Sonra eyni və ya digər istifadəçi proqramı hadisə mənbəyinə qoşur və o icra etməyə başlayır. Yükləmə və əlaqəni ayırmaq bir neçə səbəbə görə lazımdır. Birincisi, doğrulayıcı işlətmək nisbətən bahalıdır və eyni proqramı bir neçə dəfə yükləməklə biz kompüter vaxtını itirmiş oluruq. İkincisi, proqramın tam olaraq necə bağlanması onun növündən asılıdır və bir il əvvəl hazırlanmış bir "universal" interfeys yeni proqram növləri üçün uyğun olmaya bilər. (Baxmayaraq ki, indi memarlıq daha yetkinləşir, bu interfeysi səviyyədə birləşdirmək ideyası var. libbpf.)
Diqqətli oxucu görə bilər ki, şəkillərlə hələ bitməmişik. Həqiqətən, yuxarıda göstərilənlərin hamısı BPF-nin klassik BPF ilə müqayisədə mənzərəni niyə əsaslı şəkildə dəyişdirdiyini izah etmir. Tətbiq sahəsini əhəmiyyətli dərəcədə genişləndirən iki yenilik, paylaşılan yaddaş və nüvə köməkçi funksiyalarından istifadə etmək imkanıdır. BPF-də paylaşılan yaddaş sözdə xəritələrdən - müəyyən bir API ilə paylaşılan məlumat strukturlarından istifadə etməklə həyata keçirilir. Yəqin ki, onlar bu adı alıblar, çünki ilk görünən xəritə növü hash cədvəli olub. Sonra massivlər meydana çıxdı, yerli (her CPU) hash cədvəlləri və yerli massivlər, axtarış ağacları, BPF proqramlarına göstəriciləri ehtiva edən xəritələr və daha çox. İndi bizim üçün maraqlı olan odur ki, BPF proqramları indi zənglər arasında vəziyyəti saxlamaq və onu digər proqramlarla və istifadəçi sahəsi ilə bölüşmək imkanına malikdir.
Xəritələrə sistem zəngi vasitəsilə istifadəçi proseslərindən daxil olur bpf(2), və köməkçi funksiyalardan istifadə edərək nüvədə işləyən BPF proqramlarından. Üstəlik, köməkçilər yalnız xəritələrlə işləmək üçün deyil, həm də digər nüvə imkanlarına daxil olmaq üçün mövcuddur. Məsələn, BPF proqramları paketləri digər interfeyslərə yönləndirmək, mükəmməl hadisələr yaratmaq, nüvə strukturlarına daxil olmaq və s. üçün köməkçi funksiyalardan istifadə edə bilər.
Xülasə, BPF ixtiyari, yəni yoxlayıcı tərəfindən sınaqdan keçirilmiş istifadəçi kodunu nüvə sahəsinə yükləmək imkanı verir. Bu kod zənglər arasında vəziyyəti saxlaya və istifadəçi sahəsi ilə məlumat mübadiləsi edə bilər, həmçinin bu tip proqramlar tərəfindən icazə verilən kernel alt sistemlərinə çıxışı var.
Bu, artıq kernel modullarının təmin etdiyi imkanlara bənzəyir, bununla müqayisədə BPF bəzi üstünlüklərə malikdir (əlbəttə ki, siz yalnız oxşar tətbiqləri müqayisə edə bilərsiniz, məsələn, sistem izləmə - BPF ilə ixtiyari bir sürücü yaza bilməzsiniz). Daha aşağı giriş həddini qeyd edə bilərsiniz (BPF-dən istifadə edən bəzi utilitlər istifadəçidən nüvə proqramlaşdırma bacarıqlarına və ya ümumiyyətlə proqramlaşdırma bacarıqlarına malik olmasını tələb etmir), iş vaxtının təhlükəsizliyini (yazarkən sistemi pozmayanlar üçün şərhlərdə əlinizi qaldırın) və ya sınaq modulları), atomiklik - modulları yenidən yükləyərkən fasilələr olur və BPF alt sistemi heç bir hadisənin qaçırılmamasını təmin edir (ədalətli olmaq üçün bu, bütün növ BPF proqramları üçün doğru deyil).
Bu cür imkanların olması BPF-ni nüvəni genişləndirmək üçün universal bir vasitə halına gətirir ki, bu da praktikada təsdiqlənir: BPF-yə getdikcə daha çox yeni proqram növləri əlavə olunur, getdikcə daha çox böyük şirkətlər BPF-ni 24×7 döyüş serverlərində istifadə edir, getdikcə daha çox. startaplar öz bizneslərini BPF-ə əsaslanan həllər üzərində qururlar. BPF hər yerdə istifadə olunur: DDoS hücumlarından qorunmaqda, SDN yaratmaqda (məsələn, kubernetlər üçün şəbəkələrin həyata keçirilməsində), əsas sistem izləmə aləti və statistika kollektoru kimi, müdaxilənin aşkarlanması sistemlərində və sandbox sistemlərində və s.
Məqalənin icmal hissəsini burada bitirək və virtual maşın və BPF ekosisteminə daha ətraflı baxaq.
Diqressiya: kommunal xidmətlər
Aşağıdakı bölmələrdəki nümunələri işlədə bilmək üçün sizə ən azı bir sıra kommunal proqramlar lazım ola bilər. llvm/clang bpf dəstəyi ilə və bpftool. Bölmədə İnkişaf Alətləri Utilitləri, eləcə də nüvənizi yığmaq üçün təlimatları oxuya bilərsiniz. Təqdimatımızın harmoniyasını pozmamaq üçün bu bölmə aşağıda yerləşdirilmişdir.
BPF Virtual Maşın Registrləri və Təlimat Sistemi
BPF-nin arxitekturası və əmr sistemi proqramların C dilində yazılacağını və nüvəyə yükləndikdən sonra yerli koda çevriləcəyini nəzərə alaraq hazırlanmışdır. Buna görə də registrlərin sayı və əmrlər dəsti müasir maşınların imkanlarının riyazi mənada kəsişməsinə nəzər salmaqla seçilmişdir. Bundan əlavə, proqramlara müxtəlif məhdudiyyətlər qoyuldu, məsələn, son vaxtlara qədər dövrələr və alt proqramlar yazmaq mümkün deyildi və təlimatların sayı 4096 ilə məhdudlaşdırıldı (indi imtiyazlı proqramlar bir milyona qədər təlimat yükləyə bilər).
BPF on bir istifadəçinin 64 bitlik registrinə malikdir r0-r10 və proqram sayğacı. Qeydiyyatdan keçin r10 çərçivə göstəricisini ehtiva edir və yalnız oxunur. Proqramların işləmə zamanı 512 baytlıq yığına və xəritələr şəklində qeyri-məhdud paylaşılan yaddaşa çıxışı var.
BPF proqramlarına proqram tipli kernel köməkçilərinin xüsusi dəstini və daha yaxınlarda müntəzəm funksiyaları işə salmağa icazə verilir. Hər çağırılan funksiya registrlərdə ötürülən beşə qədər arqument qəbul edə bilər r1-r5, və qaytarılan dəyər ötürülür r0. Funksiyadan qayıtdıqdan sonra registrlərin məzmununa zəmanət verilir r6-r9 Dəyişməyəcək.
Proqramın effektiv tərcüməsi üçün qeydiyyatdan keçir r0-r11 bütün dəstəklənən arxitekturalar üçün cari arxitekturanın ABI xüsusiyyətləri nəzərə alınmaqla real registrlərlə unikal şəkildə əlaqələndirilir. Məsələn, üçün x86_64 qeydiyyatdan keçir r1-r5, funksiya parametrlərini ötürmək üçün istifadə olunur, üzərində göstərilir rdi, rsi, rdx, rcx, r8, parametrləri funksiyalara ötürmək üçün istifadə olunur x86_64. Məsələn, soldakı kod sağdakı koda belə çevrilir:
Qeydiyyatdan keçin r0 proqramın icrasının nəticəsini qaytarmaq üçün də istifadə olunur və registrdə r1 proqram kontekst üçün göstərici ötürülür - proqramın növündən asılı olaraq bu, məsələn, struktur ola bilər. struct xdp_md (XDP üçün) və ya struktur struct __sk_buff (müxtəlif şəbəkə proqramları üçün) və ya struktur struct pt_regs (müxtəlif növ izləmə proqramları üçün) və s.
Beləliklə, bir sıra registrlər, nüvə köməkçiləri, yığın, kontekst göstəricisi və xəritələr şəklində paylaşılan yaddaşımız var idi. Bütün bunlar səfərdə mütləq lazım deyil, amma...
Təsviri davam etdirək və bu obyektlərlə işləmək üçün komanda sistemi haqqında danışaq. Hamısı (demək olar ki, hamısı) BPF təlimatları sabit 64 bitlik ölçüyə malikdir. 64 bitlik Big Endian maşınında bir təlimata baxsanız, görəcəksiniz
Burada Code - bu təlimatın kodlaşdırılmasıdır, Dst/Src müvafiq olaraq qəbuledicinin və mənbənin kodlaşdırmalarıdır, Off - 16 bitlik imzalı abzas və Imm bəzi təlimatlarda istifadə olunan 32 bitlik işarəli tam ədəddir (cBPF sabiti K ilə oxşar). Kodlaşdırma Code iki növdən birinə malikdir:
0, 1, 2, 3 təlimat sinifləri yaddaşla işləmək üçün əmrləri müəyyənləşdirir. Onlar çağırılır, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, müvafiq olaraq. Sinif 4, 7 (BPF_ALU, BPF_ALU64) ALU təlimatları toplusunu təşkil edir. Sinif 5, 6 (BPF_JMP, BPF_JMP32) atlama təlimatlarını ehtiva edir.
BPF təlimat sistemini öyrənmək üçün sonrakı plan belədir: bütün təlimatları və onların parametrlərini diqqətlə sadalamaq əvəzinə, bu bölmədə bir neçə nümunəyə baxacağıq və onlardan təlimatların əslində necə işlədiyi və necə işlədiyi aydın olacaq. BPF üçün hər hansı ikili faylı əl ilə sökün. Məqalənin sonrakı hissəsində materialı birləşdirmək üçün Verifier, JIT kompilyatoru, klassik BPF-nin tərcüməsi, həmçinin xəritələri öyrənərkən, funksiyaları çağırarkən və s. haqqında bölmələrdə fərdi təlimatlarla da görüşəcəyik.
Proqramı tərtib etdiyimiz bir nümunəyə baxaq readelf-example.c və nəticədə ikiliyə baxın. Orijinal məzmunu açıqlayacağıq readelf-example.c aşağıda, onun məntiqini ikili kodlardan bərpa etdikdən sonra:
Komanda kodları bərabərdir b7, 15, b7 и 95. Xatırladaq ki, ən az əhəmiyyətli üç bit təlimat sinfidir. Bizim vəziyyətimizdə bütün təlimatların dördüncü biti boşdur, ona görə də təlimat sinifləri müvafiq olaraq 7, 5, 7, 5-dir. BPF_ALU64, və 5-dir BPF_JMP. Hər iki sinif üçün təlimat formatı eynidir (yuxarıya bax) və proqramımızı bu şəkildə yenidən yaza bilərik (eyni zamanda qalan sütunları insan şəklində yenidən yazacağıq):
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
Əməliyyat b sinif ALU64 - Mi BPF_MOV. O, təyinat reyestrinə dəyər təyin edir. Bit quraşdırılıbsa s (mənbə), onda dəyər mənbə registrindən götürülür və əgər bizim vəziyyətimizdə olduğu kimi təyin olunmayıbsa, onda dəyər sahədən götürülür. Imm. Beləliklə, birinci və üçüncü təlimatlarda əməliyyatı yerinə yetiririk r0 = Imm. Bundan əlavə, JMP sinif 1 əməliyyatıdır BPF_JEQ (əgər bərabərdirsə, tullanmaq). Bizim vəziyyətimizdə, bitdən bəri S sıfırdır, mənbə registrinin qiymətini sahə ilə müqayisə edir Imm. Dəyərlər üst-üstə düşürsə, keçid baş verir PC + OffHara PC, həmişə olduğu kimi, növbəti təlimatın ünvanını ehtiva edir. Nəhayət, JMP Class 9 Əməliyyatı BPF_EXIT. Bu təlimat nüvəyə qayıdaraq proqramı dayandırır r0. Gəlin cədvəlimizə yeni sütun əlavə edək:
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
Bunu daha rahat formada yenidən yaza bilərik:
r0 = 1
if (r1 == 0) goto END
r0 = 2
END:
exit
Reyestrdə nə olduğunu xatırlasaq r1 proqram nüvədən və registrdən kontekstə göstərici ötürülür r0 dəyər kernelə qaytarılır, onda görə bilərik ki, əgər kontekstin göstəricisi sıfırdırsa, onda biz 1-i, əks halda isə - 2-ni qaytarırıq. Mənbəyə baxaraq haqlı olduğumuzu yoxlayaq:
Bəli, bu mənasız proqramdır, lakin o, sadəcə dörd sadə təlimata çevrilir.
İstisna nümunəsi: 16 baytlıq təlimat
Daha əvvəl qeyd etdik ki, bəzi təlimatlar 64 bitdən çox yer tutur. Bu, məsələn, təlimatlara aiddir lddw (Kod = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — sahələrdən qoşa sözü registrə yükləyin Imm. Fakt Imm ölçüsü 32, qoşa söz isə 64 bitdir, ona görə də bir 64 bitlik təlimatda 64 bitlik ani dəyərin registrə yüklənməsi işləməyəcək. Bunun üçün sahədə 64 bitlik dəyərin ikinci hissəsini saxlamaq üçün iki bitişik təlimat istifadə olunur Imm. Nümunə:
Biz göstərişlərlə yenidən görüşəcəyik lddw, köçürmələr və xəritələrlə işləmək haqqında danışarkən.
Misal: standart alətlərdən istifadə edərək BPF-nin sökülməsi
Beləliklə, biz BPF ikili kodları oxumağı öyrəndik və lazım gələrsə, istənilən təlimatı təhlil etməyə hazırıq. Bununla belə, praktikada standart alətlərdən istifadə edərək proqramları sökmək daha rahat və daha sürətli olduğunu söyləməyə dəyər, məsələn:
(Bu yarımbölmədə təsvir edilən bəzi təfərrüatları əvvəlcə buradan öyrəndim oruc tutmaq Aleksey Starovoitov BPF Blogu.)
BPF obyektləri - proqramlar və xəritələr - əmrlərdən istifadə edərək istifadəçi sahəsindən yaradılır BPF_PROG_LOAD и BPF_MAP_CREATE sistem çağırışı bpf(2), bunun tam olaraq necə baş verdiyi haqqında növbəti hissədə danışacağıq. Bu, nüvə məlumat strukturlarını və onların hər biri üçün yaradır refcount (referans sayı) birinə təyin edilir və obyektə işarə edən fayl deskriptoru istifadəçiyə qaytarılır. Tutacaq bağlandıqdan sonra refcount obyekt bir azaldılır, sıfıra çatdıqda isə obyekt məhv olur.
Proqram xəritələrdən istifadə edirsə, o zaman refcount bu xəritələr proqramı yüklədikdən sonra bir artır, yəni. onların fayl deskriptorları istifadəçi prosesindən bağlana bilər və hələ də refcount sıfır olmayacaq:
Proqramı uğurla yüklədikdən sonra biz onu adətən bir növ hadisə generatoruna əlavə edirik. Məsələn, daxil olan paketləri emal etmək və ya bəzilərinə qoşmaq üçün onu şəbəkə interfeysinə qoya bilərik tracepoint əsasda. Bu zaman istinad sayğacı da bir artacaq və yükləyici proqramda fayl deskriptorunu bağlaya biləcəyik.
İndi yükləyicini bağlasaq nə olar? Bu, hadisə generatorunun (qarmaq) növündən asılıdır. Bütün şəbəkə qarmaqları yükləyici tamamlandıqdan sonra mövcud olacaq, bunlar qlobal qarmaqlar adlananlardır. Və, məsələn, izləmə proqramları onları yaradan proses başa çatdıqdan sonra buraxılacaq (və buna görə də “yerlidən prosesə” yerli adlanır). Texniki olaraq, yerli qarmaqlar həmişə istifadəçi məkanında müvafiq fayl deskriptoruna malikdir və buna görə də proses bağlandıqda bağlanır, lakin qlobal qarmaqlar yoxdur. Aşağıdakı şəkildə, qırmızı xaçlardan istifadə edərək, yükləyici proqramının dayandırılmasının yerli və qlobal qarmaqlar vəziyyətində obyektlərin ömrünə necə təsir etdiyini göstərməyə çalışıram.
Niyə yerli və qlobal qarmaqlar arasında fərq var? Bəzi növ şəbəkə proqramlarını işə salmaq istifadəçi sahəsi olmadan məna kəsb edir, məsələn, DDoS qorunmasını təsəvvür edin - yükləyici qaydaları yazır və BPF proqramını şəbəkə interfeysinə qoşur, bundan sonra yükləyici gedib özünü öldürə bilər. Digər tərəfdən, on dəqiqə ərzində dizlərinizə yazdığınız bir ayıklama izi proqramını təsəvvür edin - bu, başa çatdıqdan sonra sistemdə heç bir zibil qalmamasını istərdiniz və yerli qarmaqlar bunu təmin edəcəkdir.
Digər tərəfdən, nüvədəki izləmə nöqtəsinə qoşulmaq və uzun illər ərzində statistika toplamaq istədiyinizi təsəvvür edin. Bu halda, siz istifadəçi hissəsini tamamlamaq və vaxtaşırı statistikaya qayıtmaq istərdiniz. bpf fayl sistemi bu imkanı təmin edir. Bu, BPF obyektlərinə istinad edən faylların yaradılmasına və bununla da onların həcmini artırmağa imkan verən yalnız yaddaşda olan psevdofayl sistemidir. refcount obyektlər. Bundan sonra yükləyici çıxa bilər və onun yaratdığı obyektlər canlı qalacaq.
BPF obyektlərinə istinad edən bpff-lərdə faylların yaradılması "pinning" adlanır (aşağıdakı ifadədə olduğu kimi: "proses BPF proqramını və ya xəritəsini bağlaya bilər"). BPF obyektləri üçün fayl obyektlərinin yaradılması təkcə yerli obyektlərin ömrünü uzatmaq üçün deyil, həm də qlobal obyektlərin istifadəyə yararlılığı baxımından məna kəsb edir - qlobal DDoS mühafizə proqramı ilə nümunəyə qayıdaraq, gəlib statistikaya baxmaq istəyirik. zaman-zaman.
BPF fayl sistemi adətən quraşdırılır /sys/fs/bpf, lakin o, yerli olaraq da quraşdırıla bilər, məsələn, bu kimi:
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
Fayl sistemi adları əmrdən istifadə etməklə yaradılır BPF_OBJ_PIN BPF sistem çağırışı. Nümunə etmək üçün bir proqramı götürək, onu tərtib edək, yükləyək və onu bağlayaq bpffs. Proqramımız faydalı heç nə etmir, biz sadəcə kodu təqdim edirik ki, nümunəni təkrarlayasınız:
İndi isə yardımçı proqramdan istifadə edərək proqramımızı yükləyək bpftool və müşayiət olunan sistem zənglərinə baxın bpf(2) (bəzi aidiyyatı olmayan xətlər strace çıxışından çıxarılıb):
Burada istifadə edərək proqramı yüklədik BPF_PROG_LOAD, nüvədən fayl deskriptoru aldı 3 və əmrdən istifadə etməklə BPF_OBJ_PIN bu fayl deskriptorunu fayl kimi bağladı "bpf-mountpoint/test". Bundan sonra bootloader proqramı bpftool işləməyi başa çatdırdı, lakin proqramı heç bir şəbəkə interfeysinə əlavə etməsək də, nüvədə qaldı:
$ 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
Fayl obyektini normal şəkildə silə bilərik unlink(2) və bundan sonra müvafiq proqram silinəcək:
$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory
Obyektlərin silinməsi
Obyektlərin silinməsindən danışarkən aydınlaşdırmaq lazımdır ki, proqramı çəngəldən (hadisə generatoru) ayırdıqdan sonra heç bir yeni hadisə onun işə salınmasına səbəb olmayacaq, lakin proqramın bütün cari nümunələri normal qaydada tamamlanacaq. .
BPF proqramlarının bəzi növləri proqramı tez bir zamanda əvəz etməyə imkan verir, yəni. ardıcıl atomluluğu təmin edir replace = detach old program, attach new program. Bu halda, proqramın köhnə versiyasının bütün aktiv nümunələri öz işini bitirəcək və yeni proqramdan yeni hadisə idarəçiləri yaradılacaq və burada "atomluq" heç bir hadisənin qaçırılmaması deməkdir.
Proqramların hadisə mənbələrinə əlavə edilməsi
Bu yazıda proqramların hadisə mənbələrinə qoşulmasını ayrıca təsvir etməyəcəyik, çünki bunu müəyyən bir proqram növü kontekstində araşdırmağın mənası var. Santimetr. misal aşağıda, XDP kimi proqramların necə bağlandığını göstəririk.
bpf Sistem Zəngindən istifadə edərək Obyektlərin Manipulyasiyası
BPF proqramları
Bütün BPF obyektləri sistem çağırışından istifadə edərək istifadəçi məkanından yaradılır və idarə olunur bpf, aşağıdakı prototipə malikdir:
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
Budur komanda cmd tip dəyərlərindən biridir enum bpf_cmd, attr — müəyyən bir proqram üçün parametrlər üçün göstərici və size — göstəriciyə görə obyekt ölçüsü, yəni. adətən bu sizeof(*attr). kernel 5.8-də sistem çağırışı bpf 34 müxtəlif əmrləri dəstəkləyir və müəyyənləşdirilməsiunion bpf_attr 200 sətir tutur. Ancaq bundan qorxmamalıyıq, çünki bir neçə məqalə ərzində əmrlər və parametrlərlə tanış olacağıq.
Komandadan başlayaq BPF_PROG_LOAD, BPF proqramlarını yaradan - BPF təlimatları toplusunu götürür və onu nüvəyə yükləyir. Yükləmə anında yoxlayıcı işə salınır, sonra JIT kompilyatoru və müvəffəqiyyətlə icra edildikdən sonra proqram faylı deskriptoru istifadəçiyə qaytarılır. Bundan sonra onun başına gələnləri əvvəlki hissədə gördük BPF obyektlərinin həyat dövrü haqqında.
İndi biz sadə bir BPF proqramını yükləyən xüsusi proqram yazacağıq, lakin əvvəlcə hansı proqramı yükləmək istədiyimizə qərar verməliyik - seçməliyik. Tipi və bu tip çərçivəsində yoxlayıcı testdən keçəcək proqram yazın. Ancaq prosesi çətinləşdirməmək üçün burada hazır bir həll var: kimi bir proqramı alacağıq BPF_PROG_TYPE_XDP, dəyəri qaytaracaq XDP_PASS (bütün paketləri atlayın). BPF assembler-də çox sadə görünür:
r0 = 2
exit
Qərar verdikdən sonra o yükləyəcəyik, bunu necə edəcəyimizi sizə deyə bilərik:
Proqramda maraqlı hadisələr massivin tərifindən başlayır insns - maşın kodunda BPF proqramımız. Bu halda, BPF proqramının hər bir təlimatı struktura yığılır bpf_insn. Birinci element insns göstərişlərə uyğundur r0 = 2, ikinci - exit.
Geri çəkilmək. Kernel maşın kodlarının yazılması və nüvə başlıq faylının istifadəsi üçün daha rahat makroları müəyyən edir tools/include/linux/filter.h yaza bildik
Lakin BPF proqramlarını yerli kodda yazmaq yalnız nüvədə testlər və BPF haqqında məqalələr yazmaq üçün lazım olduğundan, bu makroların olmaması tərtibatçının həyatını həqiqətən çətinləşdirmir.
BPF proqramını müəyyən etdikdən sonra onun nüvəyə yüklənməsinə keçirik. Bizim minimalist parametrlər dəstimiz attr proqram növü, təlimatların dəsti və sayı, tələb olunan lisenziya və ad daxildir "woo", yüklədikdən sonra proqramımızı sistemdə tapmaq üçün istifadə edirik. Proqram, söz verildiyi kimi, sistem çağırışı ilə sistemə yüklənir bpf.
Proqramın sonunda biz faydalı yükü simulyasiya edən sonsuz bir döngəyə düşürük. Onsuz, sistem çağırışının bizə qaytardığı fayl deskriptoru bağlandıqda proqram nüvə tərəfindən öldürüləcək. bpf, və biz bunu sistemdə görməyəcəyik.
Yaxşı, sınaq üçün hazırıq. Proqramı yığıb işə salaq stracehər şeyin lazım olduğu kimi işlədiyini yoxlamaq üçün:
Hər şey yaxşıdır, bpf(2) bizə qolu 3 qaytardı və sonsuz bir döngəyə girdik pause(). Proqramımızı sistemdə tapmağa çalışaq. Bunu etmək üçün başqa bir terminala gedəcəyik və köməkçi proqramdan istifadə edəcəyik bpftool:
Sistemdə yüklənmiş proqramın olduğunu görürük woo qlobal ID-si 390-dır və hazırda davam edir simple-prog proqrama işarə edən açıq fayl deskriptoru var (və əgər simple-prog sonra işi bitirəcək woo yox olacaq). Gözlənildiyi kimi, proqram woo BPF arxitekturasında ikili kodların 16 baytını - iki təlimatı alır, lakin yerli formada (x86_64) artıq 40 baytdır. Proqramımıza orijinal formada baxaq:
sürprizlər yoxdur. İndi JIT kompilyatoru tərəfindən yaradılan koda baxaq:
# 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
üçün çox təsirli deyil exit(2), amma insaf naminə desək, proqramımız çox sadədir və qeyri-trivial proqramlar üçün JIT kompilyatorunun əlavə etdiyi proloq və epiloq, əlbəttə ki, lazımdır.
Maps
BPF proqramları həm digər BPF proqramları, həm də istifadəçi məkanındakı proqramlar üçün əlçatan olan strukturlaşdırılmış yaddaş sahələrindən istifadə edə bilər. Bu obyektlər xəritələr adlanır və bu bölmədə sistem çağırışından istifadə edərək onları necə idarə edəcəyimizi göstərəcəyik bpf.
Dərhal deyək ki, xəritələrin imkanları yalnız ümumi yaddaşa daxil olmaq ilə məhdudlaşmır. Xüsusi təyinatlı xəritələr var, məsələn, BPF proqramlarına göstəricilər və ya şəbəkə interfeyslərinə göstəricilər, mükəmməl hadisələrlə işləmək üçün xəritələr və s. Oxucunu çaşdırmamaq üçün burada onlar haqqında danışmayacağıq. Bundan əlavə, biz sinxronizasiya məsələlərinə məhəl qoymuruq, çünki bu, nümunələrimiz üçün vacib deyil. Mövcud xəritə növlərinin tam siyahısını burada tapa bilərsiniz <linux/bpf.h>, və bu bölmədə tarixən birinci tip olan hash cədvəlini nümunə götürəcəyik BPF_MAP_TYPE_HASH.
C++-da hash cədvəli yaratsanız, deyəcəksiniz unordered_map<int,long> woo, rus dilində “Mənə masa lazımdır woo açarları tipli olan limitsiz ölçü int, və dəyərlər növüdür long" BPF hash cədvəlini yaratmaq üçün biz eyni şeyi etməliyik, istisna olmaqla, cədvəlin maksimum ölçüsünü təyin etməliyik və açarların və dəyərlərin növlərini təyin etmək əvəzinə, onların ölçülərini baytlarda göstərməliyik. . Xəritələr yaratmaq üçün əmrdən istifadə edin BPF_MAP_CREATE sistem çağırışı bpf. Xəritə yaradan az-çox minimal proqrama baxaq. BPF proqramlarını yükləyən əvvəlki proqramdan sonra bu sizə sadə görünməlidir:
Burada bir sıra parametrlər təyin edirik attr, biz deyirik ki, “Mənə açarlar və ölçü dəyərləri olan bir hash cədvəli lazımdır sizeof(int), mən maksimum dörd element qoya bilərəm." BPF xəritələrini yaradarkən, digər parametrləri təyin edə bilərsiniz, məsələn, proqramla nümunədə olduğu kimi, biz obyektin adını aşağıdakı kimi göstərdik. "woo".
Budur sistem çağırışı bpf(2) bizə təsviri xəritə nömrəsini qaytardı 3 və sonra proqram, gözlənildiyi kimi, sistem çağırışında əlavə təlimatları gözləyir pause(2).
İndi proqramımızı arxa plana göndərək və ya başqa bir terminal açaq və yardımçı proqramdan istifadə edərək obyektimizə baxaq bpftool (xəritəmizi adı ilə başqalarından fərqləndirə bilərik):
$ sudo bpftool map
...
114: hash name woo flags 0x0
key 4B value 4B max_entries 4 memlock 4096B
...
114 rəqəmi obyektimizin qlobal identifikatorudur. Sistemdəki istənilən proqram bu identifikatordan əmrdən istifadə edərək mövcud xəritəni aça bilər BPF_MAP_GET_FD_BY_ID sistem çağırışı bpf.
İndi hash cədvəlimizlə oynaya bilərik. Onun məzmununa nəzər salaq:
$ sudo bpftool map dump id 114
Found 0 elements
Boş. Gəlin buna dəyər verək hash[1] = 1:
$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0
Cədvələ yenidən baxaq:
$ sudo bpftool map dump id 114
key: 01 00 00 00 value: 01 00 00 00
Found 1 element
Yaşasın! Bir element əlavə edə bildik. Qeyd edək ki, bunu etmək üçün bayt səviyyəsində işləməliyik, çünki bptftool hash cədvəlindəki dəyərlərin hansı tipdə olduğunu bilmir. (Bu bilik BTF istifadə edərək ona ötürülə bilər, lakin indi daha çox.)
Bpftool elementləri tam olaraq necə oxuyur və əlavə edir? Başlıq altına nəzər salaq:
Əvvəlcə komandanı istifadə edərək xəritəni qlobal ID ilə açdıq BPF_MAP_GET_FD_BY_ID и bpf(2) bizə deskriptor 3-ü qaytardı.Daha sonra əmrdən istifadə etməklə BPF_MAP_GET_NEXT_KEY ötürərək cədvəldə birinci açarı tapdıq NULL "əvvəlki" açarın göstəricisi kimi. Əgər açarımız varsa, edə bilərik BPF_MAP_LOOKUP_ELEMgöstəriciyə dəyər qaytarır value. Növbəti addım, cari açara göstərici ötürməklə növbəti elementi tapmağa çalışacağıq, lakin cədvəlimizdə yalnız bir element və əmr var. BPF_MAP_GET_NEXT_KEY qayıdır ENOENT.
Yaxşı, gəlin dəyəri 1 açarı ilə dəyişək, tutaq ki, biznes məntiqimiz qeydiyyatdan keçməyi tələb edir hash[1] = 2:
Gözlənildiyi kimi, çox sadədir: əmr BPF_MAP_GET_FD_BY_ID xəritəmizi ID və əmrlə açır BPF_MAP_UPDATE_ELEM elementin üzərinə yazır.
Beləliklə, bir proqramdan hash cədvəli yaratdıqdan sonra onun məzmununu digər proqramdan oxuya və yaza bilərik. Qeyd edək ki, əgər biz bunu komanda xəttindən edə bilsək, sistemdəki hər hansı digər proqram bunu edə bilər. İstifadəçi məkanından xəritələrlə işləmək üçün yuxarıda təsvir edilən əmrlərə əlavə olaraq, Aşağıdakı:
BPF_MAP_LOOKUP_ELEM: açarla dəyəri tapın
BPF_MAP_UPDATE_ELEM: yeniləyin/dəyər yaradın
BPF_MAP_DELETE_ELEM: açarı çıxarın
BPF_MAP_GET_NEXT_KEY: növbəti (və ya birinci) açarı tapın
BPF_MAP_GET_NEXT_ID: bütün mövcud xəritələrdən keçməyə imkan verir, bu belə işləyir bpftool map
BPF_MAP_GET_FD_BY_ID: qlobal ID ilə mövcud xəritəni açın
BPF_MAP_LOOKUP_AND_DELETE_ELEM: obyektin dəyərini atomik olaraq yeniləyin və köhnəsini qaytarın
BPF_MAP_FREEZE: xəritəni istifadəçi sahəsindən dəyişməz etmək (bu əməliyyat geri qaytarıla bilməz)
BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: kütləvi əməliyyatlar. Misal üçün, BPF_MAP_LOOKUP_AND_DELETE_BATCH - bu, xəritədən bütün dəyərləri oxumaq və sıfırlamaq üçün yeganə etibarlı yoldur
Bu əmrlərin hamısı bütün xəritə növləri üçün işləmir, lakin ümumiyyətlə istifadəçi məkanından digər xəritə növləri ilə işləmək hash cədvəlləri ilə işləmək kimi görünür.
Sifariş naminə hash cədvəli təcrübələrimizi bitirək. Yadda saxlayın ki, biz dörd açarı ehtiva edə bilən bir cədvəl yaratdıq? Daha bir neçə element əlavə edək:
$ 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
$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long
Gözlənildiyi kimi, uğur qazana bilmədik. Gəlin xətaya daha ətraflı baxaq:
$ 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 +++
Hər şey qaydasındadır: gözlənildiyi kimi, komanda BPF_MAP_UPDATE_ELEM yeni, beşinci, açar yaratmağa çalışır, lakin çökür E2BIG.
Beləliklə, biz BPF proqramlarını yarada və yükləyə, həmçinin istifadəçi məkanından xəritələr yarada və idarə edə bilərik. İndi BPF proqramlarının xəritələrindən necə istifadə edə biləcəyimizə baxmaq məntiqlidir. Biz bu barədə maşın makro kodlarında çətin oxunan proqramların dilində danışa bilərdik, lakin əslində BPF proqramlarının əslində necə yazıldığını və saxlandığını göstərməyin vaxtı gəldi - istifadə edərək. libbpf.
(Aşağı səviyyəli nümunənin olmamasından narazı olan oxucular üçün: xəritələrdən istifadə edən proqramları və istifadə edərək yaradılan köməkçi funksiyaları ətraflı təhlil edəcəyik. libbpf və təlimat səviyyəsində nə baş verdiyini sizə xəbər verin. Narazı olan oxucular üçün çox, əlavə etdik misal məqalənin müvafiq yerində.)
libbpf istifadə edərək BPF proqramlarının yazılması
Maşın kodlarından istifadə edərək BPF proqramlarının yazılması yalnız ilk dəfə maraqlı ola bilər və sonra toxluq başlayır. Bu anda diqqətinizi yönəltməlisiniz llvm, BPF arxitekturası üçün kod yaratmaq üçün arxa plana, eləcə də kitabxanaya malikdir libbpf, bu, BPF tətbiqlərinin istifadəçi tərəfini yazmağa və istifadə edərək yaradılan BPF proqramlarının kodunu yükləməyə imkan verir. llvm/clang.
Əslində, bu və sonrakı məqalələrdə görəcəyimiz kimi, libbpf onsuz kifayət qədər çox iş görür (və ya oxşar alətlər - iproute2, libbcc, libbpf-govə s.) yaşamaq mümkün deyil. Layihənin öldürücü xüsusiyyətlərindən biri libbpf BPF CO-RE (Bir dəfə tərtib et, hər yerdə işlə) - müxtəlif API-lərdə işləmək imkanı ilə bir nüvədən digərinə daşınan BPF proqramlarını yazmağa imkan verən layihədir (məsələn, kernel strukturu versiyadan dəyişdikdə). versiyaya). CO-RE ilə işləyə bilmək üçün nüvəniz BTF dəstəyi ilə tərtib edilməlidir (bunun necə ediləcəyini bölmədə təsvir edirik. İnkişaf Alətləri. Nüvənizin BTF ilə qurulduğunu və ya çox sadə olmadığını aşağıdakı faylın olması ilə yoxlaya bilərsiniz:
Bu fayl nüvədə istifadə olunan bütün məlumat növləri haqqında məlumatları saxlayır və istifadə etdiyimiz bütün nümunələrdə istifadə olunur libbpf. Növbəti məqalədə CO-RE haqqında ətraflı danışacağıq, lakin bu məqalədə özünüzə bir nüvə qurmaq kifayətdir. CONFIG_DEBUG_INFO_BTF.
kitabxana libbpf kataloqda yaşayır tools/lib/bpf kernel və onun inkişafı poçt siyahısı vasitəsilə həyata keçirilir [email protected]. Bununla belə, nüvədən kənarda yaşayan tətbiqlərin ehtiyacları üçün ayrıca bir depo saxlanılır https://github.com/libbpf/libbpf nüvə kitabxanasının daha çox və ya daha az olduğu kimi oxumaq üçün əks olunduğu.
Bu bölmədə istifadə edən bir layihəni necə yarada biləcəyinizi nəzərdən keçirəcəyik libbpf, gəlin bir neçə (az-çox mənasız) test proqramı yazaq və hamısının necə işlədiyini ətraflı təhlil edək. Bu, BPF proqramlarının xəritələr, nüvə köməkçiləri, BTF və s. ilə necə qarşılıqlı əlaqədə olduğunu aşağıdakı bölmələrdə daha asan izah etməyə imkan verəcək.
Adətən layihələrdən istifadə edir libbpf git alt modulu kimi GitHub repozitoriyası əlavə edin, biz də eyni şeyi edəcəyik:
Bu bölmədə növbəti planımız belədir: kimi bir BPF proqramı yazacağıq BPF_PROG_TYPE_XDP, əvvəlki nümunədə olduğu kimi, lakin C dilində biz onu istifadə edərək tərtib edirik clang, və onu nüvəyə yükləyən köməkçi proqram yazın. Növbəti bölmələrdə biz həm BPF proqramının, həm də köməkçi proqramının imkanlarını genişləndirəcəyik.
Misal: libbpf istifadə edərək tam hüquqlu proqram yaratmaq
Başlamaq üçün fayldan istifadə edirik /sys/kernel/btf/vmlinuxyuxarıda qeyd olunan , və onun ekvivalentini başlıq faylı şəklində yaradın:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
Bu fayl nüvəmizdə mövcud olan bütün məlumat strukturlarını saxlayacaq, məsələn, nüvədə IPv4 başlığı belə müəyyən edilir:
Proqramımızın çox sadə olduğu ortaya çıxsa da, hələ də bir çox detallara diqqət yetirməliyik. Birincisi, daxil etdiyimiz ilk başlıq faylıdır vmlinux.h, biz indicə istifadə edərək yaratdıq bpftool btf dump - indi nüvə strukturlarının necə göründüyünü öyrənmək üçün kernel-headers paketini quraşdırmağa ehtiyacımız yoxdur. Aşağıdakı başlıq faylı bizə kitabxanadan gəlir libbpf. İndi bizə yalnız makronu təyin etmək lazımdır SEC, bu simvolu ELF obyekt faylının müvafiq bölməsinə göndərir. Proqramımız bölmədə yer alır xdp/simple, burada kəsikdən əvvəl proqram növünü BPF təyin edirik - bu, istifadə olunan konvensiyadır libbpf, bölmə adına əsaslanaraq başlanğıcda düzgün növü əvəz edəcək bpf(2). BPF proqramının özüdür C - çox sadə və bir sətirdən ibarətdir return XDP_PASS. Nəhayət, ayrı bir bölmə "license" lisenziyanın adını ehtiva edir.
Biz proqramımızı llvm/clang, >= 10.0.0 və ya daha yaxşısı, daha böyük versiyadan istifadə edərək tərtib edə bilərik (bölməyə bax). İnkişaf Alətləri):
Maraqlı xüsusiyyətlər arasında: hədəf arxitekturasını göstəririk -target bpf və başlıqlara gedən yol libbpf, bu yaxınlarda quraşdırdığımız. Həmçinin, haqqında unutmayın -O2, bu seçim olmadan gələcəkdə sürprizlərlə qarşılaşa bilərsiniz. Gəlin kodumuza baxaq, istədiyimiz proqramı yaza bildikmi?
Bəli, işlədi! İndi proqramla ikili faylımız var və onu nüvəyə yükləyən proqram yaratmaq istəyirik. Bu məqsədlə kitabxana libbpf bizə iki seçim təklif edir - aşağı səviyyəli API və ya daha yüksək səviyyəli API istifadə edin. Biz ikinci yolla gedəcəyik, çünki BPF proqramlarını sonrakı öyrənilməsi üçün minimum səylə yazmağı, yükləməyi və birləşdirməyi öyrənmək istəyirik.
Birincisi, eyni yardım proqramından istifadə edərək, proqramımızın ikili sistemindən "skeletini" yaratmalıyıq bpftool — BPF dünyasının İsveçrə bıçağı (bunu sözün əsl mənasında qəbul etmək olar, çünki BPF-nin yaradıcılarından və baxıcılarından biri olan Daniel Borkman isveçrəlidir):
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
Faylda xdp-simple.skel.h proqramımızın ikili kodunu və idarəetmə funksiyalarını ehtiva edir - obyektimizi yükləmək, əlavə etmək, silmək. Sadə vəziyyətimizdə bu, həddindən artıq yük kimi görünür, lakin obyekt faylında çoxlu BPF proqramları və xəritələr olduğu halda da işləyir və bu nəhəng ELF-i yükləmək üçün sadəcə skelet yaratmaq və xüsusi proqramdan bir və ya iki funksiyanı çağırmaq lazımdır. yazır, indi davam edək.
Düzünü desək, yükləyici proqramımız mənasızdır:
#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);
}
Burada struct xdp_simple_bpf faylda müəyyən edilir xdp-simple.skel.h və obyekt faylımızı təsvir edir:
Aşağı səviyyəli API-nin izlərini burada görə bilərik: struktur struct bpf_program *simple и struct bpf_link *simple. Birinci struktur bölmədə yazılmış proqramımızı xüsusi olaraq təsvir edir xdp/simple, ikincisi isə proqramın hadisə mənbəyinə necə qoşulduğunu təsvir edir.
Function xdp_simple_bpf__open_and_load, ELF obyektini açır, onu təhlil edir, bütün strukturları və alt strukturları yaradır (proqramdan başqa ELF-də digər bölmələr də var - verilənlər, yalnız oxunan məlumatlar, sazlama məlumatları, lisenziya və s.) və sonra sistemdən istifadə edərək onu nüvəyə yükləyir. zəng edin bpf, proqramı tərtib edib işlətməklə yoxlaya bilərik:
İndi istifadə etdiyimiz proqramı nəzərdən keçirək bpftool. Gəlin onun şəxsiyyət vəsiqəsini tapaq:
# 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)
və dump (biz komandanın qısaldılmış formasından istifadə edirik bpftool prog dump xlated):
# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
0: (b7) r0 = 2
1: (95) exit
Yeni bir şey! Proqram C mənbə faylımızın hissələrini çap etdi.Bunu kitabxana həyata keçirdi libbpf, binar sistemdə debug bölməsini tapmış, onu BTF obyektinə tərtib etmiş və istifadə edərək nüvəyə yükləmişdir. BPF_BTF_LOAD, sonra isə proqramı əmrlə yükləyərkən yaranan fayl deskriptorunu təyin edin BPG_PROG_LOAD.
Kernel Köməkçiləri
BPF proqramları "xarici" funksiyaları - nüvə köməkçilərini işlədə bilər. Bu köməkçi funksiyalar BPF proqramlarına nüvə strukturlarına daxil olmaq, xəritələri idarə etmək, həmçinin “real dünya” ilə əlaqə saxlamaq imkanı verir - mükəmməl hadisələr yaratmaq, aparatları idarə etmək (məsələn, paketləri yönləndirmək) və s.
Misal: bpf_get_smp_processor_id
“Nümunə ilə öyrənmə” paradiqması çərçivəsində köməkçi funksiyalardan birini nəzərdən keçirək, bpf_get_smp_processor_id(), müəyyən faylda kernel/bpf/helpers.c. Onu çağıran BPF proqramının işlədiyi prosessorun nömrəsini qaytarır. Lakin biz onun semantikası ilə o qədər də maraqlanmırıq, çünki onun həyata keçirilməsi bir xətt çəkir:
BPF köməkçi funksiyasının tərifləri Linux sistem çağırış təriflərinə bənzəyir. Burada, məsələn, heç bir arqumenti olmayan funksiya müəyyən edilir. (Məsələn, üç arqument götürən funksiya makrodan istifadə etməklə müəyyən edilir BPF_CALL_3. Arqumentlərin maksimum sayı beşdir.) Lakin bu, tərifin yalnız birinci hissəsidir. İkinci hissə tip strukturunu müəyyən etməkdir struct bpf_func_proto, yoxlayıcının başa düşdüyü köməkçi funksiyanın təsvirini ehtiva edir:
Müəyyən tipli BPF proqramlarının bu funksiyadan istifadə etməsi üçün onlar onu, məsələn, növ üçün qeydiyyatdan keçirməlidirlər BPF_PROG_TYPE_XDP nüvədə funksiya müəyyən edilir xdp_func_protoXDP-nin bu funksiyanı dəstəklədiyini və ya dəstəkləmədiyini köməkçi funksiya identifikatorundan müəyyən edir. Bizim funksiyamızdır dəstəkləyir:
Yeni BPF proqram növləri faylda "müəyyən edilmişdir" include/linux/bpf_types.h makrodan istifadə etməklə BPF_PROG_TYPE. Məntiqi tərif olduğu üçün dırnaqlarla müəyyən edilir və C dili terminlərində konkret konstruksiyaların bütün toplusunun tərifi başqa yerlərdə də olur. Xüsusilə, faylda kernel/bpf/verifier.c fayldan bütün təriflər bpf_types.h strukturlar massivi yaratmaq üçün istifadə olunur bpf_verifier_ops[]:
Yəni, BPF proqramının hər bir növü üçün növün məlumat strukturuna göstərici müəyyən edilir struct bpf_verifier_ops, dəyəri ilə başlatılmışdır _name ## _verifier_ops, yəni, xdp_verifier_ops uğrunda xdp. Struktur xdp_verifier_opstərəfindən müəyyənləşdirilmişdir faylda net/core/filter.c belədir:
Burada tanış funksiyamızı görürük xdp_func_proto, bu, hər dəfə problemlə qarşılaşdıqda doğrulayıcını işə salacaq bəziləri BPF proqramı daxilində funksiyalar, bax verifier.c.
Gəlin hipotetik BPF proqramının funksiyadan necə istifadə etdiyinə baxaq bpf_get_smp_processor_id. Bunun üçün əvvəlki bölməmizdəki proqramı aşağıdakı kimi yenidən yazırıq:
yəni, bpf_get_smp_processor_id dəyəri 8 olan funksiya göstəricisidir, burada 8 dəyərdir BPF_FUNC_get_smp_processor_id növü enum bpf_fun_id, faylda bizim üçün müəyyən edilmişdir vmlinux.h (fayl bpf_helper_defs.h nüvədə bir skript yaradılır, buna görə də "sehrli" nömrələr yaxşıdır). Bu funksiya heç bir arqument qəbul etmir və növün dəyərini qaytarır __u32. Proqramımızda işlətdiyimiz zaman, clang göstəriş yaradır BPF_CALL "düzgün növ" Proqramı tərtib edək və bölməyə baxaq xdp/simple:
Birinci sətirdə təlimatları görürük call, parametr IMM 8-ə bərabərdir və SRC_REG - sıfır. Doğrulayıcı tərəfindən istifadə edilən ABI razılaşmasına əsasən, bu, səkkiz nömrəli köməkçi funksiyaya zəngdir. O işə salındıqdan sonra məntiq sadədir. Reyestrdən dəyəri qaytarın r0 -ə köçürdü r1 2,3-cü sətirlərdə isə tipə çevrilir u32 — yuxarı 32 bit təmizlənir. 4,5,6,7-ci sətirlərdə 2 qaytarırıq (XDP_PASS) və ya 1 (XDP_DROP) 0-cı sətirdəki köməkçi funksiyanın sıfır və ya sıfırdan fərqli qiymət qaytarmasından asılı olaraq.
Gəlin özümüzü sınayaq: proqramı yükləyin və nəticəyə baxaq 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, doğrulayıcı düzgün kernel-köməkçini tapdı.
Nümunə: arqumentləri ötürmək və nəhayət proqramı işə salmaq!
Bütün run səviyyəli köməkçi funksiyaların prototipi var
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
Köməkçi funksiyalara parametrlər registrlərə ötürülür r1-r5, və dəyər registrdə qaytarılır r0. Beşdən çox arqument götürən funksiyalar yoxdur və gələcəkdə onlara dəstəyin əlavə edilməsi gözlənilmir.
Gəlin yeni nüvə köməkçisinə və BPF-nin parametrləri necə ötürməsinə nəzər salaq. Yenidən yazaq xdp-simple.bpf.c aşağıdakı kimi (sətirlərin qalan hissəsi dəyişməyib):
SEC("xdp/simple")
int simple(void *ctx)
{
bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
return XDP_PASS;
}
Proqramımız işlədiyi CPU-nun nömrəsini çap edir. Gəlin onu tərtib edək və koda baxaq:
0-7 sətirlərdə sətir yazırıq running on CPU%un, və sonra 8-ci sətirdə tanış olanı işə salırıq bpf_get_smp_processor_id. 9-12-ci sətirlərdə köməkçi arqumentlər hazırlayırıq bpf_printk - qeydiyyatdan keçir r1, r2, r3. Niyə ikisi yox, üçü var? Çünki bpf_printk - bu makro sarğıdır əsl köməkçi ətrafında bpf_trace_printk, format sətirinin ölçüsünü keçməsi lazımdır.
İndi bir neçə sətir əlavə edək xdp-simple.cproqramımızın interfeysə qoşulması üçün lo və həqiqətən başladı!
Burada funksiyadan istifadə edirik bpf_set_link_xdp_fd, XDP tipli BPF proqramlarını şəbəkə interfeyslərinə birləşdirən. İnterfeys nömrəsini kodlaşdırdıq lo, bu həmişə 1-dir. Biz funksiyanı iki dəfə işlədirik ki, əgər köhnə proqram əlavə olunubsa, əvvəlcə onu ayırmaq lazımdır. Diqqət yetirin ki, indi bizim çağırışa ehtiyacımız yoxdur pause və ya sonsuz döngə: yükləyici proqramımız çıxacaq, lakin hadisə mənbəyinə qoşulduğu üçün BPF proqramı öldürülməyəcək. Uğurlu yükləmə və əlaqədən sonra proqram hər gələn şəbəkə paketi üçün işə salınacaq lo.
Proqramı yükləyək və interfeysə baxaq 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
Yüklədiyimiz proqram ID 669-a malikdir və interfeysdə eyni ID-ni görürük lo. Bir neçə paket göndərəcəyik 127.0.0.1 (sorğu + cavab):
$ ping -c1 localhost
və indi debug virtual faylının məzmununa baxaq /sys/kernel/debug/tracing/trace_pipe, hansında bpf_printk mesajlarını yazır:
# 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
Üzərində iki paket göründü lo və CPU0-da işlənmişdir - ilk tam hüquqlu mənasız BPF proqramımız işləmişdir!
Bunu qeyd etməyə dəyər bpf_printk Sazlama faylına yazması boş yerə deyil: bu istehsalda istifadə üçün ən uğurlu köməkçi deyil, amma məqsədimiz sadə bir şey göstərmək idi.
BPF proqramlarından xəritələrə daxil olmaq
Misal: BPF proqramından xəritədən istifadə etmək
Əvvəlki bölmələrdə istifadəçi məkanından xəritələrin necə yaradılmasını və istifadəsini öyrəndik, indi isə nüvə hissəsinə baxaq. Həmişə olduğu kimi, bir nümunə ilə başlayaq. Proqramımızı yenidən yazaq xdp-simple.bpf.c belədir:
Proqramın əvvəlində xəritə tərifini əlavə etdik woo: Bu kimi dəyərləri saxlayan 8 elementli massivdir u64 (C dilində biz belə bir massivi təyin edərdik u64 woo[8]). Bir proqramda "xdp/simple" cari prosessor nömrəsini dəyişənə alırıq key və sonra köməkçi funksiyadan istifadə edin bpf_map_lookup_element massivdəki müvafiq girişə bir göstərici alırıq, onu bir artırırıq. Rus dilinə tərcümə: gələn paketləri hansı CPU-nun emal etdiyinə dair statistikanı hesablayırıq. Proqramı işə salmağa çalışaq:
Gəlin onun bağlı olduğunu yoxlayaq lo və bir neçə paket göndərin:
$ 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
Demək olar ki, bütün proseslər CPU7-də işlənmişdir. Bu bizim üçün vacib deyil, əsas odur ki, proqram işləyir və biz BPF proqramlarından xəritələrə necə daxil olmağı başa düşürük - istifadə edərək хелперов bpf_mp_*.
Mistik göstərici
Beləliklə, kimi zənglərdən istifadə edərək BPF proqramından xəritəyə daxil ola bilərik
$ 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
Ancaq artıq yüklənmiş proqrama baxsaq, düzgün xəritəyə bir göstərici görürük (sətir 4):
Beləliklə, belə bir nəticəyə gələ bilərik ki, yükləyici proqramımızı işə salarkən, link &woo kitabxanası olan bir şeylə əvəz olundu libbpf. Əvvəlcə çıxışa baxacağıq strace:
səbəb olur xdp_simple_bpf__load fayldan xdp-simple.skel.h
səbəb olur bpf_object__load_skeleton fayldan libbpf/src/libbpf.c
səbəb olur bpf_object__load_xattr haqqında libbpf/src/libbpf.c
Son funksiya, digər şeylər arasında, zəng edəcək bpf_object__create_maps, mövcud xəritələri yaradan və ya açan, onları fayl deskriptorlarına çevirən. (Gördüyümüz yer budur BPF_MAP_CREATE çıxışda strace.) Sonra funksiya çağırılır bpf_object__relocate və gördüklərimizi xatırladığımız üçün bizi maraqlandıran odur woo köçürmə cədvəlində. Onu araşdıraraq, nəticədə özümüzü funksiyada tapırıq bpf_program__relocate, hansı xəritələrin köçürülməsi ilə məşğul olur:
case RELO_LD64:
insn[0].src_reg = BPF_PSEUDO_MAP_FD;
insn[0].imm = obj->maps[relo->map_idx].fd;
break;
və içindəki mənbə registrini ilə əvəz edin BPF_PSEUDO_MAP_FD, və xəritəmizin fayl deskriptoruna ilk IMM və bərabərdirsə, məsələn, 0xdeadbeef, nəticədə biz təlimat alacağıq
18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll
Xəritə məlumatı xüsusi yüklənmiş BPF proqramına belə ötürülür. Bu halda, xəritə istifadə edərək yaradıla bilər BPF_MAP_CREATE, və istifadə edərək ID ilə açılır BPF_MAP_GET_FD_BY_ID.
Ümumi, istifadə edərkən libbpf alqoritm aşağıdakı kimidir:
tərtib zamanı xəritələrə keçidlər üçün yerdəyişmə cədvəlində qeydlər yaradılır
libbpf ELF obyekt kitabını açır, bütün istifadə olunan xəritələri tapır və onlar üçün fayl deskriptorları yaradır
fayl deskriptorları təlimatın bir hissəsi kimi nüvəyə yüklənir LD64
Təsəvvür edə bildiyiniz kimi, qarşıda daha çox şey var və biz özümüzə baxmalı olacağıq. Xoşbəxtlikdən, bir ipucumuz var - mənasını yazdıq BPF_PSEUDO_MAP_FD mənbə reyestrinə daxil edin və biz onu basdıra bilərik, bu da bizi bütün müqəddəslərin müqəddəsliyinə aparacaq - kernel/bpf/verifier.c, burada fərqli ada malik funksiya fayl deskriptorunu tipli strukturun ünvanı ilə əvəz edir struct bpf_map:
(tam kodu tapa bilərsiniz по ссылке). Beləliklə, alqoritmimizi genişləndirə bilərik:
Proqramı yükləyərkən yoxlayıcı xəritədən düzgün istifadəni yoxlayır və müvafiq strukturun ünvanını yazır struct bpf_map
ELF binar faylını yükləyərkən istifadə edərək libbpf Daha çox şey var, amma biz bunu digər məqalələrdə müzakirə edəcəyik.
Proqramların və xəritələrin libbpf olmadan yüklənməsi
Söz verdiyimiz kimi, xəritələrdən istifadə edən proqramı köməksiz necə yaratmağı və yükləməyi öyrənmək istəyən oxucular üçün bir nümunədir. libbpf. Bu, asılılıq qura bilməyəcəyiniz bir mühitdə işləyərkən və ya hər biti saxlaya bildiyiniz zaman və ya bu kimi bir proqram yazarkən faydalı ola bilər. ply, tez BPF ikili kodu yaradır.
Məntiqə əməl etməyi asanlaşdırmaq üçün nümunəmizi bu məqsədlər üçün yenidən yazacağıq xdp-simple. Bu nümunədə müzakirə olunan proqramın tam və bir qədər genişləndirilmiş kodunu burada tapa bilərsiniz əsas.
Tətbiqimizin məntiqi belədir:
tip xəritəsi yaradın BPF_MAP_TYPE_ARRAY əmrindən istifadə edərək BPF_MAP_CREATE,
bu xəritədən istifadə edən proqram yaradın,
proqramı interfeysə qoşun lo,
kimi insan dilinə tərcümə olunur
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);
}
Burada map_create sistem çağırışı ilə bağlı ilk nümunədə etdiyimiz kimi bir xəritə yaradır bpf - “kernel, xahiş edirəm mənə 8 elementdən ibarət massiv şəklində yeni xəritə yaradın __u64 və mənə fayl deskriptorunu geri verin":
Çətin hissə prog_load BPF proqramımızın strukturlar massivi kimi tərifidir struct bpf_insn insns[]. Ancaq biz C dilində olan bir proqramdan istifadə etdiyimiz üçün bir az aldada bilərik:
Ümumilikdə, kimi strukturlar şəklində 14 təlimat yazmalıyıq struct bpf_insn (məsləhət: yuxarıdan zibil götürün, təlimatlar bölməsini yenidən oxuyun, açın linux/bpf.h и linux/bpf_common.h və müəyyən etməyə çalışın struct bpf_insn insns[] öz başına):
Bunu özləri yazmayanlar üçün bir məşq - tapın map_fd.
Proqramımızda daha bir açıqlanmayan hissə qalıb - xdp_attach. Təəssüf ki, XDP kimi proqramlar sistem çağırışı ilə qoşula bilməz bpf. BPF və XDP-ni yaradan insanlar onlayn Linux icmasından idilər, yəni onlara ən tanış olanı istifadə etdilər (lakin bunu etmək üçün yox) normal insanlar) nüvə ilə qarşılıqlı əlaqə üçün interfeys: netlink yuvaları, həmçinin bax RFC3549. Həyata keçirməyin ən sadə yolu xdp_attach kodunu kopyalayır libbpf, yəni fayldan netlink.c, biz bunu bir az qısaldaraq etdik:
Netlink rozetkaları dünyasına xoş gəlmisiniz
Netlink yuvası növünü açın 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;
}
Bu yuvadan oxuyuruq:
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;
}
Nəhayət, rozetkanı açan və ona fayl deskriptoru olan xüsusi mesaj göndərən funksiyamız budur:
$ 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
Hörmətli, hər şey işləyir. Yeri gəlmişkən qeyd edək ki, xəritəmiz yenidən bayt şəklində göstərilir. Bu, fərqli olaraq olması ilə əlaqədardır libbpf tip məlumatını (BTF) yükləmədik. Ancaq bu barədə növbəti dəfə daha çox danışacağıq.
İnkişaf Alətləri
Bu bölmədə biz minimum BPF tərtibatçı alət dəstinə baxacağıq.
Ümumiyyətlə, BPF proqramlarını inkişaf etdirmək üçün xüsusi bir şeyə ehtiyacınız yoxdur - BPF hər hansı layiqli paylama nüvəsi üzərində işləyir və proqramlar aşağıdakı proqramlardan istifadə etməklə qurulur. clang, paketdən təmin edilə bilər. Bununla belə, BPF inkişaf mərhələsində olduğu üçün nüvə və alətlər daim dəyişir, əgər 2019-cu ildən köhnə metodlardan istifadə edərək BPF proqramları yazmaq istəmirsinizsə, onda siz tərtib etməli olacaqsınız.
llvm/clang
pahole
onun əsası
bpftool
(İstinad üçün, bu bölmə və məqalədəki bütün nümunələr Debian 10-da işlədilib.)
llvm/clang
BPF LLVM ilə dostdur və bu yaxınlarda BPF üçün proqramlar gcc istifadə edərək tərtib oluna bilsə də, bütün cari inkişaflar LLVM üçün həyata keçirilir. Buna görə də, ilk növbədə, hazırkı versiyanı quracağıq clang git-dən:
$ 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
... много времени спустя
$
İndi hər şeyin düzgün olub olmadığını yoxlaya bilərik:
Biz indicə qurduğumuz proqramları quraşdırmayacağıq, əksinə onları əlavə edəcəyik PATH, məsələn:
export PATH="`pwd`/bin:$PATH"
(Buna əlavə etmək olar .bashrc və ya ayrıca fayla. Şəxsən mən bu kimi şeyləri əlavə edirəm ~/bin/activate-llvm.sh və lazım olanda bunu edirəm . activate-llvm.sh.)
Pahole və BTF
Kommunal pahole BTF formatında sazlama məlumatı yaratmaq üçün nüvəni qurarkən istifadə olunur. BTF texnologiyasının təfərrüatları haqqında bu yazıda onun rahat olması və ondan istifadə etmək istədiyimizdən başqa, ətraflı məlumat verməyəcəyik. Beləliklə, nüvənizi qurmaq niyyətindəsinizsə, əvvəlcə qurun pahole (olmadan pahole seçimi ilə kernel qura bilməyəcəksiniz 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 ilə təcrübə aparmaq üçün ləpələr
BPF-nin imkanlarını araşdırarkən mən öz nüvəmi yığmaq istəyirəm. Bu, ümumiyyətlə, lazım deyil, çünki siz paylama kernelində BPF proqramlarını tərtib edə və yükləyə biləcəksiniz, lakin öz nüvənizin olması sizə ən son BPF xüsusiyyətlərindən istifadə etməyə imkan verir ki, bu da paylamada ən yaxşı halda aylar ərzində görünəcək. , və ya bəzi sazlama alətlərində olduğu kimi yaxın gələcəkdə paketlənməyəcək. Həmçinin, öz nüvəsi kodla sınaq keçirməyi vacib hiss edir.
Bir nüvə qurmaq üçün, birincisi, nüvənin özü, ikincisi, nüvənin konfiqurasiya faylı lazımdır. BPF ilə təcrübə etmək üçün adi üsuldan istifadə edə bilərik vanil kernel və ya inkişaf ləpələrindən biri. Tarixən, BPF inkişafı Linux şəbəkə icması daxilində baş verir və buna görə də bütün dəyişikliklər gec-tez Linux şəbəkəsinin təminatçısı David Millerdən keçir. Təbiətindən asılı olaraq - redaktələr və ya yeni funksiyalar - şəbəkə dəyişiklikləri iki nüvədən birinə düşür - net və ya net-next. BPF üçün dəyişikliklər arasında eyni şəkildə paylanır bpf и bpf-next, sonra müvafiq olaraq net və net-next-ə birləşdirilir. Ətraflı məlumat üçün bax bpf_devel_QA и netdev-FAQ. Beləliklə, zövqünüzə və sınaqdan keçirdiyiniz sistemin sabitlik ehtiyaclarına əsaslanaraq nüvəni seçin (*-next ləpələr sadalananlardan ən qeyri-sabitdir).
Kernel konfiqurasiya fayllarını necə idarə etmək barədə danışmaq bu məqalənin əhatə dairəsi xaricindədir - güman edilir ki, ya bunu necə edəcəyinizi bilirsiniz, ya da öyrənməyə hazırdır tək başına. Bununla belə, aşağıdakı təlimatlar sizə işlək BPF-i dəstəkləyən sistem vermək üçün kifayət qədər az və ya çox olmalıdır.
Yuxarıdakı ləpələrdən birini endirin:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next
Minimum işləyən nüvə konfiqurasiyası qurun:
$ cp /boot/config-`uname -r` .config
$ make localmodconfig
Faylda BPF seçimlərini aktivləşdirin .config öz seçiminizlə (çox güman ki CONFIG_BPF systemd istifadə etdiyi üçün artıq aktiv olacaq). Bu məqalə üçün istifadə olunan nüvədən seçimlərin siyahısı:
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
Sonra modulları və kerneli asanlıqla yığıb quraşdıra bilərik (yeri gəlmişkən, siz yeni yığılmış nüvədən istifadə edərək nüvəni yığa bilərsiniz. clangəlavə etməklə CC=clang):
$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install
və yeni nüvə ilə yenidən başladın (bunun üçün istifadə edirəm kexec paketdən 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
Məqalədə ən çox istifadə olunan yardım proqramı köməkçi proqram olacaqdır bpftool, Linux nüvəsinin bir hissəsi kimi təchiz edilmişdir. O, BPF tərtibatçıları üçün BPF tərtibatçıları tərəfindən yazılır və saxlanılır və bütün növ BPF obyektlərini idarə etmək üçün istifadə edilə bilər - proqramları yükləmək, xəritələr yaratmaq və redaktə etmək, BPF ekosisteminin həyatını araşdırmaq və s. İnsan səhifələri üçün mənbə kodları şəklində sənədlər tapıla bilər əsasda və ya artıq tərtib edilmiş, şəbəkədə.
Bu yazı zamanı bpftool yalnız RHEL, Fedora və Ubuntu üçün hazırdır (bax, məsələn, bu ip, qablaşdırmanın yarımçıq hekayəsindən bəhs edir bpftool Debian-da). Ancaq nüvənizi artıq qurmusunuzsa, onda qurun bpftool pasta kimi asan:
$ 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 ]
$
(Burada ${linux} - bu sizin nüvə kataloqunuzdur.) Bu əmrləri yerinə yetirdikdən sonra bpftool kataloqda toplanacaq ${linux}/tools/bpf/bpftool və yola əlavə edilə bilər (ilk növbədə istifadəçi üçün root) və ya sadəcə kopyalayın /usr/local/sbin.
Toplayın bpftool sonuncudan istifadə etmək daha yaxşıdır clang, yuxarıda göstərildiyi kimi yığılır və düzgün yığılıb- yığılmadığını yoxlayın - məsələn, əmrdən istifadə edərək
$ 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
...
nüvənizdə hansı BPF xüsusiyyətlərinin aktiv olduğunu göstərəcək.
Yeri gəlmişkən, əvvəlki əmr kimi işlədilə bilər
# bpftool f p k
Bu, paketdəki kommunallarla bənzətmə yolu ilə edilir iproute2, məsələn, deyə bilərik ip a s eth0 əvəzinə ip addr show dev eth0.
Nəticə
BPF, nüvənin funksionallığını effektiv şəkildə ölçmək və anında dəyişmək üçün birə çəkməyə imkan verir. Sistem UNIX-in ən yaxşı ənənələrində çox uğurlu oldu: ləpəni (yenidən) proqramlaşdırmağa imkan verən sadə mexanizm çoxlu sayda insan və təşkilata təcrübə keçirməyə imkan verdi. Təcrübələr, eləcə də BPF infrastrukturunun özünün inkişafı başa çatmaqdan çox uzaq olsa da, sistem artıq etibarlı və ən əsası effektiv biznes məntiqi qurmağa imkan verən sabit ABI-yə malikdir.
Qeyd etmək istərdim ki, mənim fikrimcə, texnologiya bu qədər populyarlaşdı, çünki bir tərəfdən bunu edə bilər oyun (bir maşının arxitekturasını bir axşama az-çox başa düşmək olar), digər tərəfdən isə üzə çıxmazdan əvvəl (gözəl) həlli mümkün olmayan məsələləri həll etmək. Bu iki komponent birlikdə insanları təcrübə və xəyal qurmağa məcbur edir ki, bu da getdikcə daha çox innovativ həllərin yaranmasına səbəb olur.
Bu məqalə, xüsusilə qısa olmasa da, yalnız BPF dünyasına girişdir və memarlığın "qabaqcıl" xüsusiyyətlərini və vacib hissələrini təsvir etmir. Gələcək plan belədir: növbəti məqalədə BPF proqram növlərinin icmalı olacaq (5.8 kerneldə dəstəklənən 30 proqram növü var), sonra biz nəhayət kernel izləmə proqramlarından istifadə edərək real BPF tətbiqlərinin necə yazılacağına baxacağıq. misal olaraq, BPF arxitekturasına dair daha dərin kursun, ardınca BPF şəbəkəsi və təhlükəsizlik proqramlarının nümunələrinin vaxtıdır.
BPF və XDP İstinad Bələdçisi — ciliumdan, daha doğrusu, BPF-nin yaradıcılarından və baxıcılarından biri olan Daniel Borkmandan BPF haqqında sənədlər. Bu, digərlərindən fərqlənən ilk ciddi təsvirlərdən biridir ki, Daniel nə haqqında yazdığını dəqiq bilir və orada heç bir səhv yoxdur. Xüsusilə, bu sənəd tanınmış yardım proqramından istifadə edərək XDP və TC tipli BPF proqramları ilə necə işləməyi təsvir edir. ip paketdən iproute2.
Documentation/şəbəkə/filter.txt — klassik və sonra genişləndirilmiş BPF üçün sənədləri olan orijinal fayl. Montaj dilini və texniki memarlıq detallarını araşdırmaq istəyirsinizsə yaxşı oxuyun.
Facebook-dan BPF haqqında blog. Nadir hallarda yenilənir, lakin Aleksey Starovoitov (eBPF-nin müəllifi) və Andrii Nakryiko - (baxıcı) orada yazdıqları kimi uyğundur. libbpf).
bpftool sirləri. Quentin Monnet-dən bpftool istifadəsinin nümunələri və sirləri ilə əyləncəli twitter mövzusu.