BPF və eBPF-ə Qısa Giriş

Hey Habr! Nəzərinizə çatdırırıq ki, kitab nəşr etməyə hazırlaşırıq”BPF ilə Linux Müşahidəsi".

BPF və eBPF-ə Qısa Giriş
BPF virtual maşını təkamül etməyə davam etdiyi və praktikada fəal şəkildə istifadə edildiyi üçün onun əsas xüsusiyyətlərini və hazırkı vəziyyətini təsvir edən məqaləni sizin üçün tərcümə etdik.

Son illərdə proqramlaşdırma alətləri və texnikaları yüksək performanslı paket emalı tələb olunduğu hallarda Linux nüvəsinin məhdudiyyətlərini kompensasiya etmək üçün populyarlıq qazanmışdır. Bu növün ən məşhur üsullarından biri adlanır əsas bypass (kernel bypass) və nüvənin şəbəkə qatını atlayaraq istifadəçi sahəsindən bütün paket emalını həyata keçirməyə imkan verir. Nüvədən yan keçmək şəbəkə kartını idarə etməyi də əhatə edir istifadəçi sahəsi. Başqa sözlə, şəbəkə kartı ilə işləyərkən biz sürücüyə güvənirik istifadəçi sahəsi.

Şəbəkə kartına tam nəzarəti istifadəçi-məkan proqramına ötürməklə biz nüvənin yükünü (kontekst keçidləri, şəbəkə qatının işlənməsi, fasilələr və s.) azaldırıq ki, bu da 10 Gb/s və ya XNUMX Gb/s sürətlə işləyərkən olduqca vacibdir. daha yüksək. Kerneli və digər xüsusiyyətlərin birləşməsini keçmək (toplu emal) və diqqətli performans tənzimlənməsi (NUMA mühasibatlığı, CPU izolyasiyasıvə s.) yüksək performanslı istifadəçi məkan şəbəkəsinin əsaslarına uyğundur. Bəlkə də paket emalına bu yeni yanaşmanın nümunəvi nümunəsidir DPDK Intel-dən (Data Plane Development Kit), baxmayaraq ki, digər tanınmış alətlər və üsullar, o cümlədən Cisco-dan VPP (Vektor Paket Emalı), Nemap və əlbəttə ki, qaxmaq.

İstifadəçi məkanında şəbəkə qarşılıqlı əlaqəsinin təşkili bir sıra çatışmazlıqlara malikdir:

  • ƏS nüvəsi aparat resursları üçün abstraksiya təbəqəsidir. İstifadəçi məkan proqramları öz resurslarını birbaşa idarə etməli olduğundan, onlar da öz aparatlarını idarə etməlidirlər. Bu, çox vaxt öz sürücülərinizi proqramlaşdırmaq deməkdir.
  • Biz nüvə sahəsindən tamamilə imtina etdiyimiz üçün nüvənin təmin etdiyi bütün şəbəkə funksiyalarından da imtina edirik. İstifadəçi məkanı proqramları nüvə və ya əməliyyat sistemi tərəfindən artıq təmin edilmiş funksiyaları yenidən həyata keçirməlidir.
  • Proqramlar sandbox rejimində işləyir ki, bu da onların qarşılıqlı əlaqəsini ciddi şəkildə məhdudlaşdırır və əməliyyat sisteminin digər hissələri ilə inteqrasiyasına mane olur.

Əslində, istifadəçi məkanında şəbəkə qurarkən, paket emalını nüvədən istifadəçi sahəsinə köçürməklə performans artımı əldə edilir. XDP tam əksini edir: o, şəbəkə proqramlarını istifadəçi sahəsindən (filtrlər, çeviricilər, marşrutlaşdırma və s.) nüvə sahəsinə köçürür. XDP bizə şəbəkə funksiyasını paket şəbəkə interfeysinə dəyən kimi və nüvənin şəbəkə alt sisteminə keçməyə başlamazdan əvvəl yerinə yetirməyə imkan verir. Nəticədə paketin emal sürəti əhəmiyyətli dərəcədə artır. Bununla belə, kernel istifadəçiyə öz proqramlarını nüvə məkanında işlətməyə necə imkan verir? Bu suala cavab verməzdən əvvəl gəlin BPF-nin nə olduğuna baxaq.

BPF və eBPF

Tam aydın olmayan adına baxmayaraq, BPF (Berkeley Packet Filtering) əslində virtual maşın modelidir. Bu virtual maşın əvvəlcə paket filtrasiyasını idarə etmək üçün nəzərdə tutulmuşdu, buna görə də adı.

BPF-dən istifadə edən ən məşhur vasitələrdən biridir tcpdump. Istifadə edərək paketləri ələ keçirərkən tcpdump istifadəçi paketləri süzmək üçün ifadə təyin edə bilər. Yalnız bu ifadəyə uyğun gələn paketlər tutulacaq. Məsələn, “tcp dst port 80” 80-ci porta gələn bütün TCP paketlərinə aiddir. Kompilyator bu ifadəni BPF bayt koduna çevirərək qısalda bilər.

