Berkeley Packet Filters (BPF) â Ă«shtĂ« njĂ« teknologji e kernelit Linux qĂ« ka ndĂ«rruar shumĂ« nĂ« faqet e para tĂ« periodikĂ«ve teknikĂ« anglishtfolĂ«s kĂ«to disa vite. Konferencat janĂ« tĂ« mbushura me prezantime mbi pĂ«rdorimin dhe zhvillimin e BPF. David Miller, mbajtĂ«si i nĂ«nstruktures rrjetĂ«sore tĂ« Linux, e quajti prezantimin e tij nĂ« Linux Plumbers 2018 (XDP â Ă«shtĂ« njĂ« nga mĂ«nyrat e pĂ«rdorimit tĂ« BPF). Brendan Gregg mbajti prezantime me titullin . Toke HĂžiland-JĂžrgensen , thotĂ« se kĂ«tu kerneli tani Ă«shtĂ« microkernel. Thomas Graf promovon idenĂ« se .
Në Habrë ende nuk ka një përshkrim sistematik të BPF, dhe prandaj në një seri artikujsh do mundohem të flas për historinë e teknologjisë, të përshkruaj arkitekturën dhe mjetet e zhvillimit, të përshkruaj fushat e aplikimit dhe praktikat e përdorimit të BPF. Në këtë artikull të parë, do të flas për historinë dhe arkitekturën e BPF klasike, si dhe do zbuloj sekretet e parimeve të funksionimit tcpdump, seccomp, strace, dhe shumë më tepër.
Zhvillimi i BPF kontrollohet nga komuniteti rrjetësor i Linux, aplikacionet kryesore ekzistuese të BPF lidhen me rrjetet dhe prandaj, me lejen e , e quajta serinë "BPF për të vegjlit", në nder të serisë së madhe .
Një kurs i shkurtër i historisë BPF (me)
Teknologjia bashkĂ«kohore BPF â Ă«shtĂ« njĂ« version i pĂ«rmirĂ«suar dhe i plotĂ«suar i teknologjisĂ« sĂ« vjetĂ«r me tĂ« njĂ«jtin emĂ«r, e njohur tani, pĂ«r tĂ« shmangur konfuzionin, si BPF klasike. NĂ« bazĂ« tĂ« BPF klasike u krijua utiliteti i njohur tĂ« gjitha, mekanizmi tcpdump, si dhe modul seccompxt_bpf dhe klasifikuesi pĂ«r iptables cls_bpf . NĂ« Linuxin bashkĂ«kohor, programet klasike BPF automatikisht pĂ«rkthehen nĂ« formĂ«n e re, megjithatĂ«, nga kĂ«ndvĂ«shtrimi i pĂ«rdoruesit, API mbeti i njĂ«jtĂ« dhe aplikacionet e reja tĂ« BPF klasike, si do ta shohim nĂ« kĂ«tĂ« artikull, janĂ« ende tĂ« pranishme. PĂ«r kĂ«tĂ« shkak, si dhe pĂ«r tĂ« kuptuar historinĂ« e zhvillimit tĂ« BPF klasike nĂ« Linux, do tĂ« bĂ«het mĂ« e qartĂ« se si dhe pse ajo ka evoluar nĂ« formĂ«n moderne, vendosa tĂ« filloj pikĂ«risht me njĂ« artikull mbi BPF klasike.NĂ« fund tĂ« viteve '80, inzhinierĂ«t nga Lawrence Berkeley Laboratory u interesuan pĂ«r mĂ«nyrĂ«n se si duhej tĂ« filtroheshin paketat rrjet nĂ« harduerin e sotĂ«m tĂ« fundit tĂ« viteve '80. Ideja e bazĂ«s pĂ«r filtrimin, e cila u realizua fillimisht nĂ« teknologjinĂ« CSPF (CMU/Stanford Packet Filter), ishte qĂ« tĂ« filtroheshin paketat e panevojshme sa mĂ« herĂ«t, dmth. nĂ« hapĂ«sirĂ«n e kernelit, pasi kjo lejon qĂ« tĂ« mos kopjohen tĂ« dhĂ«na tĂ« panevojshme nĂ« hapĂ«sirĂ« tĂ« pĂ«rdoruesit. PĂ«r tĂ« garantuar sigurinĂ« gjatĂ« ekzekutimit tĂ« kodit tĂ« pĂ«rdoruesit nĂ« hapĂ«sirĂ«n e kernelit, u pĂ«rdor njĂ« makinĂ« virtuale â njĂ« sandbox.
NĂ« fund tĂ« viteve '80, inxhinierĂ«t e njohur tĂ« Laboratorit Lawrence Berkeley u interesuan se si tĂ« filtronin paketat rrjeti nĂ« harduerin modern tĂ« asaj kohe. Ideja bazĂ« e filtrimit, e realizuar fillimisht nĂ« teknologjinĂ« CSPF (CMU/Stanford Packet Filter), ishte tĂ« filtronte paketat e tepĂ«rta sa mĂ« herĂ«t qĂ« tĂ« ishte e mundur, nĂ« hapĂ«sirĂ«n e bĂ«rthamorit, pasi kjo lejonte qĂ« tĂ« mos kopjoheshin tĂ« dhĂ«na tĂ« panevojshme nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit. PĂ«r tĂ« garantuar sigurinĂ« e kohĂ«s sĂ« ekzekutimit pĂ«r ekzekutimin e kodit tĂ« pĂ«rdoruesit nĂ« hapĂ«sirĂ«n e bĂ«rthamorit, u pĂ«rdor njĂ« makinĂ« virtuale â njĂ« sandbox.
MegjithatĂ«, makinat virtuale pĂ«r filtrat ekzistues ishin projektuar pĂ«r tĂ« punuar nĂ« makina me arkitekturĂ« stack dhe nĂ« makinat e reja RISC nuk punonin aq efikas. Si rezultat i pĂ«rpjekjeve tĂ« inxhinierĂ«ve nga Berkeley Labs, u zhvillua njĂ« teknologji e re BPF (Berkeley Packet Filters), arkitektura e makinĂ«s virtuale tĂ« sĂ« cilĂ«s ishte projektuar mbi bazĂ«n e procesorit Motorola 6502 â punĂ«torin e njohur tĂ« produkteve si ose . MakinĂ« e re virtuale rriti performancĂ«n e filtrave disa herĂ« mĂ« shumĂ« krahasuar me zgjidhjet ekzistuese.
Arkitektura e makinës BPF
Ne do të njohim arkitekturën në mënyrë operative, duke analizuar shembuj. Megjithatë, për të filluar do të themi se makina kishte dy regjistra 32-bitësh të disponueshëm për përdoruesin, akumulatorin A dhe regjistrin indeks X, 64 byte memorie (16 fjalë), të disponueshme për të shkruar dhe lexuar, dhe një sistem të vogël komandash për të punuar me këto objekte. Në programet ishin të disponueshme edhe instruksione kalimi për realizimin e shprehjeve kushtore, megjithatë, për të garantuar përfundimin e punës në kohë, kalimi mund të bëhej vetëm përpara, dmth., për shembull, duhej të ishte e ndaluar krijimi i cikleve.
Skema e përgjithshme e ekzekutimit të makinës është si më poshtë. Përdoruesi krijon një program për arkitekturën BPF dhe, duke përdorur një mekanizëm të kernelit (për shembull, thirrja për sistem), ngarkon dhe bashkangjit programin në një generaator eventi në kernel (për shembull, një ngjarje është ardhja e një pakete në kartën e rrjetit). Kur ndodh një ngjarje, kernel lanson programin (për shembull, në interpretues), kurse memoria e makinës përputhet me një regionin e memories së kernelit (për shembull, të dhënat e paketës së ardhur).
E thënë më lart, do na mjaftojë për të filluar të analizojmë shembujt: do të njohim sistemin dhe formatin e komandave sipas nevojës. Nëse dëshironi të studioni menjëherë sistemin e komandave të makinës virtuale dhe të njihni të gjitha mundësitë e saj, mund të lexoni artikullin origjinal dhe/apo gjysmën e parë të files nga dokumentacioni i bërthamës. Për më tepër, mund të studiojmë prezantimin , ku McCanne, një nga autorët e BPF, flet për historinë e krijimit libpcap.
Ne do të kalojmë në shqyrtimin e të gjitha shembujve thelbësorë të aplikimit të BPF-klasik në Linux: tcpdump (libpcap), seccomp, dhe klasifikuesi, . Në Linuxin bashkëkohor, programet klasike BPF automatikisht përkthehen në formën e re, megjithatë, nga këndvështrimi i përdoruesit, API mbeti i njëjtë dhe aplikacionet e reja të BPF klasike, si do ta shohim në këtë artikull, janë ende të pranishme. Për këtë shkak, si dhe për të kuptuar historinë e zhvillimit të BPF klasike në Linux, do të bëhet më e qartë se si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj pikërisht me një artikull mbi BPF klasike..
tcpdump
Zhvillimi i BPF Ă«shtĂ« zhvilluar paralelisht me zhvillimin e frontend-it pĂ«r filtrimin e paketave â utiliteteve tĂ« njohura tcpdump. Dhe, duke qenĂ« se ky Ă«shtĂ« shembulli mĂ« i vjetĂ«r dhe mĂ« i njohur i pĂ«rdorimit tĂ« BPF-klasik, i disponueshĂ«m nĂ« shumĂ« sisteme operative, ne do ta fillojmĂ« studimin e teknologjisĂ« prej tij.
(Të gjithë shembujt në këtë artikull i kam ekzekutuar në Linux 5.6.0-rc6. Rezultatet e disa komandave janë redaktuar për lehtësinë e lexueshmërisë.)
Shembulli: vëzhgojmë paketat IPv6
Le të imagjinojmë se duam të shohim të gjitha paketat IPv6 në interface eth0. Për këtë, mund të ekzekutojmë programin tcpdump me filtrin më të thjeshtë ip6:
$ sudo tcpdump -i eth0 ip6Dhe kështu tcpdump do të kompolojë filtrin ip6 në kod-bajt të arkitekturës BPF dhe do ta dërgojë në bërthamë (shih detajet në seksionin ). Filtri i ngarkuar do të ekzekutohet për çdo paketë që kalon përmes interface-it eth0. Nëse filtri kthen një vlerë jo zero n, atëherë deri n bytat e paketës do të kopjohen në hapësirën e përdoruesit dhe ne do ta shohim atë në daljen tcpdump.

