Utangulizi mfupi wa BPF na eBPF

Habari, Habr! Tungependa kukuarifu kwamba tunatayarisha kitabu kwa ajili ya kutolewa."Kuonekana kwa Linux na BPF".

Utangulizi mfupi wa BPF na eBPF
Kwa kuwa mashine pepe ya BPF inaendelea kubadilika na inatumika kikamilifu, tumetafsiri kwa ajili yako makala inayoelezea uwezo wake mkuu na hali ya sasa.

Katika miaka ya hivi karibuni, zana na mbinu za programu zimezidi kuwa maarufu ili kulipa fidia kwa mapungufu ya kernel ya Linux katika hali ambapo usindikaji wa pakiti ya juu ya utendaji inahitajika. Moja ya mbinu maarufu zaidi za aina hii inaitwa kernel bypass (kernel bypass) na inaruhusu, kupita safu ya mtandao ya kernel, kutekeleza usindikaji wote wa pakiti kutoka kwa nafasi ya mtumiaji. Kukwepa kernel pia kunahusisha kudhibiti kadi ya mtandao kutoka nafasi ya mtumiaji. Kwa maneno mengine, tunapofanya kazi na kadi ya mtandao, tunategemea dereva nafasi ya mtumiaji.

Kwa kuhamisha udhibiti kamili wa kadi ya mtandao kwenye programu ya nafasi ya mtumiaji, tunapunguza kernel ya juu (kubadilisha muktadha, usindikaji wa safu ya mtandao, usumbufu, nk), ambayo ni muhimu sana wakati wa kukimbia kwa kasi ya 10Gb / s au zaidi. Kernel bypass pamoja na mchanganyiko wa vipengele vingine (usindikaji wa kundi) na urekebishaji makini wa utendaji (NUMA uhasibu, Kutengwa kwa CPU, nk) yanahusiana na misingi ya usindikaji wa mtandao wa utendaji wa juu katika nafasi ya mtumiaji. Labda mfano wa mfano wa mbinu hii mpya ya usindikaji wa pakiti ni DPDK kutoka kwa Intel (Seti ya Kutengeneza Data ya Ndege), ingawa kuna zana na mbinu zingine zinazojulikana, pamoja na VPP ya Cisco (Uchakataji wa Pakiti ya Vector), Netmap na, kwa kweli, piga.

Kupanga mwingiliano wa mtandao katika nafasi ya mtumiaji kuna shida kadhaa:

  • Kiini cha OS ni safu ya uondoaji kwa rasilimali za vifaa. Kwa sababu programu za nafasi ya mtumiaji zinapaswa kudhibiti rasilimali zao moja kwa moja, pia wanapaswa kudhibiti maunzi yao wenyewe. Hii mara nyingi inamaanisha kulazimika kupanga viendeshaji vyako mwenyewe.
  • Kwa sababu tunaachana na nafasi ya kernel kabisa, pia tunaachana na utendakazi wote wa mtandao unaotolewa na kernel. Programu za nafasi ya mtumiaji lazima zitekeleze vipengele ambavyo tayari vinaweza kutolewa na kernel au mfumo wa uendeshaji.
  • Programu hufanya kazi katika hali ya kisanduku cha mchanga, ambayo inazuia mwingiliano wao na inazuia kuunganishwa na sehemu zingine za mfumo wa uendeshaji.

Kwa asili, wakati wa kuunganisha kwenye nafasi ya mtumiaji, faida za utendaji hupatikana kwa kuhamisha usindikaji wa pakiti kutoka kwa kernel hadi nafasi ya mtumiaji. XDP hufanya kinyume kabisa: huhamisha programu za mitandao kutoka kwa nafasi ya mtumiaji (vichungi, visuluhishi, uelekezaji, n.k.) hadi kwenye nafasi ya kernel. XDP huturuhusu kutekeleza utendakazi wa mtandao mara tu pakiti inapogonga kiolesura cha mtandao na kabla ya kuanza kusogea hadi kwenye mfumo mdogo wa mtandao wa kernel. Matokeo yake, kasi ya usindikaji wa pakiti huongezeka kwa kiasi kikubwa. Walakini, kernel huruhusuje mtumiaji kutekeleza programu zao kwenye nafasi ya kernel? Kabla ya kujibu swali hili, hebu tuangalie BPF ni nini.

BPF na eBPF

Licha ya jina la kutatanisha, BPF (Berkeley Packet Filtering) ni, kwa kweli, mfano wa mashine. Mashine hii ya mtandaoni iliundwa awali kushughulikia uchujaji wa pakiti, kwa hivyo jina.

Moja ya zana maarufu kutumia BPF ni tcpdump. Wakati wa kukamata pakiti kwa kutumia tcpdump mtumiaji anaweza kubainisha usemi wa kuchuja pakiti. Ni pakiti zinazolingana na usemi huu pekee ndizo zitanaswa. Kwa mfano, usemi β€œtcp dst port 80” inarejelea pakiti zote za TCP zinazowasili kwenye mlango wa 80. Kikusanyaji kinaweza kufupisha usemi huu kwa kuubadilisha kuwa 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

