Një hyrje e shkurtër në BPF dhe eBPF

Përshëndetje, Habr! Ju njoftojmë se jemi duke përgatitur një libër për botim”.Vëzhgueshmëria e Linux me BPF".

Një hyrje e shkurtër në BPF dhe eBPF
Meqenëse makina virtuale BPF vazhdon të zhvillohet dhe përdoret në mënyrë aktive në praktikë, ne kemi përkthyer për ju një artikull që përshkruan aftësitë e saj kryesore dhe gjendjen aktuale.

Vitet e fundit, mjetet dhe teknikat e programimit janë bërë gjithnjë e më të njohura për të kompensuar kufizimet e kernelit Linux në rastet kur kërkohet përpunim i paketave me performancë të lartë. Një nga teknikat më të njohura të këtij lloji quhet anashkalimi i kernelit (bypass kernel) dhe lejon, duke anashkaluar shtresën e rrjetit të kernelit, të kryejë të gjithë përpunimin e paketave nga hapësira e përdoruesit. Anashkalimi i kernelit përfshin gjithashtu kontrollin e kartës së rrjetit nga hapësirën e përdoruesit. Me fjalë të tjera, kur punojmë me një kartë rrjeti, ne mbështetemi te shoferi hapësirën e përdoruesit.

Duke transferuar kontrollin e plotë të kartës së rrjetit në një program të hapësirës së përdoruesit, ne zvogëlojmë ngarkesën e kernelit (ndërrimi i kontekstit, përpunimi i shtresës së rrjetit, ndërprerjet, etj.), gjë që është mjaft e rëndësishme kur funksionon me shpejtësi 10 Gb/s ose më shumë. Anashkalimi i kernelit plus një kombinim i veçorive të tjera (përpunimi në grup) dhe akordim i kujdesshëm i performancës (Kontabiliteti NUMA, Izolimi i CPU-së, etj.) korrespondojnë me bazat e përpunimit të rrjetit me performancë të lartë në hapësirën e përdoruesit. Ndoshta një shembull shembullor i kësaj qasjeje të re për përpunimin e paketave është Dpdk nga Intel (Kompleti i zhvillimit të planit të të dhënave), megjithëse ka mjete dhe teknika të tjera të njohura, duke përfshirë Cisco's VPP (Vector Packet Processing), Netmap dhe, natyrisht, Snabb.

Organizimi i ndërveprimeve të rrjetit në hapësirën e përdoruesit ka një sërë disavantazhesh:

  • Kerneli i OS është një shtresë abstraksioni për burimet e harduerit. Për shkak se programet e hapësirës së përdoruesve duhet të menaxhojnë drejtpërdrejt burimet e tyre, ata gjithashtu duhet të menaxhojnë harduerin e tyre. Kjo shpesh nënkupton që duhet të programoni drejtuesit tuaj.
  • Për shkak se ne po heqim dorë tërësisht nga hapësira e kernelit, ne po heqim dorë gjithashtu nga të gjithë funksionalitetin e rrjetit të ofruar nga kerneli. Programet e hapësirës së përdoruesit duhet të ri-zbatojnë veçoritë që mund të ofrohen tashmë nga kerneli ose sistemi operativ.
  • Programet funksionojnë në modalitetin sandbox, gjë që kufizon seriozisht ndërveprimin e tyre dhe i pengon ato të integrohen me pjesë të tjera të sistemit operativ.

Në thelb, gjatë rrjetëzimit në hapësirën e përdoruesit, përfitimet e performancës arrihen duke lëvizur përpunimin e paketave nga kerneli në hapësirën e përdoruesit. XDP bën pikërisht të kundërtën: lëviz programet e rrjetit nga hapësira e përdoruesit (filtrat, zgjidhësit, rutimi, etj.) në hapësirën e kernelit. XDP na lejon të kryejmë një funksion rrjeti sapo një paketë godet një ndërfaqe rrjeti dhe përpara se të fillojë të lëvizë lart në nënsistemin e rrjetit kernel. Si rezultat, shpejtësia e përpunimit të paketave rritet ndjeshëm. Megjithatë, si e lejon kerneli përdoruesin të ekzekutojë programet e tij në hapësirën e kernelit? Para se t'i përgjigjemi kësaj pyetjeje, le të shohim se çfarë është BPF.

BPF dhe eBPF

