Destpêkek Kurtî ya BPF û eBPF

Hey Habr! Em ji we re radigihînin ku em amadekariya derxistina pirtûkê dikin."Çavdêriya Linux bi BPF".

Destpêkek Kurtî ya BPF û eBPF
Gava ku makîneya virtual ya BPF pêşkeftina xwe didomîne û bi awayekî çalak di pratîkê de tê bikar anîn, me gotarek ji we re wergerandiye ku taybetmendiyên wê yên sereke û rewşa heyî vedibêje.

Di van salên dawî de, amûr û teknîkên bernamekirinê populerbûnek bidest xistin da ku di rewşên ku pêvajoyek pakêtê ya bi performansa bilind hewce ye de sînorên kernel Linux telafî bikin. Yek ji rêbazên herî populer ên bi vî rengî tê gotin derbasbûna bingehîn (dorpêça kernelê) û destûrê dide, ku ji qata torê ya kernelê derbikeve, ku hemî pêvajoyên pakêtê ji cîhê bikarhêner pêk bîne. Dûrxistina kernelê di heman demê de birêvebirina qerta torê ji cîhê bikarhêner. Bi gotinek din, dema ku bi qerta torê re dixebitin, em xwe dispêrin ajokar cîhê bikarhêner.

Bi veguheztina kontrola tam a qerta torê li bernameyek cîhê bikarhêner, em serkêşiya kernelê kêm dikin (veguheztinên naverokê, pêvajokirina qata torê, qutkirin, hwd.), ku ev yek pir girîng e dema ku bi leza 10 Gb / s an bilindtir. Derbaskirina kernelê û tevliheviya taybetmendiyên din (pêvajoya batch) û ahenga performansa baldar (hesabkirina NUMA, tecrîda CPU, û hwd.) bingehên tora cîhê bikarhêner-performansa bilind bicîh dikin. Dibe ku mînakek nimûne ya vê nêzîkatiya nû ya ji bo pêvajoya pakêtê ye DPDK ji Intel (Kit Development Plane Data), her çend amûr û teknîkên din ên naskirî hene, di nav de VPP ji Cisco (Pêvajoya Pakêta Vektor), Netmap û, bê guman, snab.

Rêxistinkirina danûstendinên torê di cîhê bikarhêner de çend kêmasiyên xwe hene:

  • Kernelek OS ji bo çavkaniyên hardware qatek abstraksiyonê ye. Ji ber ku bernameyên cîhê bikarhêner neçar in ku çavkaniyên xwe rasterast birêve bibin, ew jî neçar in ku hardware xwe bi rêve bibin. Ev pir caran tê wateya bernamekirina ajokarên xwe.
  • Ji ber ku em bi tevahî dev ji cîhê kernelê berdidin, em dev ji hemî fonksiyona torê ya ku ji hêla kernel ve hatî peyda kirin jî berdidin. Bernameyên cîhê bikarhêner neçar in ku taybetmendiyên ku jixwe ji hêla kernel an pergala xebitandinê ve hatine peyda kirin ji nû ve bicîh bikin.
  • Bername di moda sandboxê de dixebitin, ku bi giranî danûstendina wan sînordar dike û pêşî li yekbûna wan bi beşên din ên pergala xebitandinê digire.

Di eslê xwe de, dema ku di cîhê bikarhêner de torê tê girêdan, destkeftiyên performansê bi veguhastina pêvajoya pakêtê ji kernel berbi cîhê bikarhêner ve têne bidestxistin. XDP tam berevajiyê wê dike: ew bernameyên torê ji cîhê bikarhêner (fîlter, veguhêz, rêkirin, hwd.) ber bi qada kernelê ve diherike. XDP dihêle ku em fonksiyona torê gava ku pakêt têkeve navrûya torê û berî ku ew dest bi rêwîtiya berbi bine-pergala torê ya kernelê bike, fonksiyona torê pêk bînin. Wekî encamek, leza hilberandina pakêtê bi girîngî zêde dibe. Lêbelê, kernel çawa dihêle bikarhêner bernameyên xwe li cîhê kernelê bimeşîne? Berî ku em bersiva vê pirsê bidin, em binihêrin ka BPF çi ye.

BPF û eBPF

Tevî navê bi tevahî ne zelal, BPF (Parzkirina pakêtê, Berkeley) bi rastî, modelek makîneya virtual e. Ev makîneya virtual bi eslê xwe ji bo birêvebirina fîlterkirina pakêtê hate çêkirin, ji ber vê yekê navê.

Yek ji wan amûrên herî naskirî ku BPF bikar tîne ev e tcpdump. Dema girtina pakêtan bi tcpdump Bikarhêner dikare ji bo fîlterkirina pakêtê îfadeyek diyar bike. Tenê pakêtên ku bi vê gotinê re hevaheng in dê werin girtin. Mînak gotina "tcp dst port 80” tê wateya hemî pakêtên TCP yên ku digihîjin porta 80-ê. Berhevkar dikare vê biwêjê bi veguheztina wê li BPF bytecode kurt bike.