Rrëfyer, ne mund të zbulojmë lehtësisht se cilin kod-bajt kemi dërguar në bërthamë tcpdump duke e ekzekutuar atë me opsionin tcpdump$ sudo tcpdump -i eth0 -d ip6 (000) ldh [12] (001) jeq #0x86dd jt 2 jf 3 (002) ret #262144 (003) ret #0 -d:
NĂ« rreshtin zero ne ekzekutojmĂ« komandĂ«nldh [12] , e cila interpretohet si "ngarko nĂ« regjistringjysmĂ«-pjese (16 bit), qĂ« ndodhet nĂ« adresĂ«n 12" dhe pyetja e vetme â çfarĂ« lloj memorjeje po adresojmĂ«? PĂ«rgjigja qĂ«ndron nĂ« faktin se adresa A fillon x -tĂ«n bajtĂ« e paketĂ«s rrjetĂ«s qĂ« po analizohet. Ne i lexojmĂ« paketat nga interface-i Ethernet, (x+1)nĂ« tĂ« cilin eth0, qĂ« paketa duket si nĂ« vijim (pĂ«r thjeshtĂ«si, ne supozojmĂ« se nĂ« paketĂ« nuk ka etiketa VLAN): 6 6 2 |MAC Adresa e Destinacionit|MAC Adresa e Burimit|Lloji i Ethernetit|...|
Pra, pasi tĂ« ekzekutohet komandanĂ« regjistrin , e cila interpretohet si "ngarko nĂ« regjistrin do tĂ« ndodhet fusha A Lloji i Ethernetit â lloji i paketĂ«s e dĂ«rguar nĂ« kĂ«tĂ« Ethernet-frame. NĂ« rreshtin 1 ne krahasojmĂ« pĂ«rmbajtjen e regjistrit (lloji i paketĂ«s) me A 0x86dd dhe kjo Ă«shtĂ«, qĂ« paketa duket si nĂ« vijim (pĂ«r thjeshtĂ«si, ne supozojmĂ« se nĂ« paketĂ« nuk ka etiketa VLAN): jt 2 jf 3 dhe â etiketat nĂ« tĂ« cilat duhet tĂ« kalojmĂ« nĂ« rast tĂ« krahasimit tĂ« suksesshĂ«m ( A == 0x86dd) dhe tĂ« dĂ«shtuar. Pra, nĂ« rast suksesi (IPv6) ne kalojmĂ« nĂ« rreshtin 2, ndĂ«rsa nĂ« rast dĂ«shtimi â nĂ« rreshtin 3. NĂ« rreshtin 3, programi mbyllet me kod 0 (mos kopjoni paketĂ«n), nĂ« rreshtin 2 programi mbyllet me kod 262144 (kopjoni maksimumi 256 kilobajtĂ« tĂ« paketĂ«s).Shembulli mĂ« i komplikuar: shohim paketat TCP sipas portit tĂ« destinacionit
Le të shohim se si duket filtri që kopjon të gjitha paketat TCP me portin e destinacionit 666. Ne do të shqyrtojmë rastin IPv4, sepse rasti IPv6 është më i thjeshtë. Pas shqyrtimit të këtij shembulli, ju mund të përdorni si një ushtrim të analizoni filtrin për IPv6 (
ip6 and tcp dst port 666) dhe filtrin për rastin e përgjithshëm (tcp dst port 666). Pra, filtri që na intereson duket si në vijim:$ sudo tcpdump -i eth0 -d ip and tcp dst port 666 (000) ldh [12] (001) jeq #0x800 jt 2 jf 10 (002) ldb [23] (003) jeq #0x6 jt 4 jf 10 (004) ldh [20] (005) jset #0x1fff jt 10 jf 6 (006) ldxb 4*([14]&0xf) (007) ldh [x + 16] (008) jeq #0x29a jt 9 jf 10 (009) ret #262144 (010) ret #0
ĂfarĂ« bĂ«jnĂ« rreshtat 0 dhe 1 ne e dimĂ« tashmĂ«. NĂ« rreshtin 2 ne kemi kontrolluar se Ă«shtĂ« paketa IPv4 (Lloji i Ethernetit =) dhe ngarkojmĂ« nĂ« regjistrin 0x80024-tĂ«n bajt tĂ« paketĂ«s. Paketa jonĂ« duket si A 14 8 1 1 |header-i ethernet|fushat ip|ttl|protokolli|...|
pra ne ngarkojmë në regjistrinfushën Protokolli të headers-IP, gjithashtu është logjike, sepse ne duam të kopjojmë vetëm paketat TCP. Ne krahasojmë Protokollin me A IPPROTO_TCP 0x6 (Në rreshtat 4 dhe 5 ne ngarkojmë gjysmë-fjalë që ndodhen në adresën 20, dhe me ndihmën e komandës
jset kontrollojmĂ« nĂ«se ndonjĂ« nga tre â nĂ« maskĂ«n e dhĂ«nĂ« janĂ« pastruar tre bitĂ«t mĂ« tĂ« vjetĂ«r. Dy bitĂ« nga tre na tregojnĂ« nĂ«se paketa Ă«shtĂ« pjesĂ« e njĂ« pakete IP tĂ« fragmentuar, dhe nĂ«se po, nĂ«se Ă«shtĂ« fragmen i fundit. BitĂ«t e tretĂ« janĂ« tĂ« rezervuar dhe duhet tĂ« jenĂ« zero. Ne nuk duam tĂ« kontrollojmĂ« as paketat jo tĂ« plota dhe as tĂ« dĂ«mtuara, prandaj kontrollojmĂ« tĂ« tre bitĂ«t. kontrollojmĂ« nĂ«se ndonjĂ« nga tre Rreshti 6 Ă«shtĂ« mĂ« interesanti nĂ« kĂ«tĂ« listĂ«. Shprehja
ldxb 4*([14]&0xf) do tĂ« thotĂ« se ne ngarkojmĂ« nĂ« regjistrin katĂ«r bitĂ«t mĂ« tĂ« rinj tĂ« bajtĂ«s sĂ« pesĂ«mbĂ«dhjetĂ« tĂ« paketĂ«s, tĂ« shumĂ«zuar me 4. KatĂ«r bitĂ«t mĂ« tĂ« rinj tĂ« bajtĂ«s sĂ« pesĂ«mbĂ«dhjetĂ« janĂ« fusha X GjatĂ«sia e Header-it tĂ« Internetit 4*([14]&0xf) Ă«shtĂ« njĂ« shenjĂ« e veçantĂ« e adresimit, e cila mund tĂ« pĂ«rdoret vetĂ«m nĂ« kĂ«tĂ« formĂ« dhe vetĂ«m pĂ«r regjistrin â kjo Ă«shtĂ« njĂ« shenjĂ« qĂ« tregon njĂ« skemĂ« adresimi tĂ« veçantĂ«, e cila mund tĂ« pĂ«rdoret vetĂ«m nĂ« kĂ«tĂ« formĂ« dhe vetĂ«m pĂ«r regjistrin X, domethen, ne mund te themi as ldb 4*([14]&0xf) as ldxb 5*([14]&0xf) (mund tĂ« specifikojmĂ« njĂ« offset tjetĂ«r, pĂ«r shembull, ldxb 4*([16]&0xf)). ĂshtĂ« e qartĂ« se kjo skemĂ« adresimi u shtua nĂ« BPF pikĂ«risht pĂ«r tĂ« marrĂ« nĂ« X (regjistri i indekseve) gjatĂ«si e header-it IPv4.
Pra, në rreshtin 7 po përpiqemi të ngarkojmë një fjalë gjysmë, në adresën (X+16). Duke kujtuar se 14 byte zë header-i i Ethernet-it, dhe X përmban gjatësi të header-it IPv4, kuptojmë se në A ngarkohet porta e destinacionit TCP:
14 X 2 2
|header ethernet|header ip|porta burimore|porta destinacioni|SĂ« fundi, nĂ« rreshtin 8 krahasojmĂ« portĂ«n e destinacionit me vlerĂ«n e kĂ«rkuar dhe nĂ« rreshtat 9 ose 10 kthejmĂ« rezultatin â tĂ« kopjojmĂ« paketĂ«n ose jo.
Tcpdump: ngarko
Në shembujt e mëparshëm ne qëllimisht nuk u ndalëm në detaje mbi mënyrën se si ngarkojmë BPF bytecode në bërthamë për filtrimin e paketave. Në përgjithësi, tcpdump është portuar në shumë sisteme dhe për të punuar me filtrat përdor bibliotekën . Në përmbledhje, për të vendosur filtrin në ndërfaqe duke përdorur libpcap, duhet të bësh si më poshtë:
- të krijosh një descriptor të tipit
pcap_tnga emri i ndërfaqes: , - të aktivizosh ndërfaqen: ,
- të kompilosh filtrin: ,
- të lidhësh filtrin: .
Për të parë se si funksioni pcap_setfilter është implementuar në Linux, ne përdorim strace (disa rreshta janë fshirë):
$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768) = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...Në dy rreshtat e parë të daljes ne krijojmë për të lexuar të gjitha frame-et Ethernet dhe e lidhim atë me ndërfaqen eth0. Nga ne e dimë se filtri ip do të përbëhet nga katër instruksione BPF, dhe në rreshtin e tretë ne shohim se si përmes opsionit thirrjes sistematike setsockopt ngarkojmë dhe lidhim filtrin me gjatësi 4. Ky është filtri ynë.
Vlen të përmendet se në BPF-in klasik, ngarkimi dhe lidhja e filtrit ndodhin gjithmonë si një operacion atomik, ndërsa në versionin e ri të BPF, ngarkimi i programit dhe lidhja e tij me gjeneruesin e ngjarjeve janë ndarë në kohë.
E vërteta e fshehur
Një version pak më të plotë të daljes duket kështu:
$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768) = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0xbeefbeefbeef}, 16) = 0
recvfrom(3, 0x7ffcad394257, 1, MSG_TRUNC, NULL, NULL) = -1 EAGAIN (Resurset e përkohshme të pa disponueshme)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...Siç u tha mĂ« sipĂ«r, ne ngarkojmĂ« dhe lidhim filtrin tonĂ« me socket-in nĂ« rreshtin 5, por çfarĂ« ndodh nĂ« rreshtat 3 dhe 4? Siç duket, kjo libpcap kujdeset pĂ«r ne â nĂ« mĂ«nyrĂ« qĂ« nĂ« daljen e filtrit tonĂ« tĂ« mos hyjnĂ« paketat, tĂ« cilat nuk i pĂ«rmbushin kushtet, biblioteka njĂ« filtrim ret #0 (tĂ« heqĂ« tĂ« gjitha paketat), e kthen socket-in nĂ« modin jo bllokues dhe pĂ«rpiqet tĂ« lexojĂ« tĂ« gjitha paketat qĂ« mund tĂ« kenĂ« mbetur nga filtrat e mĂ«parshĂ«m.
Pra, për të filtruar paketat në Linux duke përdorur BPF klasik, nevojitet një filtrim në formën e strukturës të tipit struct sock_fprog dhe një socket të hapur, pas të cilit filtri mund të lidhet me socket-in përmes thirrjes sistematike setsockopt.
ĂshtĂ« interesante se filtri mund tĂ« lidhet me çdo socket, jo vetĂ«m me ata raw. Ja programi qĂ« heq tĂ« gjitha, pĂ«rveç dy byte tĂ« parĂ« tĂ« tĂ« gjitha UDP datagramĂ«ve qĂ« hyjnĂ«. (Komentet i kam shtuar nĂ« kod, pĂ«r tĂ« mos ngarkuar artikullin.)
Më shumë për përdorimin setsockopt për lidhjen e filtrave shiko në , dhe për të shkruar filtrat tuaj të tipit struct sock_fprog pa ndihmën tcpdump do të flasim në seksionin .
BPF klasik dhe shekulli XXI
BPF u pĂ«rfshi nĂ« Linux nĂ« vitin 1997 dhe pĂ«r njĂ« kohĂ« tĂ« gjatĂ« mbeti njĂ« kafshĂ« pune libpcap pa ndonjĂ« ndryshim tĂ« veçantĂ« (ndryshime tĂ« specifikuara pĂ«r Linux, sigurisht, , por ato nuk ndryshuan pamjen globale). Shenjat e para tĂ« rĂ«ndĂ«sishme qĂ« BPF do tĂ« evoluonte u shfaqĂ«n nĂ« vitin 2011, kur Eric Dumazet propuzoi , duke shtuar nĂ« bĂ«rthamĂ« Just In Time Compiler â njĂ« pĂ«rkthyes pĂ«r tĂ« pĂ«rkthyer bytecode BPF nĂ« x86_64 kodin natyror.
JIT compiler ishte i pari në zinxhirin e ndryshimeve: në vitin 2012 mundësia për të shkruar filtrat për , duke përdorur BPF, në janar 2013 u moduli dhe klasifikuesi, që lejonte shkruarjen e rregullave për iptables përmes BPF, dhe në tetor 2013 u edhe një modul . Në Linuxin bashkëkohor, programet klasike BPF automatikisht përkthehen në formën e re, megjithatë, nga këndvështrimi i përdoruesit, API mbeti i njëjtë dhe aplikacionet e reja të BPF klasike, si do ta shohim në këtë artikull, janë ende të pranishme. Për këtë shkak, si dhe për të kuptuar historinë e zhvillimit të BPF klasike në Linux, do të bëhet më e qartë se si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj pikërisht me një artikull mbi BPF klasike., që lejonte të shkruaje klasifikues trafiku përmes BPF.
Shpejt do tĂ« shqyrtojmĂ« tĂ« gjithĂ« kĂ«to shembuj mĂ« nĂ« detaje, megjithatĂ« fillimisht do tĂ« jetĂ« e dobishme tĂ« mĂ«sojmĂ« tĂ« shkruajmĂ« dhe kompilohemi programe tĂ« rastĂ«sishme pĂ«r BPF, pasi mundĂ«sitĂ« qĂ« ofron biblioteka libpcap janĂ« tĂ« kufizuara (shembulli i thjeshtĂ«: filtri, i gjeneruar libpcap mund tĂ« kthejĂ« vetĂ«m dy vlera â 0 ose 0x40000) ose pĂ«r ndonjĂ« rast, si nĂ« rastin e seccomp, tĂ« papĂ«rdorshme.
Programimi i BPF me duar
Le të njihemi me formatin binar të instruksioneve BPF, ai është shumë i thjeshtë:
16 8 8 32
| code | jt | jf | k |Ădo udhĂ«zim merr 64 bit, ku 16 bit tĂ« parĂ« janĂ« kodi i komandĂ«s, ndjekur nga dy kthesa tĂ« tetĂ« bitĂ«ve, jt dhe jf, dhe 32 bit pĂ«r argumentin K, qĂ« ndryshon nĂ« varĂ«si tĂ« komandĂ«s. PĂ«r shembull, komanda ret, e cila ndalon ekzekutimin e programit ka kodin 6, ndĂ«rsa vlera e kthyer merret nga konstanta K. NĂ« gjuhĂ«n C, njĂ« instruktion BPF paraqitet si njĂ« strukturĂ«
struct sock_filter {
__u16 code;
__u8 jt;
__u8 jf;
__u32 k;
}ndërsa një program i tërë paraqitet si strukturë
struct sock_fprog {
unsigned short len;
struct sock_filter *filter;
}Kështu, tashmë mund të shkruajmë programe (kismet e instrukcioneve, le të themi se i dimë nga ). Kështu do të duket filtri ip6 nga :
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x000086dd },
{ 0x06, 0, 0, 0x00040000 },
{ 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog prog = {
.len = ARRAY_SIZE(code),
.filter = code,
};Programi prog ne mund të përdorim në thirrje
setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))TĂ« shkruash programe nĂ« formĂ«n e kodit tĂ« makinerisĂ« nuk Ă«shtĂ« shumĂ« e pĂ«rshtatshme, por ndonjĂ«herĂ« duhet (pĂ«r shembull, pĂ«r debug, krijimin e testeve unitare, shkruaj artikuj nĂ« habr dhe tĂ« tjerĂ«). PĂ«r lehtĂ«si, nĂ« file <linux/filter.h> janĂ« tĂ« definuara makrose ndihmĂ«se â shembulli i njĂ«jtĂ« mund tĂ« shkruhej si
struct sock_filter code[] = {
BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETH_P_IPV6, 0, 1),
BPF_STMT(BPF_RET|BPF_K, 0x00040000),
BPF_STMT(BPF_RET|BPF_K, 0),
}Megjithatë, as ky variant nuk është shumë i përshtatshëm. Kështu menduan edhe programuesit e bërthamës Linux dhe për këtë arsye në direktorinë të bërthamës mund të gjeni assembler dhe debagger për punë me BPF-në klasike.
Gjuha assembler është shumë e ngjashme me daljen debug tcpdump, por përveç kësaj mund të tregojmë etiketa simbolike. Për shembull, ja një program që heq të gjitha paketat, përveç TCP/IPv4:
$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0Në mënyrë default, assembleri gjeneron kod në formatin <numri i instrukcioneve>,<code1> <jt1> <jf1> <k1>,..., për shembullin tonë me TCP do të rezultojë
$ tools/bpf/bpf_asm /tmp/tcp-over-ipv4.bpf
6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 6,6 0 0 4294967295,6 0 0 0,Për lehtësi të programuesve C, mund të përdoret një format tjetër për dalje:
$ tools/bpf/bpf_asm -c /tmp/tcp-over-ipv4.bpf
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000006 },
{ 0x06, 0, 0, 0xffffffff },
{ 0x06, 0, 0, 0000000000 },Ky tekst mund të kopjohet në definicionin e strukturës së tipit struct sock_filter, ashtu siç kemi bërë në fillim të këtij kapitulli.
Zgjerimet Linux dhe netsniff-ng
Përveç instruksioneve standarde BPF, Linux dhe tools/bpf/bpf_asm përkrahin gjithashtu . Kryesisht, instruksionet shërbejnë për të aksesuar fushat e strukturës struct sk_buff, e cila përshkruan paketën rrjet në bërthamë. Megjithatë, ka edhe instruksione ndihmëse të llojeve të tjera, për shembull ldw cpu do të ngarkojë në regjistrin A rezultatin e thirrjes së funksionit të bërthamës raw_smp_processor_id(). (Në versionin e ri të BPF këto zgjerime jo standarde janë zgjeruar në formën e ofrimit të ndihmës për programet për qasje në memorie, struktura dhe gjenerimin e ngjarjeve.) Këtu është një shembull interesant i filtrit, ku kopjojmë vetëm headerat e paketimeve në hapësirën e përdoruesit, duke përdorur zgjerimin poff, offset-i i ngarkesës:
ld poff
ret aNuk do të mund të përdorim zgjerimet BPF në tcpdump, por kjo është një arsyetë e shkëlqyer për t'u njohur me paketën e mjeteve , e cila, përveç të gjithash, përmban një program të avancuar netsniff-ng, që, përveç filtrimit me ndihmën e BPF, përfshin gjithashtu një gjenerues efikas të trafikut, dhe më të avancuar se tools/bpf/bpf_asm, assemblerin BPF të quajtur bpfc. Paketa përmban dokumentacion mjaft të detajuar, shih gjithashtu lidhjet në fund të artikujt.
seccomp
Pra, tashmë dimë të shkruajmë programe BPF me kompleksitet të rastësishëm dhe jemi gati të shohim shembuj të rinj, i pari prej të cilëve është teknologjia seccomp, e cila lejon, me ndihmën e filtrave BPF, të menaxhojmë një numër dhe grupin e argumenteve të thirrjeve sistemike që janë të disponueshme për këtë proces dhe pasardhësit e tij.
Versioni i parĂ« i seccomp u shtua nĂ« bĂ«rthamĂ« nĂ« 2005 dhe nuk kishte popullaritet tĂ« madh, pasi ofronte vetĂ«m njĂ« mundĂ«si â pĂ«r tĂ« kufizuar grupin e thirrjeve sistemike, tĂ« disponueshme pĂ«r procesin, duke pĂ«rfshirĂ«: read, shkruaj, exit dhe sigreturn, dhe procesi qĂ« shkelte rregullat u vra me ndihmĂ« tĂ« SIGKILL. MegjithatĂ«, nĂ« 2012 nĂ« seccomp u shtua mundĂ«sia pĂ«r tĂ« pĂ«rdorur filtrat BPF, qĂ« lejojnĂ« pĂ«rcaktimin e grupit tĂ« thirrjeve sistemike tĂ« lejuara dhe madje kryerjen e kontrollit mbi argumentet e tyre. (ĂshtĂ« interesante se njĂ« nga pĂ«rdoruesit e parĂ« tĂ« kĂ«saj funksionaliteti ishte Chrome, dhe tani grupi nga Chrome po zhvillon mekanizmin KRSI, i bazuar nĂ« versionin e ri tĂ« BPF dhe qĂ« lejon personalizimin e Moduleve tĂ« SigurisĂ« tĂ« Linux.) Lidhje pĂ«r dokumentacionin shtesĂ« mund tĂ« gjenden nĂ« fund tĂ« artikullit.
Të theksojmë se në Habrë tashmë ka pasur artikuj mbi përdorimin e seccomp, ndoshta dikujt do t'i pëlqejë t'i lexojë ato para (ose në vend të) leximit të nënseksioneve të ardhshme. Në artikullin shtjellohen shembuj të përdorimit të seccomp, si versioni i vitit 2007 ashtu edhe versioni me përdorim të BPF (filtrat gjenerohen me libseccomp), flitet për lidhjen e seccomp me Docker, si edhe ofrohen shumë lidhje të dobishme. Në artikullin përshkruhet, veçanërisht, se si të shtoni lista të zeza ose të bardha të thirrjeve sistemike për demonët nën administrimin e systemd.
Pastaj do të shikojmë si të shkruajmë dhe ngarkojmë filtrat për seccomp në C të pastër dhe me ndihmën e bibliotekës libseccomp dhe çfarë avantazhesh dhe disavantazhesh ka çdo variant, përfundimisht do shohim si përdoret seccomp nga strace.
Shkruajmë dhe ngarkojmë filtrat për seccomp
Tani dimë të shkruajmë programet BPF dhe prandaj do të shikojmë fillimisht ndërfaqen programore të seccomp. Një filtër mund të vendoset në nivelin e procesit, në këtë rast të gjitha proceset fëmijë do të trashëgojnë kufizimet. Kjo bëhet nëpërmjet thirrjes sistemike :
seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)ku &filter â Ă«shtĂ« njĂ« tregues nĂ« strukturĂ«n qĂ« na Ă«shtĂ« njohur mĂ« parĂ« struct sock_fprog, dmth programin BPF.
Si dallohet programet për seccomp nga programet për soketë? Nga konteksti që kalon. Në rastin e soketëve, na kalonte një zonë memorje që përmbante paketën, ndërsa në rastin e seccomp na kalon një strukturë të tillë
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};KĂ«tu nr â Ă«shtĂ« numri i thirrjes sistemike qĂ« ekzekutohet, arch â arkitektura aktuale (pĂ«r kĂ«tĂ« mĂ« poshtĂ«), args â deri nĂ« gjashtĂ« argumente tĂ« thirrjes sistemike, dhe instruction_pointer â Ă«shtĂ« tregues nĂ« instrukcionin nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit qĂ« bĂ«ri kĂ«tĂ« thirrje sistemike. Pra, pĂ«r shembull, pĂ«r tĂ« ngarkuar numrin e thirrjes sistemike nĂ« regjistrin A duhet tĂ« thoni
ldw [0]Për programet seccomp ka edhe veçori të tjera, për shembull, qasja në kontekst është e mundur vetëm në nivelin e 32-bitëve dhe nuk mund të ngarkohet një gjysmë-ërë ose bajt - me përpjekjen për të ngarkuar filtrin ldh [0] thirrje sistemike seccomp do të kthejë EINVAL. Verifikimi i filtrave që ngarkohen bëhet nga funksioni i bërthamës. (Nga të qeshurat, në komitetin origjinal që shton funksionalitetin seccomp, në këtë funksion harrohej të shtohej leja për përdorimin e instrukcionit mod (rezi i mbetjeve) dhe tani ajo nuk është e disponueshme për programet seccomp BPF, pasi shtimi i saj ABI.)
Në parim, tani e dimë gjithçka që na nevojitet për të shkruajtur dhe lexuar programet seccomp. Zakonisht logjika e programit është si një listë e zezë ose e bardhë e thirrjeve sistemike, për shembull programi
ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0verifikon listën e zezë të katër thirrjeve sistemike me numrat 304, 176, 239, 279. Cilat janë këto thirrje sistemike? Ne nuk mund të themi me siguri, pasi nuk e dimë për cilën arkitekturë është shkruar programi. Prandaj autorët e seccomp të fillojmë të gjitha programet me verifikimin e arkitekturës (arkitektura aktuale është e specifikuar në kontekst si një fushë arch strukturat struct seccomp_data). Me verifikimin e arkitekturës fillimi i shembullit do të dukej si:
ld [4]
jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64dhe atëherë numrat tanë të thirrjeve sistemike do të merrnin disa vlera.
Shkruajmë dhe ngarkojmë filtrat për seccomp me ndihmën e libseccomp
Shkrimi i filtrave në kode makinerike ose për Assembler BPF ofron kontroll të plotë mbi rezultatin, por ndonjëherë është më e preferueshme të kemi kod të portueshëm dhe/ose të lexueshëm. Këtë na ndihmon biblioteka , e cila ofron një ndërfaqe standarde për shkruan filtrat e zeza ose të bardha.
Le të shkruajmë një program, i cili ekzekuton një skedar binar sipas zgjedhjes së përdoruesit, duke vendosur, paraprakisht, një listë të zezë të thirrjeve sistemike nga (programi është thjeshtuar për lexueshmëri, versioni i plotë mund të gjendet ):
#include <seccomp.h>
#include <unistd.h>
#include <err.h>
static int sys_numbers[] = {
__NR_mount,
__NR_umount2,
// ... Đ”ŃĐ” 40 ŃĐžŃŃĐ”ĐŒĐœŃŃ
ĐČŃĐ·ĐŸĐČĐŸĐČ ...
__NR_vmsplice,
__NR_perf_event_open,
};
int main(int argc, char **argv)
{
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
for (size_t i = 0; i < sizeof(sys_numbers)/sizeof(sys_numbers[0]); i++)
seccomp_rule_add(ctx, SCMP_ACT_TRAP, sys_numbers[i], 0);
seccomp_load(ctx);
execvp(argv[1], &argv[1]);
err(1, "execlp: %s", argv[1]);
}Fillimisht ne përcaktojmë një masiv sys_numbers të 40+ numrave të thirrjeve sistemike për të bllokuar. Pastaj, inicializojmë kontekstin ctx dhe i themi bibliotekës se duam të lejojmë (SCMP_ACT_ALLOW) të gjitha thirrjet sistemike si të paracaktuara (ndërtimi i listave të zeza është më i thjeshtë). Pastaj, një për një, ne shtojmë të gjitha thirrjet sistemike nga lista e zezë. Në përgjigje të thirrjes sistemike nga lista, kërkojmë SCMP_ACT_TRAP, në këtë rast seccomp do të dërgojë një sinjal në procesin SIGSYS me përshkrimin se cila thirrje sistemike e ka shkelur rregullat. Më në fund, ne ngarkojmë programin në bërthamë përmes seccomp_load, e cila do ta përkthejë programin dhe do ta lidhë atë me procesin përmes thirrjes sistemike seccomp(2).
Për të përfunduar me sukses kompilimin, programi duhet të lidhët me bibliotekën libseccomp, për shembull:
cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccompShembulli i një ekzekutimi të suksesshëm:
$ ./seccomp_lib echo ok
okShembulli i një thirrjeje sistemore të bllokuar:
$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Thirrje sistemore e gabuarPërdorim strace, për të mësuar më shumë:
$ sudo strace -e seccomp ./seccomp_lib mount -t bpf bpf /tmp
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=50, filter=0x55d8e78428e0}) = 0
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xboobdeadbeef, si_syscall=__NR_mount, si_arch=AUDIT_ARCH_X86_64} ---
+++ vrare nga SIGSYS (shkarkuar bërthamën) +++
Thirrje sistemore e gabuarnga ku mund të dimë se programi u mbyll për shkak të përdorimit të një thirrjeje sistemore të ndaluar mount(2).
Pra, ne kemi shkruar një filtër duke përdorur bibliotekën libseccomp, duke përfshirë kodin jo trivial në katër rreshta. Në shembullin e mësipërm, në prani të një numri të madh thirrjesh sistemore, koha e ekzekutimit mund të zvogëlohet dukshëm, pasi kontrolli është thjesht një listë krahasimesh. Për optimizimin, së fundmi në libseccomp është , i cili shton mbështetje për atributin e filtër SCMP_FLTATR_CTL_OPTIMIZE. Nëse vendosni këtë atribut të barabartë me 2, atëherë filtri do të konvertohet në një program të kërkimit binar.
Nëse dëshironi të shihni se si janë krijuar filtrat me kërkimin binar, atëherë shikoni në , që krijon këto programe në assembler BPF sipas një grupi numrash thirrjesh sistemore, për shembull:
$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, gabim
jgt #6, check8
jeq #1, gabim
jeq #3, gabim
ret #0x7fff0000
check8:
jeq #8, gabim
jeq #13, gabim
ret #0x7fff0000
gabim: ret #0Nuk do të jetë e mundur të shkruhet asgjë ndjeshëm më të shpejtë, pasi programet BPF nuk mund të bëjnë kalime subjektive (nuk mund të bëjmë, për shembull, jmp A ose jmp [label+X]) dhe kështu të gjitha kalimet janë statike.
seccomp dhe strace
TĂ« gjithĂ« e dinĂ« utilitarin strace â njĂ« mjet i domosdoshĂ«m pĂ«r tĂ« ekzaminuar sjelljen e proceseve nĂ« Linux. MegjithatĂ«, shumĂ« gjithashtu janĂ« tĂ« njohur me kur pĂ«rdoret ky utilitar. Kjo Ă«shtĂ« pĂ«r shkak se strace realizohet pĂ«rmes ptrace(2), dhe nĂ« kĂ«tĂ« mekanizĂ«m ne nuk mund tĂ« specifikojmĂ« se pĂ«r cilin grup thirrjesh sistemore duam tĂ« ndalojmĂ« procesin, pra, pĂ«r shembull, komandat
$ time strace du /usr/share/ >/dev/null 2>&1
real 0m3.081s
user 0m0.531s
sys 0m2.073sdhe
$ time strace -e open du /usr/share/ >/dev/null 2>&1
real 0m2.404s
user 0m0.193s
sys 0m1.800spunojnë për kohë të ngjashme, ndonëse në rastin e dytë ne dëshirojmë të ndjekim vetëm një thirrje sistemore.
Mundësi e re --seccomp-bpf, e cila u shtua në strace versionin 5.3, lejon të shpejtohet procesi disa herë dhe koha e fillimit nën ndjekjen e një thirrjeje sistemore tashmë është e krahasueshme me kohën e fillimit normal:
$ time strace --seccomp-bpf -e open du /usr/share/ >/dev/null 2>&1
real 0m0.148s
user 0m0.017s
sys 0m0.131s
$ time du /usr/share/ >/dev/null 2>&1
real 0m0.140s
user 0m0.024s
sys 0m0.116s(Këtu, sigurisht, ka një mashtrim të vogël pasi ne ndjekim jo thirrjen kryesore sistemore të kësaj komande. Nëse do të ndjekim, për shembull, newfsstat, atëherë strace do të ngadalësonte po aq shumë sa pa --seccomp-bpf.)
Si funksionon kjo opsion? Pa atë strace lidhet me procesin dhe e fillon atë përmes PTRACE_SYSCALL. Kur procesi i menaxhuar ekzekuton (cilëndo) thirrje sistemore, kontrolli i kalon strace, i cili shikon argumentet e thirrjes sistemore dhe e ekzekuton atë përmes PTRACE_SYSCALL. Pas një kohe, procesi përfundon thirrjen sistemore dhe në daljen e saj kontrolli përsëri kalon strace, i cili shikon vlerat e kthimit dhe e ekzekuton procesin përmes PTRACE_SYSCALL, etj.