Pavarësisht emrit konfuz, BPF (Berkeley Packet Filtering) është, në fakt, një model i makinës virtuale. Kjo makinë virtuale u krijua fillimisht për të trajtuar filtrimin e paketave, prandaj emri.

Një nga mjetet më të famshme që përdorin BPF është tcpdump. Kur kapni paketat duke përdorur tcpdump përdoruesi mund të specifikojë një shprehje për të filtruar paketat. Vetëm paketat që përputhen me këtë shprehje do të kapen. Për shembull, shprehja "tcp dst port 80” i referohet të gjitha paketave TCP që mbërrijnë në portin 80. Përpiluesi mund ta shkurtojë këtë shprehje duke e kthyer atë në BPF bytecode.

$ sudo tcpdump -d "tcp dst port 80"
(000) ldh [12] (001) jeq #0x86dd jt 2 jf 6
(002) ldb [20] (003) jeq #0x6 jt 4 jf 15
(004) ldh [56] (005) jeq #0x50 jt 14 jf 15
(006) jeq #0x800 jt 7 jf 15
(007) ldb [23] (008) jeq #0x6 jt 9 jf 15
(009) ldh [20] (010) jset #0x1fff jt 15 jf 11
(011) ldxb 4*([14]&0xf)
(012) ldh [x + 16] (013) jeq #0x50 jt 14 jf 15
(014) ret #262144
(015) ret #0

Kjo është ajo që në thelb bën programi i mësipërm:

  • Udhëzim (000): Ngarkon paketën në kompensim 12, si një fjalë 16-bit, në akumulator. Offset 12 korrespondon me etertipin e paketës.
  • Udhëzimi (001): krahason vlerën në akumulator me 0x86dd, domethënë me vlerën etertipit për IPv6. Nëse rezultati është i vërtetë, atëherë numëruesi i programit shkon në instruksionin (002), dhe nëse jo, atëherë në (006).
  • Udhëzimi (006): krahason vlerën me 0x800 (vlera etertipit për IPv4). Nëse përgjigja është e vërtetë, atëherë programi shkon në (007), nëse jo, atëherë në (015).

Dhe kështu me radhë derisa programi i filtrimit të paketave të kthejë një rezultat. Kjo është zakonisht një boolean. Kthimi i një vlere jo zero (udhëzimi (014)) do të thotë që paketa u pranua, dhe kthimi i zeros (udhëzimi (015)) do të thotë që paketa nuk u pranua.

Makina virtuale BPF dhe bytekodi i saj u propozuan nga Steve McCann dhe Van Jacobson në fund të vitit 1992 kur u publikua punimi i tyre Filtri i paketave BSD: Arkitekturë e re për kapjen e paketave në nivelin e përdoruesit, kjo teknologji u prezantua për herë të parë në konferencën Usenix në dimrin e vitit 1993.

Për shkak se BPF është një makinë virtuale, ajo përcakton mjedisin në të cilin ekzekutohen programet. Përveç bytekodit, ai gjithashtu përcakton modelin e memories së grumbullit (udhëzimet e ngarkesës zbatohen në mënyrë implicite në grup), regjistrat (A dhe X; regjistrat e akumulatorit dhe indeksit), ruajtjen e kujtesës gërvishtëse dhe një numërues programi të nënkuptuar. Është interesante që bytekodi BPF u modelua sipas Motorola 6502 ISA. Siç kujton Steve McCann në të tijën raport plenar në Sharkfest '11, ai ishte i njohur me ndërtimin 6502 nga ditët e tij të shkollës së mesme duke programuar në Apple II, dhe kjo njohuri ndikoi në punën e tij në hartimin e bytekodit BPF.

Mbështetja BPF zbatohet në kernelin Linux në versionet v2.5 dhe më të larta, të shtuara kryesisht nga përpjekjet e Jay Schullist. Kodi BPF mbeti i pandryshuar deri në vitin 2011, kur Eric Dumaset ridizajnoi përkthyesin BPF për të ekzekutuar në modalitetin JIT (Burimi: JIT për filtrat e paketave). Pas kësaj, kerneli, në vend që të interpretonte bytekodin BPF, mund të konvertonte drejtpërdrejt programet BPF në arkitekturën e synuar: x86, ARM, MIPS, etj.