$ 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

Ya ku bernameya jorîn dike bi bingehîn ev e:

  • Telîmat (000): Pakêtê di 12-an de, wekî peyvek 16-bit, li berhevkerê bar dike. Offset 12 bi ethertype ya pakêtê re têkildar e.
  • Rêzkirin (001): nirxa di berhevkerê de bi 0x86dd, ango bi nirxa ethertype ya IPv6 re berhev dike. Ger encam rast be, wê hingê jimarvana bernameyê diçe talîmatê (002), û heke na, wê hingê diçe (006).
  • Talîmat (006): nirxê bi 0x800 re (nirxa ethertype ji bo IPv4) berhev dike. Ger bersiv rast be, wê gavê bername diçe (007), heke na, wê hingê diçe (015).

Û bi vî awayî, heta ku bernameya fîlterkirina pakêtê encamek vegerîne. Bi gelemperî ew boolean e. Vegerandina nirxek ne-sifir (talîmata (014)) tê wê wateyê ku pakêt li hev kir, û vegerandina sifirê (talîmata (015)) tê wateya ku pakêt li hev nekir.

Makîneya virtual ya BPF û bytecode wê ji hêla Steve McCann û Van Jacobson ve di dawiya sala 1992-an de dema ku kaxeza wan derket hate pêşniyar kirin. Parzûna pakêtê ya BSD: Mîmariya nû ji bo girtina pakêtê di asta bikarhêner de, cara yekem ev teknolojî di zivistana 1993 de di konferansa Usenix de hate pêşkêş kirin.

Ji ber ku BPF makîneyek virtual e, ew jîngeha ku bername tê de dimeşin diyar dike. Ji bilî bytecode, ew di heman demê de modelek bîranîna pakêtê jî diyar dike (rêberên barkirinê bi nepenî li ser pakêtek têne sepandin), tomar (A û X; tomarokên berhevkar û navnîşan), hilanîna bîranîna xêzkirî, û jimarvanek bernameyek nepenî. Balkêş e, BPF bytecode li gorî Motorola 6502 ISA hate çêkirin. Wekî ku Steve McCann di xwe de bi bîr anî rapora giştî li Sharkfest '11, dema ku li ser Apple II bername dikir, ew ji lîseyê bi avakirina 6502 nas bû, û vê zanînê bandor li xebata wî ya sêwirana bytekodê BPF kir.

Piştgiriya BPF di guhertoya v2.5 û paşê de di kernel Linux de tête bicîh kirin, ku bi piranî ji hêla Jay Schullist ve hatî zêdekirin. Koda BPF heta sala 2011-an neguherî ma, dema ku Eric Dumaset wergêrê BPF ji nû ve dîzayn kir ku di moda JIT de bixebite (Çavkanî: JIT ji bo Parzûnên Paketê). Piştî wê, li şûna şîrovekirina bytecode BPF, kernel dikare rasterast bernameyên BPF veguherîne mîmariya armanc: x86, ARM, MIPS, hwd.

Dûv re, di 2014 de, Alexei Starovoitov mekanîzmayek nû ya JIT ji bo BPF pêşniyar kir. Di rastiyê de, ev JIT-a nû bû mîmariyek nû ya ku li ser bingeha BPF-ê ye û jê re eBPF tê gotin. Ez difikirim ku her du VM ji bo demekê bi hev re hebûn, lê fîlterkirina pakêtê niha li ser eBPF-ê tête bicîh kirin. Bi rastî, di gelek mînakên belgeyên nûjen de, BPF wekî eBPF tê binav kirin, û BPF-ya klasîk îro wekî cBPF tê zanîn.

eBPF makîneya virtual ya klasîk a BPF bi çend awayan dirêj dike:

  • Li ser mîmariyên nûjen ên 64-bit ve girêdayî ye. eBPF qeydên 64-bit bikar tîne û hejmara tomarên berdest ji 2 (accumulator û X) berbi 10-ê zêde dike.
  • Ji binepergala qata torê veqetiyaye. BPF bi modela daneya komê ve girêdayî bû. Ji ber ku ew ji bo fîlterkirina pakêtan dihat bikar anîn, koda wê di binpergala ku danûstendinên torê peyda dike de bû. Lêbelê, makîneya virtual eBPF êdî bi modelek daneyê ve nayê girêdan û dikare ji bo her armancê were bikar anîn. Ji ber vê yekê, naha bernameya eBPF dikare bi tracepoint an bi kprobe ve were girêdan. Ev derî ji amûrkirina eBPF, analîzkirina performansê, û gelek rewşên din ên karanîna di çarçoveya bine pergalên kernel ên din de vedike. Naha koda eBPF di riya xwe de ye: kernel/bpf.
  • Dikanên daneyên gerdûnî yên bi navê Nexşe. Nexşe firotgehên nirxa sereke ne ku danûstandina daneyê di navbera cîhê bikarhêner û cîhê kernel de peyda dikin. eBPF gelek celeb qertan peyda dike.
  • Fonksiyonên duyemîn. Bi taybetî, ji bo nivîsandina pakêtek, hesabek kontrolê, an jî pakêtek klon bikin. Van fonksiyonan di hundurê kernelê de dixebitin û ne girêdayî bernameyên cîhê bikarhêner in. Wekî din, bangên pergalê ji bernameyên eBPF têne çêkirin.
  • Bangên dawî bikin. Mezinahiya bernameyê di eBPF de bi 4096 bytes ve sînorkirî ye. Taybetmendiya banga dawîyê destûrê dide bernameyek eBPF ku kontrolê veguhezîne bernameyek nû ya eBPF û bi vî rengî vê sînordariyê derbas bike (heta 32 bername dikarin bi vî rengî werin zincîr kirin).