Hivi ndivyo programu hapo juu hufanya kimsingi:

  • Maagizo (000): Hupakia pakiti kwa kukabiliana na 12, kama neno la biti 16, kwenye kikusanyaji. Offset 12 inalingana na ethertype ya pakiti.
  • Maagizo (001): inalinganisha thamani katika kikusanyiko na 0x86dd, yaani, na thamani ya ethertype ya IPv6. Ikiwa matokeo ni kweli, basi kihesabu cha programu huenda kwa maagizo (002), na ikiwa sivyo, basi kwa (006).
  • Maagizo (006): inalinganisha thamani na 0x800 (thamani ya ethertype kwa IPv4). Ikiwa jibu ni kweli, basi programu huenda kwa (007), ikiwa sivyo, basi kwa (015).

Na kadhalika mpaka programu ya kuchuja pakiti inarudi matokeo. Hii ni kawaida Boolean. Kurejesha thamani isiyo ya sifuri (maagizo (014)) inamaanisha kuwa pakiti ilikubaliwa, na kurudisha thamani ya sifuri (maagizo (015)) inamaanisha kuwa pakiti haikukubaliwa.

Mashine pepe ya BPF na bytecode yake ilipendekezwa na Steve McCann na Van Jacobson mwishoni mwa 1992 wakati karatasi yao ilichapishwa. Kichujio cha Pakiti ya BSD: Usanifu Mpya wa Kukamata Pakiti ya Kiwango cha Mtumiaji, teknolojia hii iliwasilishwa kwa mara ya kwanza katika mkutano wa Usenix katika majira ya baridi ya 1993.

Kwa sababu BPF ni mashine pepe, inafafanua mazingira ambayo programu zinaendeshwa. Mbali na bytecode, pia inafafanua mfano wa kumbukumbu ya kundi (maelekezo ya mzigo yanatumiwa kwa ukamilifu kwa kundi), rejista (A na X; rejista za kikusanyiko na index), hifadhi ya kumbukumbu ya mwanzo, na counter counter ya programu isiyo wazi. Inafurahisha, bytecode ya BPF iliundwa baada ya Motorola 6502 ISA. Kama Steve McCann alikumbuka katika yake ripoti ya kikao akiwa Sharkfest '11, alikuwa anafahamu build 6502 kutoka kwa programu yake ya siku za shule ya upili kwenye Apple II, na ujuzi huu uliathiri kazi yake ya kubuni bytecode ya BPF.

Usaidizi wa BPF unatekelezwa katika kernel ya Linux katika matoleo ya v2.5 na ya juu zaidi, yaliyoongezwa hasa na juhudi za Jay Schullist. Msimbo wa BPF haujabadilika hadi 2011, wakati Eric Dumaset alipounda upya mkalimani wa BPF ili kufanya kazi katika hali ya JIT (Chanzo: JIT kwa vichungi vya pakiti) Baada ya hayo, kernel, badala ya kutafsiri BPF bytecode, inaweza kubadilisha moja kwa moja programu za BPF kuwa usanifu unaolengwa: x86, ARM, MIPS, nk.

Baadaye, mnamo 2014, Alexey Starovoitov alipendekeza utaratibu mpya wa JIT kwa BPF. Kwa kweli, JIT hii mpya ikawa usanifu mpya wa BPF na iliitwa eBPF. Nadhani VM zote mbili zilishirikiana kwa muda, lakini kwa sasa uchujaji wa pakiti unatekelezwa kulingana na eBPF. Kwa kweli, katika mifano mingi ya uhifadhi wa kisasa, BPF inaeleweka kuwa eBPF, na BPF ya kawaida leo inajulikana kama cBPF.

eBPF inapanua mashine ya kawaida ya BPF kwa njia kadhaa:

  • Kulingana na usanifu wa kisasa wa 64-bit. eBPF hutumia rejista za biti 64 na huongeza idadi ya rejista zinazopatikana kutoka 2 (kikusanyaji na X) hadi 10. eBPF pia hutoa opcode za ziada (BPF_MOV, BPF_JNE, BPF_CALL...).
  • Imetenganishwa na mfumo mdogo wa safu ya mtandao. BPF iliunganishwa kwenye muundo wa data ya kundi. Kwa kuwa ilitumika kwa kuchuja pakiti, msimbo wake ulikuwa katika mfumo mdogo ambao hutoa mawasiliano ya mtandao. Hata hivyo, mashine pepe ya eBPF haijaunganishwa tena na muundo wa data na inaweza kutumika kwa madhumuni yoyote. Kwa hivyo, sasa programu ya eBPF inaweza kuunganishwa kwa tracepoint au kprobe. Hii inafungua njia ya uwekaji ala wa eBPF, uchanganuzi wa utendakazi, na visa vingine vingi vya utumiaji katika muktadha wa mifumo mingine midogo ya kernel. Sasa nambari ya eBPF iko katika njia yake mwenyewe: kernel/bpf.
  • Duka za data za kimataifa zinazoitwa Ramani. Ramani ni hifadhi za thamani kuu zinazowezesha kubadilishana data kati ya nafasi ya mtumiaji na nafasi ya kernel. eBPF hutoa aina kadhaa za ramani.
  • Vitendo vya sekondari. Hasa, kuandika upya kifurushi, kuhesabu hundi, au kuiga kifurushi. Vitendaji hivi huendeshwa ndani ya kernel na sio programu za nafasi ya mtumiaji. Unaweza pia kupiga simu za mfumo kutoka kwa programu za eBPF.
  • Maliza simu. Ukubwa wa programu katika eBPF ni mdogo kwa baiti 4096. Kipengele cha simu ya mkia huruhusu programu ya eBPF kuhamisha udhibiti kwa programu mpya ya eBPF na hivyo kukwepa kizuizi hiki (hadi programu 32 zinaweza kuunganishwa kwa njia hii).

