Pozdrav, Habr! Obavještavamo vas da se priprema izdanje knjige "".

Kako virtualna mašina BPF nastavlja evoluirati i aktivno se koristiti u praksi, preveli smo za vas članak koji opisuje njene glavne mogućnosti i trenutnu situaciju.
Posljednjih godina popularnost su stekle alatne komponente za programiranje i tehnike koje imaju za cilj nadoknaditi ograničenja Linux jezgre u situacijama kada je potrebna visokoučinkovita obrada paketa. Jedna od najpopularnijih tehnika ovog tipa zove se zaobilaženje jezgre (kernel bypass) i omogućava, preskočivši mrežni nivo jezgre, izvođenje cijele obrade paketa iz korisničkog prostora. Zaobilaženje jezgre također podrazumijeva upravljanje mrežnom karticom iz korisničkog prostora. Drugim riječima, kada radimo s mrežnom karticom, oslanjamo se na drajver korisničkog prostora.
Dajući potpunu kontrolu nad mrežnom karticom programu iz korisničkog prostora, smanjujemo troškove nastale uslijed rada jezgre (prebacivanje konteksta, obrada mrežnog nivoa, prekidi, itd.), što je od velike važnosti pri radu brzinama od 10Gb/s ili više. Zaobilaženje jezgre plus kombinacija drugih mogućnosti (obrada paketa) i precizna podešavanja performansi (uzimanje u obzir NUMA, izolacija CPU-a, itd.) predstavljaju osnove visokoučinkovite mrežne obrade u korisničkom prostoru. Možda je uzoran primjer ovog novog pristupa obradi paketa od Intela (Data Plane Development Kit), iako postoje i drugi poznati alati i tehnike, među kojima su VPP od Cisco-a (Vector Packet Processing), Netmap i, naravno, .
Organizacija mrežnih interakcija u korisničkom prostoru ima nekoliko nedostataka:
- Jezgra OS-a je nivo apstrakcije za hardverske resurse. Kako aplikacije u korisničkom prostoru moraju izravno upravljati svojim resursima, također moraju upravljati i vlastitim hardverom. Često to znači potrebu za programiranjem vlastitih drajvera.
- Budući da se potpuno odričemo prostora jezgre, također se odričemo i svih mrežnih funkcionalnosti koje pruža jezgra. Programi u korisničkom prostoru moraju ponovno implementirati one funkcije koje je možda već osigurala jezgra ili operativni sustav.
- Programi rade u sandbox modu, što ozbiljno ograničava mogućnosti njihove interakcije i otežava im integraciju s drugim dijelovima operativnog sustava.
U biti, prilikom organizacije mrežnih interakcija u korisničkom prostoru, povećanje performansi postiže se premještanjem obrade paketa iz jezgre u korisnički prostor. XDP radi upravo suprotno: premješta mrežne programe iz korisničkog prostora (filteri, konvertori, rutiranje itd.) u prostor jezgre. XDP nam omogućuje da izvršimo mrežnu funkciju čim paket dođe na mrežni sučelje i prije nego što počne putovati prema mrežnoj podsustavu jezgre. Kao rezultat, brzina obrade paketa se znatno povećava. Međutim, kako jezgra omogućuje korisnicima da izvršavaju svoje programe u prostoru jezgre? Prije nego što odgovorimo na ovo pitanje, pogledajmo što je BPF.
BPF i eBPF
Unatoč nejasnom nazivu BPF (Berkeley Packet Filter) – to je zapravo model virtualne mašine. Ova virtualna mašina prvotno je osmišljena za obradu filtriranja paketa, odakle i naziv.
Jedan od najpoznatijih alata koji koristi BPF je tcpdump. Kada se paketi hvataju pomoću tcpdump korisnik može postaviti izraz za filtriranje paketa. Hvatat će se samo oni paketi koji odgovaraju tom izrazu. Na primjer, izraz “tcp dst port 80” odnosi se na sve TCP pakete koji dolaze na port 80. Kompajler može skratiti ovaj izraz, pretvarajući ga u BPF bajt kod.
$ 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
Evo što, zapravo, radi prethodni program:
- Instrukcija (000): učitava paket s pomakom 12, kao 16-bitnu riječ u akumulator. Pomak 12 odgovara ethertypeu paketa.
- Instrukcija (001): uspoređuje vrijednost u akumulatoru s 0x86dd, tj. s ethertype vrijednošću za IPv6. Ako je rezultat true, tada kontroverza programa prelazi na instrukciju (002), a ako nije – na (006).
- Uputa (006): uspoređuje vrijednost s 0x800 (ethertype-vrijednost za IPv4). Ako je odgovor true, program prelazi na (007), inače na (015).
I tako dalje, sve dok filtriranje paketa ne vrati rezultat. Obično je to boolean. Povratak nenultne vrijednosti (uputa (014)) znači da je paket odgovarao, dok povratak nulte (uputa (015)) znači da paket nije odgovarao.
Virtualna mašina BPF i njezin bytecode predložili su Steve McCanne i Van Jacobson krajem 1992. kada je objavljen njihov članak. , ova tehnologija prvi put je predstavljena na Usenix konferenciji zimi 1993. godine.
Budući da je BPF virtualna mašina, ona definira okruženje u kojem se programi izvršavaju. Osim bytecode-a, definira i paketni model memorije (naredbe učitavanja primjenjuju se neizravno na paket), registre (A i X; registarska akumulatora i indeksa), pohranu privremene memorije i neizravni brojač programa. Zanimljivo je da je BPF bytecode modeliran prema Motorola 6502 ISA. Kako je Steve McCanne spomenuo u svom na Sharkfest ‘11, bio je upoznat s 6502 assemblerom još od srednje škole, kada je programirao na Apple II, a to znanje utjecalo je na njegov rad na dizajnu BPF bytecode-a.
Podrška za BPF implementirana je u Linux kernel u verziji v2.5 i novijim, a dodana je uglavnom zahvaljujući naporima Jaya Shullista. BPF kod ostao je bez značajnih promjena sve do 2011. godine kada je Eric Dumazet prepravio BPF interpreter za rad u JIT režimu (Izvor: ). Nakon toga, kernel je umjesto interpretacije BPF bytecode-a mogao izravno pretvarati BPF programe za ciljnu arhitekturu: x86, ARM, MIPS, itd.
Kasnije, 2014. godine, Aleksej Starovoitov predložio je novi JIT mehanizam za BPF. U stvari, ovaj novi JIT postao je nova arhitektura temeljena na BPF i nazvan je eBPF. Mislim da je neko vrijeme obje virtualne mašine koegzistirale, ali trenutno se filtriranje paketa realizira na temelju eBPF. U stvari, u mnogim uzorcima moderne dokumentacije pod BPF se doživljava eBPF, a klasična BPF danas je poznata kao cBPF.
eBPF u nekoliko aspekata proširuje klasičnu virtualnu mašinu BPF:
- Osnova se oslanja na moderne 64-bitne arhitekture. eBPF koristi 64-bitne registre i povećava broj dostupnih registara s 2 (akumulator i X) na 10. U eBPF su također dostupni dodatni operativni kodovi (BPF_MOV, BPF_JNE, BPF_CALL…).
- Odvojena od podsustava mrežnog nivoa. BPF je bio vezan uz model paketa podataka. Kako se koristio za filtriranje paketa, njegov kod bio je unutar podsustava koja osigurava mrežne interakcije. Međutim, virtualna mašina eBPF više nije povezana s modelom podataka i može se koristiti u razne svrhe. Tako sada program eBPF može biti povezan s tracepointom ili kprobom. To otvara put za instrumentaciju eBPF-a, analizu performansi i mnoge druge primene u kontekstu drugih podsustava jezgre. Sada se kod eBPF nalazi na svom putu: kernel/bpf.
- Globalne pohrane podataka nazvane Karte. Karte su skladišta tipa 'ključ-vrijednost' koja omogućuju razmjenu podataka između korisničkog prostora i prostora jezgre. U eBPF-ju su dostupne karte nekoliko tipova.
- Pomoćne funkcije. Konkretno, za prepisivanje paketa, izračunavanje kontrolne zbroja ili kloniranje paketa. Ove funkcije se izvode unutar jezgre i ne pripadaju programima korisničkog prostora. Osim toga, programi eBPF mogu izvršavati sistemske pozive.
- Završni pozivi. Veličina programa u eBPF je ograničena na 4096 bajtova. Mogućnost završnog poziva omogućava programu eBPF da prenese kontrolu novoj eBPF programu i tako zaobiđe ovo ograničenje (na taj način mogu se povezati do 32 programa).
eBPF: primjer
U izvorima jezgre Linuxa postoji nekoliko primjera za eBPF. Oni su dostupni na adresi samples/bpf/. Da biste kompajlirali ove primjere, jednostavno unesite:
$ sudo make samples/bpf/
Neću sam pisati novi primjer za eBPF, nego ću koristiti jedan od uzoraka koji su dostupni u samples/bpf/. Pogledat ću neke dijelove koda i objasniti kako on radi. Kao primjer izabrao sam program tracex4.
Općenito, svaki od primjera u samples/bpf/ sastoji se od dva datoteke. U ovom slučaju:
tracex4_kern.c, sadrži izvorni kod koji treba izvoditi u jezgri kao eBPF bajt-kod.tracex4_user.c, sadrži program iz korisničkog prostora.
U tom slučaju, trebamo to kompajlirati tracex4_kern.c u bytecode eBPF. Trenutno nedostaje serverska komponenta za eBPF. Na sreću, gcc može isporučiti bytecode eBPF. clang za kompajliranje koristi clang u objekt datoteku. tracex4_kern.c Već sam spomenuo da je jedna od najzanimljivijih karakteristika eBPF mape. tracex4_kern definira jednu mapu:
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, };
– jedan od mnogih tipova mapa koje nudi eBPF. U ovom slučaju, to je jednostavno hash. Također ste mogli primijetiti deklaraciju BPF_MAP_TYPE_HASH SEC("maps") . SEC je makro koji se koristi za kreiranje nove sekcije binarne datoteke. U ovoj primjerutracex4_kern definira još dvije sekcije: 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;// dobijamo ip-adresu pozivaoca 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; }
Ove dvije funkcije omogućavaju brisanje zapisa iz mape ( kprobe/kmem_cache_free) i dodavanje novog zapisa u mapu (kretprobe/kmem_cache_alloc_node). Sva imena funkcija zapisana velikim slovima odgovaraju makroima definiranima uAko ispišem dump sekcija objekt datoteke, trebao bih vidjeti da su ove nove sekcije već definirane: .
$ objdump -h tracex4_kern.o
tracex4_kern.o: format datoteke elf64-little
Sekcije:
Idx Ime Veličina VMA LMA File off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
SADRŽAJI, ALOKACIJA, UČITAVANJE, SAMO-READONLY, KOD
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
SADRŽAJI, ALOKACIJA, UČITAVANJE, RELOKACIJA, SAMO-READONLY, KOD
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
3 maps 0000001c 0000000000000000 0000000000000000 00000148 2**2
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
SADRŽAJI, ALOKACIJA, UČITAVANJE, PODACI
4 licenca 00000004 0000000000000000 0000000000000000 00000164 2**0
5 verzija 00000004 0000000000000000 0000000000000000 00000168 2**2
4 licenca 00000004 0000000000000000 0000000000000000 00000164 2**0
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
4 licenca 00000004 0000000000000000 0000000000000000 00000164 2**0
SADRŽAJI, ALOKACIJA, UČITAVANJE, RELOKACIJA, SAMO-READONLY, PODACI
, osnovni program. U suštini, ovaj program prati događaje
Tu je još i kmem_cache_alloc_node . Kada se dogodi takav događaj, izvršava se odgovarajući eBPF kod. Kod sprema IP atribut objekta u mapu, a zatim se ovaj objekt periodično ispisuje u osnovni program. Primjer:Kako su povezani program korisničkog prostora i eBPF program? Prilikom inicijalizacije
$ sudo .\/tracex4
obj 0xffff8d6430f60a00 je star 2 sekunde, alociran je na ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 je star 23 sekunde, alociran je na ip ffffffff98090e8f
obj 0xffff8d5f80161780 je star 6 sekundi, alociran je na ip ffffffff98090e8f
učitava objekt datoteku tracex4_user.c tracex4_kern.o pomoću funkcije uz pomoć funkcije učitaj_bpf_datoteku.
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;
} Tijekom izvršavanja sondi definirane u eBPF datoteci dodaju se u /sys/kernel/debug/tracing/kprobe_events. Sada slušamo ta događanja, a naš program može nešto učiniti kada se ona dogode.
$ 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
Sve ostale programe u sample/bpf/ strukturirane su na isti način. Uvijek im se nalaze dva datoteke:
XXX_kern.c: eBPF program.XXX_user.c: glavna svakodnevna aplikacija.
eBPF program definira karte i funkcije vezane uz sekciju. Kada jezgra izda događaj određenog tipa (na primjer, tracepoint), vezane funkcije se izvršavaju. Karte omogućuju razmjenu podataka između programa jezgre i aplikacije korisničkog prostora.
Zaključak
U ovom članku u općim crtama su objašnjeni BPF i eBPF. Znam da danas ima puno informacija i resursa o eBPF-u, stoga bih preporučio još nekoliko materijala za daljnje proučavanje
Preporučujem da pročitate:
- Jonathana Corbetta. Uvod u BPF i priča o tome kako je evoluirala u eBPF.
- Brendana Gregga. Članak s web stranice LWN.net. Brendan često tvita o eBPF-u i vodi popis resursa na tu temu na svom .
- Julije Evans. Komentari na prezentaciju Suchakre Sharma "BSD Packet Filter: Nova arhitektura za hvatanje paketa na razini korisnika". Komentari su korisni i uistinu pomažu razjasniti slajdove.
- Ferrisa Ellisa. Dugo čitanje s , ali se isplati pročitati. Jedan od najboljih članaka o eBPF-u koji sam ikad naišao.
Izvor: habr.com