PĂ«rmes seccomp, megjithatĂ«, ky proces mund tĂ« optimizohet pikĂ«risht ashtu si na pĂ«lqen. PĂ«rshtypja Ă«shtĂ«, nĂ«se duam tĂ« shohim vetĂ«m thirrjen sistemore X, atĂ«herĂ« mund tĂ« shkruajmĂ« njĂ« filtĂ«r BPF, i cili pĂ«r X kthen vlerĂ«n SECCOMP_RET_TRACE, dhe pĂ«r thirrjet qĂ« nuk janĂ« tĂ« interesit tonĂ« â SECCOMP_RET_ALLOW:
ld [0]
jneq #X, injoro
trace: ret #0x7ff00000
injoro: ret #0x7fff0000Në këtë rast strace fillimisht e nis procesin si PTRACE_CONT, në çdo thirrje sistemore ekzekutohet filtrojmë tonë, nëse thirrja sistemore nuk është X, atëherë procesi vazhdon punën, por nëse kjo është X, atëherë seccomp do të kalojë kontrollin strace, i cili do të shikojë argumentet dhe do të ekzekutojë procesin si PTRACE_SYSCALL (pasi në seccomp nuk ka mundësi për të ekzekutuar një program në daljen e thirrjes sistemore). Kur thirrja sistemore të kthehet, strace do të rinisë procesin përmes PTRACE_CONT dhe do të presë mesazhe të reja nga seccomp.