Më vonë, në vitin 2014, Alexey Starovoitov propozoi një mekanizëm të ri JIT për BPF. Në fakt, ky JIT i ri u bë një arkitekturë e re e bazuar në BPF dhe u quajt eBPF. Unë mendoj se të dy VM-të bashkëjetuan për disa kohë, por aktualisht filtrimi i paketave zbatohet bazuar në eBPF. Në fakt, në shumë shembuj të dokumentacionit modern, BPF kuptohet të jetë eBPF, dhe BPF klasik sot njihet si cBPF.

eBPF zgjeron makinën virtuale klasike BPF në disa mënyra:

  • Bazuar në arkitekturat moderne 64-bit. eBPF përdor regjistra 64-bitësh dhe rrit numrin e regjistrave të disponueshëm nga 2 (akumulator dhe X) në 10. eBPF gjithashtu ofron opkode shtesë (BPF_MOV, BPF_JNE, BPF_CALL...).
  • Shkëputur nga nënsistemi i shtresës së rrjetit. BPF ishte i lidhur me modelin e të dhënave të grupit. Meqenëse përdorej për filtrimin e paketave, kodi i tij ndodhej në nënsistemin që ofron komunikime në rrjet. Sidoqoftë, makina virtuale eBPF nuk është më e lidhur me modelin e të dhënave dhe mund të përdoret për çdo qëllim. Pra, tani programi eBPF mund të lidhet me tracepoint ose kprobe. Kjo hap rrugën për instrumentimin eBPF, analizën e performancës dhe shumë raste të tjera të përdorimit në kontekstin e nënsistemeve të tjera të kernelit. Tani kodi eBPF ndodhet në rrugën e vet: kernel/bpf.
  • Dyqane globale të të dhënave të quajtura Maps. Hartat janë depo me vlera kyçe që mundësojnë shkëmbimin e të dhënave ndërmjet hapësirës së përdoruesit dhe hapësirës së kernelit. eBPF ofron disa lloje hartash.
  • Funksionet dytësore. Në veçanti, për të rishkruar një paketë, llogaritni një shumë kontrolli ose klononi një paketë. Këto funksione funksionojnë brenda kernelit dhe nuk janë programe të hapësirës së përdoruesit. Ju gjithashtu mund të bëni thirrje sistemi nga programet eBPF.
  • Përfundoni telefonatat. Madhësia e programit në eBPF është e kufizuar në 4096 bajt. Veçoria e thirrjes së pasme lejon një program eBPF të transferojë kontrollin në një program të ri eBPF dhe kështu të anashkalojë këtë kufizim (deri në 32 programe mund të lidhen në këtë mënyrë).

eBPF: shembull

Ka disa shembuj për eBPF në burimet e kernelit Linux. Ato janë në dispozicion në mostrat/bpf/. Për të përpiluar këta shembuj, thjesht shkruani:

$ sudo make samples/bpf/

Unë nuk do të shkruaj një shembull të ri për eBPF vetë, por do të përdor një nga mostrat e disponueshme në mostrat/bpf/. Do të shikoj disa pjesë të kodit dhe do të shpjegoj se si funksionon. Si shembull, zgjodha programin tracex4.

Në përgjithësi, secili nga shembujt në mostrat/bpf/ përbëhet nga dy skedarë. Në këtë rast:

  • tracex4_kern.c, përmban kodin burim që do të ekzekutohet në kernel si bajtkod eBPF.
  • tracex4_user.c, përmban një program nga hapësira e përdoruesit.

Në këtë rast, ne duhet të përpilojmë tracex4_kern.c në bajtkodin eBPF. Aktualisht në gcc nuk ka asnjë backend për eBPF. Për fat të mirë, clang mund të nxjerrë bajtkodin eBPF. Makefile përdor clang për përpilim tracex4_kern.c te skedari i objektit.

E përmenda më lart se një nga veçoritë më interesante të eBPF janë hartat. tracex4_kern përcakton një hartë:

struct pair {
    u64 val;
    u64 ip;
};  

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(long),
    .value_size = sizeof(struct pair),
    .max_entries = 1000000,
};

BPF_MAP_TYPE_HASH është një nga llojet e shumta të kartave të ofruara nga eBPF. Në këtë rast, është thjesht një hash. Ju gjithashtu mund të keni vënë re një reklamë SEC("maps"). SEC është një makro që përdoret për të krijuar një seksion të ri të një skedari binar. Në fakt, në shembull tracex4_kern janë përcaktuar edhe dy seksione të tjera:

