Përshëndetje, Habr! Po ju njoftojmë se jemi në përgatitje të publikimit të librit "".

Pasi makina virtuale BPF vazhdon të evoluojë dhe aplikohet aktivisht në praktikë, ne kemi përkthyer për ju një artikull që përshkruan mundësitë e saj kryesore dhe gjendjen aktuale.
Në vitet e fundit, janë bërë popullore mjetet për programim dhe teknikat, të cilat synojnë të kompensojnë kufizimet e bërthamës Linux në rastet kur kërkohet përpunim me performancë të lartë të paketave. Një nga teknikat më të njohura të këtij lloji quhet shkëputja e bërthamës (kernel bypass) dhe lejon, duke kaluar mbi nivelin e rrjetit të bërthamës, të kryejnë të gjithë përpunimin e paketave nga hapësira e përdoruesit. Shkëputja e bërthamës gjithashtu supozon menaxhimin e kartelës së rrjetit nga hapësira e përdoruesit. Me fjalë të tjera, kur punojmë me kartelën e rrjetit, ne mbështetemi në drejtuesin hapësira e përdoruesit.
Duke i dhënë kontrollin e plotë mbi kartelën e rrjetit një programi nga hapësira e përdoruesit, ne zvogëlojmë kostot e shkaktuara nga puna e bërthamës (ndërrimi i kontekstit, përpunimi i nivelit të rrjetit, ndërprerjet etj.), që është mjaft e rëndësishme kur punojmë me shpejtësi 10Gb/s ose më shumë. Shkëputja e bërthamës plus kombinimi i mundësive të tjera (përpunimi i paketave) dhe rregullimi i saktë i performancës (përllogaritja e NUMA, izolimi i CPU, etj.) përkojnë me bazat e përpunimit të rrjetit me performancë të lartë në hapësirën e përdoruesit. Një shembull model i këtij qasjeje të re në përpunimin e paketave është nga Intel (Data Plane Development Kit), megjithatë, ekzistojnë edhe mjete dhe teknika të tjera të njohura, mes të cilave VPP nga Cisco (Vector Packet Processing), Netmap dhe, sigurisht, .
Organizimi i ndërveprimeve të rrjetit në hapësirën e përdoruesit ka disa disavantazhe:
- Bërthama e OS është një nivel abstraktimi për burimet harduerike. Pasi programet e hapësirës së përdoruesit duhet të menaxhojnë burimet e tyre direkt, atyre gjithashtu u duhet të menaxhojnë harduerin e tyre. Shpesh kjo do të thotë nevojën për të programuar drejtuese të veta.
- Duke qoftë se ne heqim plotësisht çdo hapësirë në bërthamë, ne gjithashtu heqim dorë nga të gjitha funksionet rrjetore që ofrohen nga bërthama. Programet në hapësirën e përdoruesit duhet të implementojnë përsëri ato funksione, të cilat ndoshta, tashmë ofrohen nga bërthama ose sistemi operativ.
- Programet punojnë në modin e kutisë sandbox, gjë që kufizon ndjeshëm mundësitë e tyre për ndërveprim dhe pengon integrimin e tyre me pjesë të tjera të sistemit operativ.
Praktikisht, në organizimin e ndërveprimeve rrjetore në hapësirën e përdoruesit, rritja e performancës arrihet duke shpërngulur përpunimin e pakove nga bërthama në hapësirën e përdoruesit. XDP e bën të kundërtën: ai shpërngul programet rrjetore nga hapësira e përdoruesit (filtrat, konvertuesit, rrugëzimi, etj.) në fushën e bërthamës. XDP na lejon të kryejmë funksionin rrjetor sapo paketa të arrijë në ndërfaqen rrjetësore dhe përpara se ajo të nisë lëvizjen lart në nën sistemin rrjetor të bërthames. Si rezultat, shpejtësia e përpunimit të pakove rritet ndjeshëm. Por, si e lejon bërthama përdoruesit të ekzekutojë programet e tij në hapësirën e bërthamës? Para se të përgjigjemi në këtë pyetje, le të shqyrtojmë se çfarë është BPF.
BPF dhe eBPF
PavarĂ«sisht emrit tĂ« paqartĂ« BPF (Filtrimi i Pakove, Berkeley) â kjo, nĂ« tĂ« vĂ«rtetĂ«, Ă«shtĂ« njĂ« model virtual makine. Ky model virtual fillimisht u projektua pĂ«r tĂ« pĂ«rpunuar filtrimin e pakove, andaj edhe emri.
Një nga mjetet më të njohura që përdorin BPF është tcpdump. Kur kapen paketa duke përdorur tcpdump përdoruesi mund të vendosë një shprehje për filtrimin e pakove. Do të kapen vetëm paketat që përputhen me këtë shprehje. Për shembull, shprehja "tcp dst port 80" i përket të gjitha paketeve TCP që arrijnë në portin 80. Kompilatori mund ta shkurtëzojë këtë shprehje duke e shndërruar atë në kodin e bajtëve BPF.
$ 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
Kështu që, kjo është në thelb ajo që bën programi i mësipërm:
- Instruksioni (000): ngarkon paketën me një offset prej 12, në formën e një fjalë 16-bitësh në akumulator. Offseti 12 përputhet me ethertype-in e paketës.
- Instruksioni (001): krahasohet vlera nĂ« akumulator me 0x86dd, qĂ« do tĂ« thotĂ«, me vlerĂ«n ethertype pĂ«r IPv6. NĂ«se rezultati Ă«shtĂ« true, atĂ«herĂ« numri i programit kalon nĂ« instruksionin (002), nĂ«se jo â atĂ«herĂ« te (006).
- UdhĂ«zimi (006): krahason vlerĂ«n me 0x800 (vlera ethrtype pĂ«r IPv4). NĂ«se pĂ«rgjigjja Ă«shtĂ« true, programi kalon te (007), nĂ«se jo â te (015).
Dhe kështu me radhë, derisa programi i filtrimit të paketeve të kthejë një rezultat. Normalisht është një boolean. Kthimi i një vlere jo zero (udhëzimi (014)) do të thotë se paketa përputhet, ndërsa kthimi zero (udhëzimi (015)) do të thotë se paketa nuk përputhet.
Makinën virtuale BPF dhe kodin e saj byte e propozuan Steve McKanna dhe Van Jacobson në fund të vitit 1992, kur doli artikulli i tyre , teknologjia u paraqit për herë të parë në konferencën Usenix gjatë dimrit të vitit 1993.
Duke qenĂ« se BPF Ă«shtĂ« njĂ« makinĂ« virtuale, ajo pĂ«rcakton ambientin ku ekzekutohen programet. PĂ«rveç kodit byte, ajo gjithashtu pĂ«rcakton modelin e memories sĂ« paketave (udhĂ«zimet e ngarkimit zbatohen implikisht nĂ« paketĂ«), regjistrat (A dhe X; regjistrat e akumulatorit dhe indeksit), depozitimin e memories ndihmĂ«se dhe numĂ«ruesin e fshehtĂ« tĂ« programeve. ĂshtĂ« interesante se kodi byte i BPF Ă«shtĂ« modeluar sipas ISA tĂ« Motorola 6502. Siç pĂ«rmendi Steve McKanna nĂ« nĂ« Sharkfest â11, ai ishte njohur me montimin 6502 qĂ« nĂ« klasat e larta, kur programonte nĂ« Apple II, dhe kjo njohuri ndikoi nĂ« punĂ«n e tij pĂ«r dizajnimin e kodit byte tĂ« BPF.
Mbështetja për BPF është implementuar në kernelin Linux në versionin v2.5 dhe më tej, e shtuar kryesisht nga përpjekjet e Jay Shulist. Kodi BPF mbeti pa ndryshime të rëndësishme deri në vitin 2011, kur Eric Dumazet e ristrukturio interpretorin BPF për të punuar në modalitetin JIT (Burimi: ). Pas kësaj, kernelin, përveç interpretimit të kodit byte BPF, mundi të konvertonte direkt programet BPF për 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 mbi bazën e BPF dhe mori emrin eBPF. Mendoj se për një kohë të caktuar të dy makinat virtuale bashkëjetuan, por në ditët e sotme filtrimi i paketave realizohet në bazën e eBPF. Në fakt, në shumë mostra të dokumentacionit modern, nën BPF kuptohet eBPF, ndërsa BPF klasike sot njihet si cBPF.
eBPF në disa aspekte zgjeron makinën virtuale klasike BPF:
- Bazohet nĂ« arkitekturave moderne 64-bit. eBPF pĂ«rdor regjistrat 64-bit dhe rrit numrin e regjistrave tĂ« disponueshĂ«m nga 2 (akumulatori dhe X) nĂ« 10. NĂ« eBPF gjithashtu ofrohen kode operacioni shtesĂ« (BPF_MOV, BPF_JNE, BPF_CALLâŠ).
- E shkëputur nga nën-sistemi i nivelit rrjet. BPF ishte e lidhur me modelin e të dhënave të paketave. Duke qenë se përdorej për filtrimin e paketimeve, kodi i saj ndodhej në nën-sistemin që mundësonte ndërveprimet rrjet. Megjithatë, makina virtuale eBPF nuk është më e lidhur me modelin e të dhënave dhe mund të përdoret për çdo qëllim. Tani, programi eBPF mund të lidhet me një tracepoint ose me një kprobe. Kjo hap një rrugë për instrumentimin e eBPF, analizën e performancës dhe shumë mundësi të tjera përdorimi në kontekstin e nën-sistemeve të tjera të bërthamës. Tani kodi i eBPF ndodhet në një rrugë të vetën: kernel/bpf.
- Depo globale të dhënash të quajtur Hartat. Hartat janë depo të tipit "çelës-vlerë", që mundësojnë shkëmbimin e të dhënave mes hapësirës së përdoruesit dhe hapësirës së bërthamës. Në eBPF ofrohen harta të disa tipave.
- Funksione ndihmëse. Në veçanti, për rishkrimin e paketave, llogaritjen e checksum-it ose klonimin e paketave. Këto funksione kryhen brenda bërthamës dhe nuk i përkasin programeve të hapësirës së përdoruesit. Për më tepër, nga programet e eBPF mund të kryhen thirrje sistemike.
- Thirrje përfundimtare. Madhësia e programit në eBPF është e kufizuar në 4096 byte. Mundësia e thirrjes përfundimtare lejon programit eBPF të kalojë kontrollin në një program të ri eBPF dhe në këtë mënyrë të anashkalojë këtë kufizim (në këtë mënyrë mund të lidhen deri në 32 programe).
eBPF: shembull
Në burimet e bërthamës Linux ka disa shembuj për eBPF. Ata janë të disponueshëm në adresën samples/bpf/. Për t'i kompaktuar këta shembuj, thjesht futni:
$ sudo make samples/bpf/
Nuk do të shkruaj unë një shembull të ri për eBPF, por do të përdor një nga modelet e disponueshme në samples/bpf/. Do të shqyrtoj disa pjesë të kodit dhe do të shpjegoj se si funksionon. Si shembull, kam zgjedhur programin tracex4.
Në të vërtetë, secili nga shembujt në samples/bpf/ përbëhet nga dy skedare. Në këtë rast:
tracex4_kern.c, përmban kodin burimor që duhet të ekzekutohet në bërthamë si byte-code eBPF.tracex4_user.c, përmban programin nga hapësira e përdoruesit.
Në këtë rast, na nevojitet ta kompaktosh tracex4_kern.c në kodin byte eBPF. Aktualisht, gcc mungon një pjesë serveri për eBPF. Fatmirësisht, clang mund të nxjerrë kodin byte eBPF. përdor clang për kompilim tracex4_kern.c në skedarin objekt.
Më sipër përmenda se një nga karakteristikat 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 â njĂ« nga shumĂ« lloje hartash qĂ« ofrohen nga eBPF. NĂ« kĂ«tĂ« rast, Ă«shtĂ« thjesht njĂ« hash. Gjithashtu mund tĂ« keni vĂ«nĂ« re shpalljen SEC("maps"). SEC Ă«shtĂ« njĂ« makro e pĂ«rdorur pĂ«r tĂ« krijuar njĂ« seksion tĂ« ri tĂ« skedarit binar. NĂ« fakt, nĂ« shembujt e tracex4_kern pĂ«rcaktohen 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;
// marrim adresën IP të thirrësit të 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 lejojnë të fshihet një regjistër nga harta (kprobe/kmem_cache_free) dhe të shtohet një regjistër të ri në hartë (kretprobe/kmem_cache_alloc_node). Të gjitha emrat e funksioneve të shkruara me shkronja të mëdha përputhen me makrotë e përcaktuara në .
Nëse unë nxjerr në dritë dumpin e seksioneve të skedarit objekt, do të duhet të shoh se këto seksione të reja janë tashmë të përcaktuara:
$ objdump -h tracex4_kern.o
tracex4_kern.o: formati i skedarit elf64-little
Seksionet:
Idx Emri Madhësia VMA LMA Pjesa off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
PĂRMBATJA, ALLOKIM, NGJITJE, E FSHTIRĂ, KOD
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
PĂRMBATJA, ALLOKIM, NGJITJE, RIKTHIM, E FSHTIRĂ, KOD
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
PĂRMBATJA, ALLOKIM, NGJITJE, RIKTHIM, E FSHTIRĂ, KOD
3 harta 0000001c 0000000000000000 0000000000000000 00000148 2**2
PĂRMBATJA, ALLOKIM, NGJITJE, DĂRGIM
4 licenca 00000004 0000000000000000 0000000000000000 00000164 2**0
PĂRMBATJA, ALLOKIM, NGJITJE, DĂRGIM
5 versione 00000004 0000000000000000 0000000000000000 00000168 2**2
PĂRMBATJA, ALLOKIM, NGJITJE, DĂRGIM
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
PĂRMBATJA, ALLOKIM, NGJITJE, RIKTHIM, E FSHTIRĂ, TĂ DHENAT
Ka gjithashtu , programi kryesor. Në thelb, ky program dëgjon ngjarjet kmem_cache_alloc_node.Kur ndodhin një ngjarje e tillë, ekzekutohet kodi përkatës eBPF. Kodi ruan atributin IP të objektit në hartë, dhe pastaj ky objekt përsëritet në programin kryesor. Shembulli:
$ sudo .\/tracex4
obj 0xffff8d6430f60a00 është 2 sekonda e vjetër, u alokua në ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 është 23 sekonda e vjetër, u alokua në ip ffffffff98090e8f
obj 0xffff8d5f80161780 është 6 sekonda e vjetër, u alokua në ip ffffffff98090e8f
Si janë të lidhura programi i hapësirës së përdoruesit dhe programi eBPF? Gjatë inicializimit tracex4_user.c ngarkon skedarin objekt tracex4_kern.o duke përdorur funksionin ngarko_bpf_sked.
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;
} Në përmbushje sondat, të përcaktuara në skedarin eBPF, shtohen në /sys/kernel/debug/tracing/kprobe_events. Tani ne po i dëgjojmë këto ngjarje, dhe programi ynë mund të bëjë diçka në momentin 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ë sample/bpf/ janë strukturuar në mënyrë të ngjashme. Ato gjithmonë përmbajnë dy skedarë:
XXX_kern.c: programi eBPF.XXX_user.c: programi kryesor.
Programi eBPF përcakton karta dhe funksione të lidhura me seksionin. Kur bërthama lëshon një ngjarje të një lloji të caktuar (p.sh., në bërthamë. Në këtë moment, numri iReferencave gjithashtu do të rritet me një dhe ne do të mund të mbyllim deshifruesin e skedarit në programin ngarkues.), funksionet e lidhura ekzekutohen. Karta sigurojnë shkëmbim të të dhënave midis programit të bërthamës dhe programit të hapësirës së përdoruesit.
Përfundim
Në këtë artikull, u shqyrtuan përgjithësisht BPF dhe eBPF. E di që sot ka shumë informacion dhe burime mbi eBPF, prandaj do të rekomandoja disa materiale të tjera për studim të mëtejshëm.
Rekomandoj të lexoni:
- Jonathant Corbett. Një hyrje në BPF dhe tregimi se si ajo u evolucionua në eBPF.
- Brendan Gregg. Një artikull nga LWN.net. Brendan shpesh shkruan twitera mbi eBPF dhe mban një listë burimesh mbi këtë temë në profilin e tij. .
- Julia Evans. Komente mbi prezantimin e Suchakra Sharma âThe BSD Packet Filter: A New Architecture for User-level Packet Captureâ. Komentet janĂ« tĂ« mira dhe vĂ«rtet ndihmojnĂ« nĂ« kuptimin e skicave.
- Ferris Ellis. Një artikull i gjatë me , por ja vlen ta lexosh. Një nga artikujt më të mirë mbi eBPF që kam parë.
Burimi: habr.com