$ 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

Bu, əsasən yuxarıdakı proqramın etdiyi şeydir:

  • Təlimat (000): Paketi ofset 12-də 16 bitlik söz kimi akkumulyatora yükləyir. Ofset 12 paketin etertipinə uyğundur.
  • Təlimat (001): akkumulyatordakı dəyəri 0x86dd ilə, yəni IPv6 üçün etertip dəyəri ilə müqayisə edir. Nəticə doğrudursa, proqram sayğacı təlimata (002), yoxsa (006) gedir.
  • Təlimat (006): dəyəri 0x800 (IPv4 üçün ethertype dəyəri) ilə müqayisə edir. Cavab doğrudursa, proqram (007), yoxsa (015) işarəsinə keçir.

Və s., paket filtrləmə proqramı nəticə verənə qədər. Adətən boolean olur. Sıfırdan fərqli dəyərin qaytarılması (təlimat (014)) paketin uyğun gəldiyini və sıfırın qaytarılması (təlimat (015)) paketin uyğun gəlmədiyini bildirir.

BPF virtual maşını və onun bayt kodu 1992-ci ilin sonunda Stiv MakKenn və Van Yakobson tərəfindən kağızları çıxanda təklif edilmişdir. BSD Paket Filtri: İstifadəçi səviyyəsində paket ələ keçirmək üçün yeni arxitektura, ilk dəfə bu texnologiya 1993-cü ilin qışında Usenix konfransında təqdim edildi.

BPF virtual maşın olduğundan, proqramların işlədiyi mühiti müəyyən edir. Baytkoda əlavə olaraq, o, həmçinin paket yaddaş modelini (yükləmə təlimatları paketə birbaşa tətbiq olunur), registrləri (A və X; akkumulyator və indeks registrləri), cızıq yaddaş yaddaşını və gizli proqram sayğacını müəyyən edir. Maraqlıdır ki, BPF bayt kodu Motorola 6502 ISA modelindən sonra modelləşdirilmişdir. Necə ki, Steve McCann öz əsərində xatırladı plenar məruzə Sharkfest '11-də o, Apple II-də proqramlaşdırarkən orta məktəbdən 6502-ci quruluşla tanış idi və bu bilik onun BPF bayt kodunu tərtib edən işinə təsir etdi.

BPF dəstəyi əsasən Jay Schullist tərəfindən əlavə edilən v2.5 və sonrakı versiyalarda Linux nüvəsində həyata keçirilir. BPF kodu 2011-ci ilə qədər dəyişməz qaldı, Erik Dumaset BPF tərcüməçisini JIT rejimində işləmək üçün yenidən dizayn etdi (Mənbə: Paket Filtrləri üçün JIT). Bundan sonra, BPF bayt kodunu şərh etmək əvəzinə, nüvə BPF proqramlarını birbaşa hədəf arxitekturasına çevirə bilər: x86, ARM, MIPS və s.

Daha sonra, 2014-cü ildə Aleksey Starovoitov BPF üçün yeni JIT mexanizmini təklif etdi. Əslində, bu yeni JIT BPF əsasında yeni bir arxitektura oldu və eBPF adlandı. Düşünürəm ki, hər iki VM bir müddət birlikdə mövcud idi, lakin paket filtrasiyası hazırda eBPF üzərində həyata keçirilir. Əslində, bir çox müasir sənədləşdirmə nümunələrində BPF eBPF kimi istinad edilir və klassik BPF bu gün cBPF kimi tanınır.

eBPF klassik BPF virtual maşınını bir neçə yolla genişləndirir:

  • Müasir 64-bit arxitekturaya əsaslanır. eBPF 64-bit registrlərdən istifadə edir və mövcud registrlərin sayını 2-dən (akkumulyator və X) 10-a qədər artırır. eBPF həmçinin əlavə əməliyyat kodları (BPF_MOV, BPF_JNE, BPF_CALL…) təmin edir.
  • Şəbəkə qatının alt sistemindən ayrılıb. BPF toplu məlumat modelinə bağlandı. Paketləri süzgəcdən keçirmək üçün istifadə edildiyi üçün onun kodu şəbəkə qarşılıqlı əlaqəsini təmin edən alt sistemdə idi. Bununla belə, eBPF virtual maşını artıq məlumat modelinə bağlı deyil və istənilən məqsəd üçün istifadə edilə bilər. Beləliklə, indi eBPF proqramı tracepoint və ya kprobe-yə qoşula bilər. Bu, eBPF alətləri, performans təhlili və digər nüvə alt sistemləri kontekstində bir çox digər istifadə hallarına qapı açır. İndi eBPF kodu öz yolunda yerləşir: kernel/bpf.
  • Xəritələr adlı qlobal məlumat anbarları. Xəritələr istifadəçi sahəsi və nüvə sahəsi arasında məlumat mübadiləsini təmin edən əsas dəyər anbarlarıdır. eBPF bir neçə növ kart təqdim edir.
  • İkinci dərəcəli funksiyalar. Xüsusilə, paketin üzərinə yazmaq, yoxlama məbləğini hesablamaq və ya paketi klonlaşdırmaq üçün. Bu funksiyalar nüvənin daxilində işləyir və istifadəçi məkanı proqramlarına aid deyil. Bundan əlavə, sistem zəngləri eBPF proqramlarından edilə bilər.
  • Zəngləri bitirin. eBPF-də proqram ölçüsü 4096 baytla məhdudlaşır. Son zəng funksiyası eBPF proqramına nəzarəti yeni eBPF proqramına ötürməyə və beləliklə, bu məhdudiyyəti keçməyə imkan verir (bu yolla 32-yə qədər proqramı zəncirləmək olar).