SEC("kprobe/kmem_cache_free")
int bpf_prog1(struct pt_regs *ctx)
{   
    long ptr = PT_REGS_PARM2(ctx);

    bpf_map_delete_elem(&my_map, &ptr); 
    return 0;
}
    
SEC("kretprobe/kmem_cache_alloc_node") 
int bpf_prog2(struct pt_regs *ctx)
{
    long ptr = PT_REGS_RC(ctx);
    long ip = 0;

    // получаем ip-адрес вызывающей стороны kmem_cache_alloc_node() 
    BPF_KRETPROBE_READ_RET_IP(ip, ctx);

    struct pair v = {
        .val = bpf_ktime_get_ns(),
        .ip = ip,
    };
    
    bpf_map_update_elem(&my_map, &ptr, &v, BPF_ANY);
    return 0;
}   

Këto dy funksione ju lejojnë të fshini një hyrje nga harta (kprobe/kmem_cache_free) dhe shtoni një hyrje të re në hartë (kretprobe/kmem_cache_alloc_node). Të gjithë emrat e funksioneve të shkruara me shkronja të mëdha korrespondojnë me makrot e përcaktuara në bpf_helpers.h.

Nëse hedh seksionet e skedarit të objektit, duhet të shoh që këto seksione të reja janë përcaktuar tashmë:

$ objdump -h tracex4_kern.o

tracex4_kern.o: file format elf64-little

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
3 maps 0000001c 0000000000000000 0000000000000000 00000148 2**2
CONTENTS, ALLOC, LOAD, DATA
4 license 00000004 0000000000000000 0000000000000000 00000164 2**0
CONTENTS, ALLOC, LOAD, DATA
5 version 00000004 0000000000000000 0000000000000000 00000168 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

Ka edhe tracex4_user.c, programi kryesor. Në thelb, ky program dëgjon ngjarje kmem_cache_alloc_node. Kur ndodh një ngjarje e tillë, ekzekutohet kodi përkatës eBPF. Kodi ruan atributin IP të objektit në një hartë dhe më pas objekti kalon përmes programit kryesor. Shembull:

$ sudo ./tracex4
obj 0xffff8d6430f60a00 is 2sec old was allocated at ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 is 23sec old was allocated at ip ffffffff98090e8f
obj 0xffff8d5f80161780 is 6sec old was allocated at ip ffffffff98090e8f

Si lidhen një program i hapësirës së përdoruesit dhe një program eBPF? Në inicializimin tracex4_user.c ngarkon një skedar objekti tracex4_kern.o duke përdorur funksionin load_bpf_file.

int main(int ac, char **argv)
{
    struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
    char filename[256];
    int i;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    if (setrlimit(RLIMIT_MEMLOCK, &r)) {
        perror("setrlimit(RLIMIT_MEMLOCK, RLIM_INFINITY)");
        return 1;
    }

    if (load_bpf_file(filename)) {
        printf("%s", bpf_log_buf);
        return 1;
    }

    for (i = 0; ; i++) {
        print_old_objects(map_fd[1]);
        sleep(1);
    }

    return 0;
}

Duke bërë load_bpf_file sondat e përcaktuara në skedarin eBPF i shtohen /sys/kernel/debug/tracing/kprobe_events. Tani ne dëgjojmë për këto ngjarje dhe programi ynë mund të bëjë diçka kur ato ndodhin.

$ sudo cat /sys/kernel/debug/tracing/kprobe_events
p:kprobes/kmem_cache_free kmem_cache_free
r:kprobes/kmem_cache_alloc_node kmem_cache_alloc_node

Të gjitha programet e tjera në mostër/bpf/ janë të strukturuara në mënyrë të ngjashme. Ata gjithmonë përmbajnë dy skedarë:

  • XXX_kern.c: programi eBPF.
  • XXX_user.c: programi kryesor.

Programi eBPF identifikon hartat dhe funksionet që lidhen me një seksion. Kur kerneli lëshon një ngjarje të një lloji të caktuar (për shembull, tracepoint), funksionet e lidhura ekzekutohen. Kartat sigurojnë komunikim ndërmjet programit të kernelit dhe programit të hapësirës së përdoruesit.

Përfundim

Ky artikull diskutoi BPF dhe eBPF në terma të përgjithshëm. E di që ka shumë informacione dhe burime rreth eBPF sot, kështu që unë do të rekomandoj disa burime të tjera për studime të mëtejshme

Unë rekomandoj të lexoni:

Burimi: www.habr.com

Shto një koment