eBPF: mfano

Kuna mifano kadhaa ya eBPF kwenye vyanzo vya Linux kernel. Zinapatikana kwa samples/bpf/. Ili kukusanya mifano hii, ingiza tu:

$ sudo make samples/bpf/

Sitaandika mfano mpya kwa eBPF mwenyewe, lakini nitatumia moja ya sampuli zinazopatikana katika sampuli/bpf/. Nitaangalia baadhi ya sehemu za kanuni na kueleza jinsi inavyofanya kazi. Kama mfano, nilichagua programu tracex4.

Kwa ujumla, kila moja ya mifano katika sampuli/bpf/ ina faili mbili. Kwa kesi hii:

  • tracex4_kern.c, ina msimbo wa chanzo wa kutekelezwa kwenye kernel kama eBPF bytecode.
  • tracex4_user.c, ina programu kutoka kwa nafasi ya mtumiaji.

Katika kesi hii, tunahitaji kukusanya tracex4_kern.c kwa eBPF bytecode. Hivi sasa ndani gcc hakuna backend kwa eBPF. Kwa bahati nzuri, clang inaweza kutoa eBPF bytecode. Makefile hutumia clang kwa mkusanyiko tracex4_kern.c kwa faili ya kitu.

Nilitaja hapo juu kuwa moja ya sifa za kupendeza za eBPF ni ramani. tracex4_kern inafafanua ramani moja:

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 ni mojawapo ya aina nyingi za kadi zinazotolewa na eBPF. Katika kesi hii, ni hashi tu. Huenda pia umeona tangazo SEC("maps"). SEC ni jumla inayotumiwa kuunda sehemu mpya ya faili ya binary. Kweli, katika mfano tracex4_kern sehemu mbili zaidi zinafafanuliwa:

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

Kazi hizi mbili hukuruhusu kufuta ingizo kutoka kwa ramani (kprobe/kmem_cache_free) na ongeza ingizo jipya kwenye ramani (kretprobe/kmem_cache_alloc_node) Majina yote ya kazi yaliyoandikwa kwa herufi kubwa yanalingana na macros iliyofafanuliwa ndani bpf_helpers.h.

Ikiwa nitatupa sehemu za faili ya kitu, ninapaswa kuona kuwa sehemu hizi mpya tayari zimefafanuliwa:

$ 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

Kuna pia tracex4_user.c, programu kuu. Kimsingi, programu hii inasikiliza matukio kmem_cache_alloc_node. Wakati tukio kama hilo linatokea, nambari ya eBPF inayolingana inatekelezwa. Nambari huhifadhi sifa ya IP ya kitu kwenye ramani, na kitu hicho kinafungwa kupitia programu kuu. Mfano:

$ 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

Je, mpango wa nafasi ya mtumiaji na mpango wa eBPF unahusiana vipi? Juu ya uanzishaji tracex4_user.c hupakia faili ya kitu tracex4_kern.o kutumia kipengele 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;
}

Wakati wa kufanya load_bpf_file uchunguzi uliofafanuliwa katika faili ya eBPF huongezwa kwa /sys/kernel/debug/tracing/kprobe_events. Sasa tunasikiliza matukio haya na programu yetu inaweza kufanya kitu inapotokea.

$ 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

Programu zingine zote katika sampuli/bpf/ zimeundwa vivyo hivyo. Daima huwa na faili mbili:

  • XXX_kern.c: Mpango wa eBPF.
  • XXX_user.c: programu kuu.

Mpango wa eBPF hutambua ramani na utendakazi zinazohusiana na sehemu. Wakati kernel itatoa tukio la aina fulani (kwa mfano, tracepoint), kazi zilizofungwa zinatekelezwa. Kadi hutoa mawasiliano kati ya programu ya kernel na mpango wa nafasi ya mtumiaji.

Hitimisho

Nakala hii ilijadili BPF na eBPF kwa jumla. Najua kuna habari na nyenzo nyingi kuhusu eBPF leo, kwa hivyo nitapendekeza nyenzo chache zaidi kwa masomo zaidi.

Ninapendekeza kusoma:

Chanzo: mapenzi.com

Kuongeza maoni