Kur përdoret opsioni --seccomp-bpf ka dy kufizime. Së pari, nuk mund të lidhemi me një proces ekzistues (opsioni -p programet strace), pasi kjo nuk mbështetet nga seccomp. Së dyti, nuk ka mundësi nuk për të parë proceset fëmijë, pasi filtrat seccomp trashëgohen nga të gjitha proceset fëmijë pa mundësi për t'i ç'nderuar ato.
Pak më shumë detaje se si përdoren mund të mësosh nga strace punon me seccomp një raport të fundit Tani, të kthehemi në botën e rrjeteve.
dhe klasifikuesi
Historia: shumë kohë më parë, në vitin 2007, në kernel u
xt_u32 moduli xt_u32 për netfilter. Ai u shkrua me analogji me një klasifikues më të lashtë të trafikut cls_u32 dhe lejonte të shkruhen rregulla binare të rastësishme për iptables duke përdorur operacione të thjeshta: ngarko 32 bit nga paketa dhe bëj një grup operacionesh aritmetike. Për shembull,
sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"Ngarko 32 bitĂ«t e kokĂ«s IP, duke filluar nga ofseti 6, dhe aplikon maskĂ«n 0xFF (merr bajtin mĂ« tĂ« vogĂ«l). Ky Ă«shtĂ« njĂ« fushĂ« protokolli e kokĂ«s IP dhe ne e krahasojmĂ« atĂ« me 1 (ICMP). NĂ« njĂ« rregull, mund tĂ« kombinohen shumĂ« kontrole, dhe gjithashtu mund tĂ« ekzekutohet operatori @ â kaloni X bajtĂ« djathtas. PĂ«r shembull, rregulli
iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"kontrollon nëse Numri i Rendit TCP 0x29. Nuk do t'i futem më tej detajeve, pasi tashmë është e qartë se të shkruash rregulla të tilla me dorë nuk është shumë e përshtatshme. Në artikullin , ka disa lidhje me shembuj të përdorimit dhe gjenerimit të rregullave për xt_u32. Shiko gjithashtu lidhjet në fund të këtij artikulli.
Në vitin 2013 moduli përveç modulit xt_u32 mund të përdoret një modul i bazuar në BPF dhe klasifikuesi. Të gjithë ata që kanë lexuar deri këtu tashmë duhet të kenë përmbushur parimin e funksionimit të tij: ekzekutimi i bytecode BPF si rregulla iptables. Një rregull të ri mund ta krijoni, për shembull, kështu:
iptables -A INPUT -m bpf --bytecode -j LOGkĂ«tu <баĐčŃĐșĐŸĐŽ> â kjo Ă«shtĂ« kodi nĂ« formatin e daljes sĂ« assemblerit bpf_asm siç Ă«shtĂ« me standard, pĂ«r shembull,
$ cat /tmp/test.bpf
ldb [9]
jneq #17, ignore
ret #1
ignore: ret #0
$ bpf_asm /tmp/test.bpf
4,48 0 0 9,21 0 1 17,6 0 0 1,6 0 0 0,
# iptables -A INPUT -m bpf --bytecode "$(bpf_asm /tmp/test.bpf)" -j LOGNĂ« kĂ«tĂ« shembull ne filtrojmĂ« tĂ« gjitha paketat UDP. Konteksti pĂ«r programin BPF nĂ« modul dhe klasifikuesi, natyrisht, tregon tĂ« dhĂ«nat e paketĂ«s, nĂ« rastin e iptables â nĂ« fillimin e kokĂ«s IPv4. Vlera e kthyer nga programi BPF , ku false do tĂ« thotĂ« qĂ« paketa nuk pĂ«rputhet.
ĂshtĂ« e qartĂ« se moduli dhe klasifikuesi mbĂ«shtet filtra shumĂ« mĂ« tĂ« sofistikuar sesa nĂ« shembullin mĂ« sipĂ«r. Le tĂ« shohim shembuj tĂ« vĂ«rtetĂ« nga kompaninĂ« Cloudfare. Deri para pak kohĂ«sh ata pĂ«rdornin modul dhe klasifikuesi pĂ«r mbrojtjen nga sulmet DDoS. NĂ« artikullin ata tregojnĂ« si (dhe pse) ata krijojnĂ« filtra BPF dhe publikojnĂ« lidhje pĂ«r njĂ« grup utilitar pĂ«r krijimin e filtrave tĂ« tillĂ«. PĂ«r shembull, me ndihmĂ«n e utilitarit bpfgen mund tĂ« krijoni njĂ« program BPF qĂ« ndĂ«rlidhet me DNS-kĂ«rkesĂ«n pĂ«r emrin habr.com:
$ ./bpfgen --assembly dns -- habr.com
ldx 4*([0]&0xf)
ld #20
add x
tax
lb_0:
ld [x + 0]
jneq #0x04686162, lb_1
ld [x + 4]
jneq #0x7203636f, lb_1
ldh [x + 8]
jneq #0x6d00, lb_1
ret #65535
lb_1:
ret #0Në program ne fillimisht ngarkojmë në regjistrin X adresën e fillimit të vargut x04habrx03comx00 brenda datagramës UDP dhe pastaj kontrollojmë kërkesën: 0x04686162 "x04hab" etj.
Pak më vonë Cloudfare publikoi kodin e kompilatorit p0f -> BPF. Në artikullin ata tregojnë se çfarë është p0f dhe si të kthejnë nënshkrimet p0f në BPF:
$ ./bpfgen p0f -- 4:64:0:0:*,0::ack+:0
39,0 0 0 0,48 0 0 8,37 35 0 64,37 0 34 29,48 0 0 0,
84 0 0 15,21 0 31 5,48 0 0 9,21 0 29 6,40 0 0 6,
...NĂ« momentin e tanishĂ«m Cloudfare nuk e pĂ«rdor mĂ« dhe klasifikuesi, pasi ata kaluan nĂ« XDP â njĂ« nga variantet e pĂ«rdorimit tĂ« versionit tĂ« ri BPF, shiko. .
. Në Linuxin bashkëkohor, programet klasike BPF automatikisht përkthehen në formën e re, megjithatë, nga këndvështrimi i përdoruesit, API mbeti i njëjtë dhe aplikacionet e reja të BPF klasike, si do ta shohim në këtë artikull, janë ende të pranishme. Për këtë shkak, si dhe për të kuptuar historinë e zhvillimit të BPF klasike në Linux, do të bëhet më e qartë se si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj pikërisht me një artikull mbi BPF klasike.
Shembulli i fundit i përdorimit të BPF klasik në bërthamë është klasifikuesi . Në Linuxin bashkëkohor, programet klasike BPF automatikisht përkthehen në formën e re, megjithatë, nga këndvështrimi i përdoruesit, API mbeti i njëjtë dhe aplikacionet e reja të BPF klasike, si do ta shohim në këtë artikull, janë ende të pranishme. Për këtë shkak, si dhe për të kuptuar historinë e zhvillimit të BPF klasike në Linux, do të bëhet më e qartë se si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj pikërisht me një artikull mbi BPF klasike. për sistemin e kontrollit të trafikut në Linux, i shtuar në Linux në fund të vitit 2013 dhe konceptualisht zëvendësoi të lashtin cls_u32.
MegjithatĂ«, nuk do tĂ« pĂ«rshkruajmĂ« tani funksionimin . NĂ« Linuxin bashkĂ«kohor, programet klasike BPF automatikisht pĂ«rkthehen nĂ« formĂ«n e re, megjithatĂ«, nga kĂ«ndvĂ«shtrimi i pĂ«rdoruesit, API mbeti i njĂ«jtĂ« dhe aplikacionet e reja tĂ« BPF klasike, si do ta shohim nĂ« kĂ«tĂ« artikull, janĂ« ende tĂ« pranishme. PĂ«r kĂ«tĂ« shkak, si dhe pĂ«r tĂ« kuptuar historinĂ« e zhvillimit tĂ« BPF klasike nĂ« Linux, do tĂ« bĂ«het mĂ« e qartĂ« se si dhe pse ajo ka evoluar nĂ« formĂ«n moderne, vendosa tĂ« filloj pikĂ«risht me njĂ« artikull mbi BPF klasike., pasi nga kĂ«ndvĂ«shtrimi i njohurive mbi BPF klasik ne nuk do tĂ« kemi ndonjĂ« informacion tĂ« ri â ne tashmĂ« e njohim tĂ« gjithĂ« funksionalitetin. PĂ«r mĂ« tepĂ«r, nĂ« artikujt e ardhshĂ«m, qĂ« flasin pĂ«r BPF tĂ« Zgjeruar, do tĂ« takohemi pĂ«rsĂ«ri me kĂ«tĂ« klasifikues.
Një arsye tjetër për të mos folur për përdorimin e BPF klasik është . Në Linuxin bashkëkohor, programet klasike BPF automatikisht përkthehen në formën e re, megjithatë, nga këndvështrimi i përdoruesit, API mbeti i njëjtë dhe aplikacionet e reja të BPF klasike, si do ta shohim në këtë artikull, janë ende të pranishme. Për këtë shkak, si dhe për të kuptuar historinë e zhvillimit të BPF klasike në Linux, do të bëhet më e qartë se si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj pikërisht me një artikull mbi BPF klasike. se krahasuar me BPF të Zgjeruar, në këtë rast fushëveprimi i aplikimeve reduktohet në mënyrë drastike: programet klasike nuk mund të ndryshojnë përmbajtjen e paketa dhe nuk mund të ruajnë gjendjen midis thirrjeve.
Pra, ka ardhur koha të lëmë pas BPF klasike dhe të shikojmë në të ardhmen.
Lamtumirë BPF klasik
Ne shikuam se si teknologjia BPF, e zhvilluar nĂ« fillim tĂ« viteve '90, arriti tĂ« jetojĂ« njĂ« çerek shekulli dhe vazhdoi tĂ« gjejĂ« aplikime tĂ« reja. MegjithatĂ«, si kalimi nga makinat me stack nĂ« RISC, qĂ« shĂ«noi fillimin e zhvillimit tĂ« BPF klasik, nĂ« vitet 2000 ndodhi kalimi nga makinat 32-bit nĂ« ato 64-bit dhe BPF klasik filloi tĂ« dalĂ« nga pĂ«rdorimi. PĂ«r mĂ« tepĂ«r, mundĂ«sitĂ« e BPF klasik janĂ« shumĂ« tĂ« kufizuara dhe pĂ«rveç arkitekturĂ«s sĂ« vjetruar â ne nuk kemi mundĂ«si tĂ« ruajmĂ« gjendjen midis thirrjeve tĂ« programeve BPF, nuk kemi mundĂ«si tĂ« ndĂ«rveprojmĂ« drejtpĂ«rdrejt me pĂ«rdoruesin, nuk kemi mundĂ«si tĂ« ndĂ«rveprojmĂ« me bĂ«rthamĂ«n, pĂ«rveç leximit tĂ« njĂ« numri tĂ« kufizuar fushash tĂ« strukturĂ«s sk_buff dhe ekzekutimin e funksioneve ndihmuese mĂ« tĂ« thjeshta, nuk mund tĂ« ndryshojmĂ« pĂ«rmbajtjen e paketa dhe t'i ridrejtojmĂ« ato.
Në të vërtetë, aktualisht nga BPF klasik në Linux ka mbetur vetëm interfaci i API, ndërsa brenda bërthamës të gjitha programet klasike, qofshin këto filtrat e soketëve ose filtrat seccomp, automatike përkthehen në formatin e ri, Extended BPF. (Ne do të flasim për mënyrën si ndodh kjo në artikullin e ardhshëm.)
Kalimi në arkitekturën e re filloi në vitin 2013, kur Alexey Starovoitov ofroi një diagram për azhurnimin e BPF. Në vitin 2014, patches përkatëse në bërthamë. Sipas kuptimit tim, fillimisht ishte planifikuar vetëm optimizimi i arkitekturës dhe JIT-compiler për një funksionim më efikas në makinat 64-bit, por përkundrazi, këto optimizime hapën një kapitull të ri në zhvillimin e Linux.
Artikujt e mëtejshëm në këtë seri do të flasin për arkitekturën dhe aplikimet e teknologjisë së re, e cila fillimisht ishte e njohur si internal BPF, pastaj extended BPF, dhe tani thjesht si BPF.
Linke
- Steven McCanne dhe Van Jacobson, "Filtri i Paketave BSD: Një Arkitekturë e Re për Kapjen e Paketave në Nivelin e Përdoruesit",
https://www.tcpdump.org/papers/bpf-usenix93.pdf - Steven McCanne, "libpcap: Një Arkitekturë dhe Metodologji Optimizimi për Kapjen e Paketave",
https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf tcpdump,libpcap:- .
- BPF â bytecode i harruar:
https://blog.cloudflare.com/bpf-the-forgotten-bytecode/ - Prezantimi i Mjetit BPF:
https://blog.cloudflare.com/introducing-the-bpf-tools/ bpf_cls:http://man7.org/linux/man-pages/man8/tc-bpf.8.html- Një pasqyrë e seccomp:
https://lwn.net/Articles/656307/ https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst- Paul Chaignon, "strace âseccomp-bpf: njĂ« shikim nĂ«n kapak",
https://fosdem.org/2020/schedule/event/debugging_strace_bpf/ netsniff-ng:http://netsniff-ng.org/
Burimi: habr.com