Mînaka eBPF

Di çavkaniyên kernel Linux de ji bo eBPF gelek nimûne hene. Ew li ser nimûneyan / bpf / hene. Ji bo berhevkirina van mînakan, tenê binivîse:

$ sudo make samples/bpf/

Ez ê bi xwe mînakek nû ji bo eBPF nenivîsim, lê ez ê yek ji nimûneyên ku di nimûneyan de hene bikar bînim/bpf/. Ez ê li hin beşên kodê binihêrim û rave bikim ka ew çawa dixebite. Wek mînak, min bername hilbijart tracex4.

Bi gelemperî, her yek ji mînakên di nimûneyan/bpf/ de ji du pelan pêk tê. Di vê rewşê de:

  • tracex4_kern.c, koda çavkaniyê heye ku di kernelê de wekî bytecode eBPF were darve kirin.
  • tracex4_user.c, bernameyek ji cîhê bikarhêner heye.

Di vê rewşê de, divê em berhev bikin tracex4_kern.c eBPF bytecode. Di vê demê de li gcc e tu beşek server ji bo eBPF hene. Dilşane, clang dikare bytecode eBPF hilberîne. Makefile bikar tîne clang berhev kirin tracex4_kern.c ji bo pelê object.

Min li jor behs kir ku yek ji taybetmendiyên balkêş ên eBPF nexşe ne. tracex4_kern yek nexşeyê diyar dike:

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 yek ji gelek celebên kartê ye ku ji hêla eBPF ve hatî pêşkêş kirin. Di vê rewşê de, ew tenê haş e. Dibe ku we reklamê jî dîtibe SEC("maps"). SEC makroyek e ku ji bo afirandina beşa nû ya pelek binary tê bikar anîn. Bi rastî, di nimûne tracex4_kern du beşên din têne diyarkirin:

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

Van her du fonksiyonan dihêle hûn navnîşek ji nexşeyê derxînin (kprobe/kmem_cache_free) û navnîşek nû li nexşeyê zêde bikin (kretprobe/kmem_cache_alloc_node). Hemî navên fonksiyonê ku bi tîpên mezin têne nivîsandin bi makroyên ku tê de hatine destnîşan kirin re têkildar in bpf_helpers.h.

Ger ez beşên pelê objeyê bavêjim, divê ez bibînim ku ev beşên nû jixwe hatine diyar kirin:

$ 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

Heta niha heye tracex4_user.c, bernameya sereke. Di bingeh de, ev bername li bûyeran guhdarî dike kmem_cache_alloc_node. Dema ku bûyerek wusa diqewime, koda eBPF ya têkildar tê darve kirin. Kod taybetmendiya IP-ya objektê li nexşeyek tomar dike, û dûv re ew tişt bi bernameya sereke ve tê veguheztin. Mînak:

$ 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

Bernameya cîhê bikarhêner û bernameya eBPF çawa têkildar in? Di destpêkê de tracex4_user.c pelê objeyê bar dike tracex4_kern.o fonksiyonê bikar tîne 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;
}

Dema dikin load_bpf_file sondajên ku di pelê eBPF de hatine diyarkirin li wan têne zêdekirin /sys/kernel/debug/tracing/kprobe_events. Niha em li van bûyeran guhdarî dikin û dema ku çêdibin bernameya me dikare tiştekî bike.

$ 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

Hemî bernameyên din ên di nimûne / bpf / de bi heman rengî têne saz kirin. Ew her gav du pelan hene:

  • XXX_kern.c: bernameya eBPF.
  • XXX_user.c: bernameya sereke.

Bernameya eBPF nexşe û fonksiyonên ku bi beşê ve girêdayî ne diyar dike. Dema ku kernel bûyerek celebek diyar diweşîne (mînak, tracepoint), fonksiyonên girêdayî têne kirin. Nexşe danûstandinê di navbera bernameyek kernel û bernameyek cîhê bikarhêner de peyda dike.

encamê

Di vê gotarê de, BPF û eBPF bi gelemperî têne nîqaş kirin. Ez dizanim ku îro di derbarê eBPF de gelek agahdarî û çavkanî hene, ji ber vê yekê ez ê çend materyalên din ji bo lêkolîna bêtir pêşniyar bikim.

Ez xwendinê pêşniyar dikim:

Source: www.habr.com

Add a comment