eBPF nümunəsi

Linux nüvə mənbələrində eBPF üçün bir neçə nümunə var. Onlar nümunələr/bpf/ ünvanında mövcuddur. Bu nümunələri tərtib etmək üçün sadəcə olaraq yazın:

$ sudo make samples/bpf/

Mən özüm eBPF üçün yeni bir nümunə yazmayacağam, lakin samples/bpf/-də mövcud olan nümunələrdən birini istifadə edəcəyəm. Kodun bəzi hissələrinə baxacağam və onun necə işlədiyini izah edəcəyəm. Nümunə olaraq proqramı seçdim tracex4.

Ümumiyyətlə, samples/bpf/-dəki nümunələrin hər biri iki fayldan ibarətdir. Bu halda:

  • tracex4_kern.c, nüvədə eBPF bayt kodu kimi icra ediləcək mənbə kodunu ehtiva edir.
  • tracex4_user.c, istifadəçi məkanından proqramı ehtiva edir.

Bu halda biz kompilyasiya etməliyik tracex4_kern.c eBPF bayt koduna. Hazırda daxil gcc eBPF üçün arxa uç yoxdur. Xoşbəxtlikdən, clang eBPF bayt kodunu yarada bilər. Makefile istifadə edir clang tərtib üçün tracex4_kern.c obyekt faylına.

Yuxarıda qeyd etdim ki, eBPF-nin ən maraqlı xüsusiyyətlərindən biri xəritələrdir. tracex4_kern bir xəritə müəyyən edir:

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 eBPF tərəfindən təklif olunan bir çox kart növlərindən biridir. Bu vəziyyətdə, bu, sadəcə bir hashdır. Ola bilsin ki, siz də elana diqqət yetirmisiniz SEC("maps"). SEC ikili faylın yeni bölməsini yaratmaq üçün istifadə olunan makrodur. Əslində, nümunədə tracex4_kern daha iki bölmə müəyyən edilir:

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

Bu iki funksiya xəritədən qeydi silməyə imkan verir (kprobe/kmem_cache_free) və xəritəyə yeni giriş əlavə edin (kretprobe/kmem_cache_alloc_node). Böyük hərflərlə yazılmış bütün funksiya adları ilə müəyyən edilmiş makrolara uyğun gəlir bpf_helpers.h.

Obyekt faylının bölmələrini atsam, bu yeni bölmələrin artıq müəyyən edildiyini görməliyəm:

$ 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

da var tracex4_user.c, əsas proqram. Əsasən, bu proqram hadisələri dinləyir kmem_cache_alloc_node. Belə bir hadisə baş verdikdə, müvafiq eBPF kodu icra edilir. Kod obyektin IP atributunu xəritədə saxlayır və sonra obyekt əsas proqram vasitəsilə dövrələnir. Misal:

$ 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

İstifadəçi məkanı proqramı və eBPF proqramı necə əlaqəlidir? Başlandıqdan sonra tracex4_user.c obyekt faylını yükləyir tracex4_kern.o funksiyasından istifadə etməklə 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;
}

Etərkən load_bpf_file eBPF faylında müəyyən edilmiş zondlar əlavə edilir /sys/kernel/debug/tracing/kprobe_events. İndi biz bu hadisələri dinləyirik və proqramımız baş verəndə nəsə edə bilər.

$ 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

sample/bpf/-dəki bütün digər proqramlar oxşar şəkildə qurulub. Onlar həmişə iki fayldan ibarətdir:

  • XXX_kern.c: eBPF proqramı.
  • XXX_user.c: əsas proqram.

eBPF proqramı bölmə ilə əlaqəli xəritələri və funksiyaları müəyyən edir. Kernel müəyyən bir növ hadisə yaydıqda (məsələn, tracepoint), bağlı funksiyalar yerinə yetirilir. Xəritələr nüvə proqramı ilə istifadəçi məkan proqramı arasında əlaqəni təmin edir.

Nəticə

Bu məqalədə BPF və eBPF ümumi şəkildə müzakirə edilmişdir. Mən bilirəm ki, bu gün eBPF haqqında çoxlu məlumat və resurs var, ona görə də əlavə araşdırma üçün bir neçə daha çox resurs tövsiyə edəcəyəm

Oxumağı tövsiyə edirəm:

Mənbə: www.habr.com

Добавить комментарий