Berkeley Packet Filters (BPF) Ă«shtĂ« njĂ« teknologji e bĂ«rthamĂ«s Linux, qĂ« Ă«shtĂ« nĂ« qendĂ«r tĂ« vĂ«mendjes nĂ« botimet teknike anglishtfolĂ«se tashmĂ« disa vite. Konferencat janĂ« tĂ« mbushura me prezantime mbi pĂ«rdorimin dhe zhvillimin e BPF. David Miller, mirĂ«mbajtĂ«si i nĂ«nsystemit rrjetĂ« tĂ« Linux, e quan prezantimin e tij nĂ« Linux Plumbers 2018 (XDP â Ă«shtĂ« njĂ« nga variantet e pĂ«rdorimit tĂ« BPF). Brendan Gregg mban prezantime me titullin . Toke HĂžiland-JĂžrgensen , qĂ« bĂ«rthama tani Ă«shtĂ« microkernel. Thomas Graf promovon idenĂ« se .
Në Habrë ende nuk ka një përshkrim sistematik të BPF, dhe prandaj në këtë seri artikujsh do të përpiqem të tregoj 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ë, artikullin e parë të ciklit, tregohet historia dhe arkitektura e BPF klasik, si dhe zbërthehen sekreti i principeve të funksionimit tcpdump, seccomp, strace, dhe shumë të tjera.
Zhvillimi i BPF kontrollohet nga komuniteti rrjetë të Linux, aplikimet 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 mbi historinë e BPF (c)
Teknologjia moderne BPF është një version i përmirësuar dhe i zgjeruar i teknologjisë së vjetër me të njëjtin emër, tani e quajtur, për të evituar konfuzionin, BPF klasike. Në bazë të BPF klasike është krijuar utiliteti i njohur tcpdump, mekanizmi seccomp, si dhe moduli më pak i njohur xt_bpf për iptables dhe klasifikuesi cls_bpf. Në Linuxin modern, programet klasike BPF automatikisht përkthehen në një formë të re, megjithatë, nga këndvështrimi i përdoruesit, API ka mbetur në vend, dhe aplikimet e reja të BPF klasike, si do të shohim në këtë artikull, ende ekzistojnë. Për këtë arsye, si dhe për shkak se duke ndjekur historinë e zhvillimit të BPF klasike në Linux, do të bëhet e qartë si dhe pse ajo ka evoluar në formën moderne, vendosa të filloj me artikullin mbi BPF klasike.
NĂ« fund tĂ« viteve '80 tĂ« shekullit tĂ« kaluar, inxhinierĂ«t nga laboratorĂ«t e njohur Lawrence Berkeley u interesuan pĂ«r problemin se si tĂ« filtronin nĂ« mĂ«nyrĂ« korrekte paketat e rrjetit nĂ« harduerin modern tĂ« asaj kohe. Ideja themelore e filtrimit, e implementuar fillimisht nĂ« teknologjinĂ« CSPF (Filtri i Paketimeve CMU/Stanford), ishte qĂ« tĂ« filtronin paketat e tepĂ«rta sa mĂ« herĂ«t, domethĂ«nĂ« nĂ« hapĂ«sirĂ«n e bĂ«rthamĂ«s, pasi kjo lejonte qĂ« tĂ« mos kopjohej informacion i tepĂ«rt nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit. PĂ«r tĂ« siguruar sigurinĂ« e kohĂ«s sĂ« ekzekutimit pĂ«r ekzekutimin e kodit tĂ« pĂ«rdoruesit nĂ« hapĂ«sirĂ«n e bĂ«rthamĂ«s, u pĂ«rdor njĂ« makinĂ« virtuale â njĂ« sandbox.
MegjithatĂ«, makinat virtuale pĂ«r filtrat ekzistues ishin projektuar pĂ«r t'u ekzekutuar nĂ« makina me arkitekturĂ« stack dhe nuk punonin aq efikasisht nĂ« makinat e reja RISC. Si rezultat i pĂ«rpjekjeve tĂ« inxhinierĂ«ve nga Berkeley Labs, u zhvillua njĂ« teknologji e re BPF (Filtrat e Paketimeve Berkeley), arkitektura e makinĂ«s virtuale tĂ« sĂ« cilĂ«s ishte projektuar mbi bazĂ«n e procesorit Motorola 6502 â njĂ« punĂ«tor i njohur pĂ«r produkte tĂ« tilla si ose . MakinĂ« e re virtuale rriti performancĂ«n e filtrave disa herĂ« nĂ« krahasim me zgjidhjet ekzistuese.
Arkitektura e makinës BPF
Ne do të njihemi me arkitekturën në punë, duke shqyrtuar shembuj. Megjithatë, përpara se të fillojmë, le të themi se makina kishte dy regjistra 32-bitë të disponueshëm për përdoruesin, një akumulator A dhe një regji indeksimi X, 64 byte memorje (16 fjalë), të disponueshme për shkrim dhe lexim të mëvonshëm, dhe një sistem të vogël komandash për të punuar me këto objekte. Në programet ishin të disponueshme gjithashtu instrukcionet e kalimit për implementimin e shprehjeve kushtetuese, megjithatë për të garantuar përfundimin e përshtatshëm të punës së programit, mund të kalonim vetëm përpara, dmth., në veçanti, ndalohej krijimi i cikleve.
Skema e pĂ«rgjithshme e ekzekutimit tĂ« makinĂ«s Ă«shtĂ« si vijon. PĂ«rdoruesi krijon njĂ« program pĂ«r arkitekturĂ«n BPF dhe, me anĂ« tĂ« ndonjĂ« mekanizmi tĂ« bĂ«rthamĂ«s (p.sh., thirrje sistemesh), ngarkon dhe lidh programin me ndonjĂ« gjenerator ngjarjesh nĂ« bĂ«rthamĂ« (p.sh., njĂ« ngjarje â Ă«shtĂ« ardhja e njĂ« pakete nĂ« kartĂ«n e rrjetit). Kur ndodh njĂ« ngjarje, bĂ«rthama ekzekuton programin (p.sh., nĂ« njĂ« interpretues), nĂ« kĂ«tĂ« rast, memorja e makinĂ«s pĂ«rputhet. ndonjĂ« regjioni i memories sĂ« bĂ«rthamĂ«s (p.sh., tĂ« dhĂ«nat e paketĂ«s sĂ« ardhur).
E thënë më sipër, kjo do të mjaftojë për të filluar me analizimin e shembujve: do të njihemi me 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ë mësoni për të gjitha mundësitë e saj, mund të lexoni artikullin origjinal. dhe/o pjesën e parë të skedarit nga dokumentacioni i bërthamës. Për më tepër, mund të shqyrtoni prezentimin , në të cilin McCanne, një nga autorët e BPF, diskuton për historinë e krijimit të tij. libpcap.
Ne kalojmë në shqyrtimin e të gjitha shembujve të rëndësishëm të përdorimit të BPF klasik në Linux: tcpdump (libpcap), seccomp, xt_bpf, cls_bpf.
tcpdump
Zhvillimi i BPF Ă«shtĂ« realizuar paralelisht me zhvillimin e front-end-it pĂ«r filtrimin e paketave â mjeti i njohur tcpdump. Dhe, pasi kjo Ă«shtĂ« shembulli mĂ« i vjetĂ«r dhe mĂ« i njohur i pĂ«rdorimit tĂ« BPF klasik, qĂ« Ă«shtĂ« i disponueshĂ«m nĂ« shumĂ« sisteme operative, do ta fillojmĂ« studimin e teknologjisĂ« prej tij.
(Të gjithë shembujt në këtë artikull i kam ekzekutuar në Linux 5.6.0-rc6. Daljet e disa komandave janë redaktuar për lehtësinë e lexueshmërisë.)
Shembull: vëzhgojmë paketat IPv6
Supozoni se dëshirojmë të shohim të gjitha paketat IPv6 në ndërfaqen eth0. Për këtë, mund të ekzekutojmë programin tcpdump me një filtër të thjeshtë ip6:
$ sudo tcpdump -i eth0 ip6Dhe tcpdump kompiloi filtrin ip6 në kodin e arkitekturës BPF dhe do ta dërgojë në bërthamë (shihni detajet në seksionin ). Filtri i ngarkuar do të ekzekutohet për çdo paketë që kalon përmes ndërfaqes eth0. Nëse filtri kthen një vlerë të jo zero n, atëherë deri n bytet e paketës do të kopjohen në hapësirën e përdoruesit dhe ne do ta shohim atë në dalje tcpdump.

Duket se mund të zbulojmë lehtësisht se cila është saktësisht kodi i bytit që dërguam në bërthamë tcpdump me anë të vetë tcpdump, nëse e ekzekutojmë atë me opsionin -d:
$ sudo tcpdump -i eth0 -d ip6
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 3
(002) ret #262144
(003) ret #0NĂ« rreshtin zero, ne ekzekutojmĂ« komandĂ«n ldh [12], qĂ« dekodohet si "ngarko nĂ« regjistrin A pjesĂ«n e fjalĂ«s (16 bit), qĂ« ndodhet nĂ« adresĂ«n 12" dhe pyetja e vetme Ă«shtĂ« â çfarĂ« lloj memorjeje po adresojmĂ«? PĂ«rgjigjja Ă«shtĂ« se nĂ« adresĂ«n x fillon (x+1)-i byt i paketĂ«s sĂ« rrjetit nĂ« analizĂ«. Ne lexojmĂ« paketat nga ndĂ«rfaqja Ethernet eth0, dhe kjo Ă«shtĂ« , kĂ«shtu qĂ« paketa duket si mĂ« poshtĂ« (pĂ«r thjeshtĂ«si ne supozojmĂ« se paketa nuk ka etiketat VLAN):
6 6 2
|Destination MAC|Source MAC|Ether Type|...|Pas njenes sĂ« ekzekutimit tĂ« komandĂ«s ldh [12] nĂ« regjistĂ«r A do tĂ« shfaqet fusha Ether Type â tipi i paketĂ«s qĂ« dĂ«rgohet nĂ« kĂ«tĂ« Ethernet frame. NĂ« rreshtin 1, ne krahasojmĂ« pĂ«rmbajtjen e regjistrit A (tipi i paketĂ«s) me 0x86dd, dhe kjo Ă«shtĂ« tipi qĂ« na intereson, IPv6. NĂ« rreshtin 1 pĂ«rveç komandĂ«s sĂ« krahasimit ka edhe dy kolona â jt 2 dhe jf 3 â etiketat tĂ« cilat duhet tĂ« kalojmĂ« nĂ« rastin e krahasimit tĂ« suksesshĂ«m (A == 0x86dd) dhe tĂ« pasuksesshĂ«m. Pra, nĂ« rastin e suksesshĂ«m (IPv6), kalojmĂ« nĂ« rreshtin 2, ndĂ«rsa nĂ« atĂ« tĂ« pasuksesshĂ«m â nĂ« rreshtin 3. NĂ« rreshtin 3 programi pĂ«rfundohet me kodin 0 (mos kopjo paketĂ«n), ndĂ«rsa nĂ« rreshtin 2 programi pĂ«rfundohet me kodin 262144 (kopjo mĂ« sĂ« shumti 256 kilobajt tĂ« paketĂ«s).
Një shembull më të komplikuar: shikojmë paketat TCP mbi portin e destinacionit
Le të shohim si duket filtrimi 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 studimit të këtij shembulli, mund të shqyrtoni si ushtrim 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 kështu:
$ 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 tashmĂ« e dimĂ«. NĂ« rreshtin 2, ne tashmĂ« kemi kontrolluar se Ă«shtĂ« njĂ« paketĂ« IPv4 (Ether Type = 0x800) dhe ngarkohet nĂ« regjistĂ«r A byte-i 24 i paketĂ«s. Paketa jonĂ« duket si
14 8 1 1
|ethernet header|ip fields|ttl|protocol|...dhe kështu ngarkohet në regjistër A fusha Protocol e titullit IP, që është e logjikshme, pasi duam të kopjojmë vetëm paketat TCP. Ne krahasojmë Protocol me 0x6 () në rreshtin 3.
NĂ« rreshtat 4 dhe 5, ne ngarkojmĂ« njĂ« fjalĂ« tĂ« plotĂ« qĂ« ndodhet nĂ« adresĂ«n 20, dhe me ndihmĂ«n e komandĂ«s jset kontrollojmĂ« nĂ«se ndonjĂ«ri nga tre â nĂ« maskĂ«n e dhĂ«nĂ« jset janĂ« pastruar tre bitĂ«t mĂ« tĂ« lartĂ«. Dy bitĂ« nga tre na tregojnĂ« nĂ«se paketa Ă«shtĂ« pjesĂ« e njĂ« pakete IP tĂ« fragmentuar, dhe nĂ«se po, nĂ«se Ă«shtĂ« fragmenti pĂ«rfundimtar. Bitin e tretĂ« e kemi rezervuar dhe duhet tĂ« jetĂ« zero. Ne nuk duam tĂ« kontrollojmĂ« as paketat jo tĂ« plota as tĂ« dĂ«mtuara, prandaj kontrollojmĂ« tĂ« tre bitĂ«t.
Rreshti 6 â Ă«shtĂ« mĂ« interesant nĂ« kĂ«tĂ« listim. Shprehja ldxb 4*([14]&0xf) do tĂ« thotĂ« se ngarkohemi nĂ« regjistĂ«r X katĂ«r bitĂ«t mĂ« tĂ« vogla tĂ« bajtit tĂ« pestĂ«mbĂ«dhjetĂ« tĂ« paketĂ«s, tĂ« shumzuara me 4. KatĂ«r bitĂ«t mĂ« tĂ« vogla tĂ« bajtit tĂ« pestĂ«mbĂ«dhjetĂ« janĂ« njĂ« fushĂ« e baĆit IPv4, ku ruhet gjatĂ«sia e krye nĂ« fjalĂ«, prandaj duhet shumĂ«zuar me 4. ĂshtĂ« interesante se shprehja 4*([14]&0xf) Ă«shtĂ« njĂ« shenjĂ« e skemĂ«s speciale tĂ« adresimit, e cila mund tĂ« pĂ«rdoret vetĂ«m nĂ« kĂ«tĂ« formĂ« dhe vetĂ«m pĂ«r regjistrin X, dmth. ne nuk mund tĂ« themi as ldb 4*([14]&0xf) as ldxb 5*([14]&0xf) (ne mund tĂ« citojmĂ« vetĂ«m njĂ« offset tjetĂ«r, p.sh. ldxb 4*([16]&0xf)). ĂshtĂ« evidente se kjo skemĂ« adresimi u shtua nĂ« BPF pikĂ«risht pĂ«r tĂ« marrĂ« nĂ« X (regjistri indeks) gjatĂ«sinĂ« e krye tĂ« IPv4.
Në këtë mënyrë, në rreshtin 7 ne përpiqemi të ngarkojmë gjysmën e një fjale, në adresën (X+16). Duke u kujtuar se 14 bajtë zënë krye Ethernet, dhe X përmban gjatësinë e krye të IPv4, ne e kuptojmë se në A ngarkohet porta e destinacionit TCP:
14 X 2 2
|krye ethernet|krye ip|porta e burimit|porta e destinacionit|Më në fund, në rreshtin 8, ne 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: ngarkimi
Në shembujt e mëparshëm, ne qëllimisht nuk u ndalëm në mënyrën se si ngarkojmë BPF bajtkodin në bërthamë për filtrimin e paketimeve. Në përgjithësi, tcpdump është portuar në shumë sisteme dhe për punën me filtrat përdor bibliotekën . Në përmbledhje, për të vendosur filtrin në interfisin përmes libpcap, duhet të bësh si në vijim:
- të krijosh një deskriptues të tipit
pcap_tnga emri i interfisit: , - të aktivizosh interfisin: ,
- të kompilosh filtrin: ,
- të lidhësh filtrin: .
Për të parë se si është zbatuar funksioni pcap_setfilter në Linux, ne përdorim strace (disa rreshta u hoqën):
$ 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ë gjithë framet Ethernet dhe e lidhnim atë me interfisin eth0. Nga ne e dimë se filtri ip do të përbëhet nga katër instruktime BPF, dhe në rreshtin e tretë shohim se si me opcionin thirrjes së sistemit setsockopt ne ngarkohet dhe lidhen filtrin e gjatë 4. Ky është filtri ynë.
ĂshtĂ« e rĂ«ndĂ«sishme tĂ« theksohet 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 gjeneratorin e ngjarjeve janĂ« tĂ« ndara nĂ« kohĂ«.
E vërteta e fshehur
Një versioni disi më i plotë i 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 (Burimi përkohësisht i paavailable)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...Siç u tha mĂ« parĂ«, ne ngarkojmĂ« dhe lidhim filtrin tonĂ« me soketin nĂ« rreshtin 5, por çfarĂ« ndodh nĂ« rreshtat 3 dhe 4? Duket se kjo libpcap e menaxhon pĂ«r ne â pĂ«r tĂ« siguruar qĂ« paketa qĂ« nuk i pĂ«rmbush kriteret, tĂ« mos kalojnĂ« nĂ« daljen tonĂ«, biblioteka njĂ« filtrin fals ret #0 (tĂ« heqĂ« tĂ« gjitha paketat), e çon soketin nĂ« modalitetin jo-bllokues dhe pĂ«rpiqet tĂ« lexojĂ« tĂ« gjitha paketat qĂ« mund tĂ« mbeteshin nga filtrat e kaluar.
Pra, për të filtruar paketat në Linux duke përdorur BPF-in klasik, është e nevojshme të keni një filtrin në formën e një strukture të tipit struct sock_fprog dhe një soket të hapur, pas së cilës filtri mund të lidhet me soketin përmes thirrjes sistemike setsockopt.
ĂshtĂ« interesante se filtri mund tĂ« lidhet me çdo soket, jo vetĂ«m me atĂ« raw. Ja programet, qĂ« tĂ« gjitha, pĂ«rveç dy bajtĂ«ve tĂ« parĂ« tĂ« tĂ« gjitha datagrameve UDP tĂ« ardhshme. (Komentet i kam shtuar nĂ« kod, pĂ«r tĂ« mos e mbingarkuar artikullin).
Detaje më shumë rreth përdorimit 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ë bisedojmë në seksionin .
BPF-i klasik dhe shekulli XXI
BPF u pĂ«rfshi nĂ« Linux nĂ« vitin 1997 dhe pĂ«r njĂ« kohĂ« tĂ« gjatĂ« mbeti njĂ« kalĂ« punĂ« libpcap pa ndryshime tĂ« mĂ«dha (ndryshime specifike pĂ«r Linux, sigurisht, , por ato nuk ndryshuan pamjen globale). Shenjat e para serioze se BPF do tĂ« evoluonte u shfaqĂ«n nĂ« vitin 2011, kur Eric Dumazet sugjeroi , duke shtuar njĂ« Kompilator Just In Time nĂ« bĂ«rthamĂ« â njĂ« pĂ«rkthyes pĂ«r tĂ« pĂ«rkthyer bytecode BPF nĂ« x86_64 kode tĂ« natyrshĂ«m.
Kompilatori JIT 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 ishte moduli xt_bpf, i lejon të shkruhen rregullat për iptables nëpërmjet BPF, dhe në tetor 2013 ishte edhe një moduli cls_bpf, që lejon shkrimin e klasifikuesve të trafikut me ndihmën e BPF.
Shpejt do t'i shqyrtojmĂ« tĂ« gjitha kĂ«to shembuj mĂ« nĂ« detaje, por sĂ« pari do tĂ« jetĂ« e dobishme tĂ« mĂ«sojmĂ« si tĂ« shkruajmĂ« dhe kompelojmĂ« programe tĂ« rastĂ«sishme pĂ«r BPF, pasi mundĂ«sitĂ« qĂ« ofron biblioteka libpcap janĂ« tĂ« kufizuara (njĂ« shembull i thjeshtĂ«: njĂ« filtrues i gjeneruar libpcap mund tĂ« kthejĂ« vetĂ«m dy vlera â 0 ose 0x40000) ose nĂ« tĂ« vĂ«rtetĂ«, si nĂ« rastin e seccomp, Ă«shtĂ« e papĂ«rshtatshme.
Programimi i BPF me duar tona
Të njohim formatin binar tëInstruksioneve BPF, ai është shumë i thjeshtë:
16 8 8 32
| kod | jt | jf | k |Ădo instruksion zĂ« 64 bita, ku 16 bita tĂ« parĂ« janĂ« kod komande, pasuar nga dy zhvendosje me tetĂ« bita, jt dhe jf, dhe 32 bita pĂ«r argumentin K, destinacioni i tĂ« cilit ndryshon nga komanda nĂ« komandĂ«. PĂ«r shembull, komanda kthehu, qĂ« pĂ«rfundon ekzekutimin e programit ka kod 6, dhe vlera e kthyer merret nga konstantja K. NĂ« gjuhĂ«n C njĂ« instruksion BPF paraqitet si njĂ« strukturĂ«
struct sock_filter {
__u16 code;
__u8 jt;
__u8 jf;
__u32 k;
}dhe njĂ« program i tĂ«rĂ« â si njĂ« strukturĂ«
struct sock_fprog {
unsigned short len;
struct sock_filter *filter;
}Kështu, ne tashmë mund të shkruajmë programe (kodet e instruksioneve ne, supozojmë, i dimë nga ). Kështu do të duket filtruesi 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 ligjërisht në thirrjen
setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))TĂ« shkruash programe nĂ« formĂ«n e kodit tĂ« makinĂ«s nuk Ă«shtĂ« shumĂ« e rehatshme, por ndonjĂ«herĂ« duhet (pĂ«r shembull, pĂ«r debuggimin, pĂ«r krijimin e testeve njĂ«si, pĂ«r shkruarjen e artikujve nĂ« Habr dhe tĂ« tjera). PĂ«r lehtĂ«sim nĂ« skedarin <linux/filter.h> definohen makroshtĂ«pĂ«njese â tĂ« njĂ«jtin shembull, si mĂ« sipĂ«r, mund ta shkruajmĂ« 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ë, edhe ky variant nuk është shumë i lehtë. Kështu arsyetuan edhe programuesit e bërthamës Linux dhe prandaj në direktorinë të bërthamës mund të gjeni assembler dhe debugger për punë me BPF klasik.
Gjuha assembler është shumë e ngjashme me rezultatin e debuggimit tcpdump, por përveç kësaj mund të tregojmë etiketat simbolike. Për shembull, këtu është një program që hedh të gjithë 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 #0Sipas parazgjedhjes, assembleri gjeneron kod në formatin , ,..., 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ësinë e programuesve C, mund të përdoret një format tjetër i daljes:
$ 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ë përkufizimin e strukturës së tipit struct sock_filter, ashtu siç e bëmë në fillim të këtij kapitulli.
Zgjerimet Linux dhe netsniff-ng
Përveç instruksioneve standarde BPF, Linux dhe tools/bpf/bpf_asm mbështesin gjithashtu . Kryesisht, instruksionet shërbejnë për qasje në fushat e strukturës struct sk_buff, e cila përshkruan një paketë rrjeti në bërthamë. Megjithatë, ka gjithashtu instruksione ndihmëse të një lloji tjetër, 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 u zgjeruan në formën e ofrimit të grupeve të ndihmësve të kernelit për qasje në memorie, struktura dhe për gjenerimin e ngjarjeve.) Ja një shembull interesant i filtrit, në të cilin kopjojmë në hapësirën e përdoruesit vetëm headerat e pakove, duke përdorur zgjerimin poff, offset-i i ngarkesës:
ld poff
ret aZgjerimet BPF nuk do të mund të përdoren në tcpdump, por ky është një arsyetim i mirë për t'u njohur me paketën e mjeteve , i cili, përveç të gjithave, përmban një program të avancuar netsniff-ng, i cili, përveç filtrimit duke përdorur BPF, përmban gjithashtu një gjenerator të trafikut efikas dhe një asamble BPF më të avancuar të quajtur tools/bpf/bpf_asmbpfc. Paketa përmban një dokumentacion bastante të detajuar, shih gjithashtu lidhjet në fund të artikullit.Pra, tashmë dimë të shkruajmë programe BPF të kompleksiteteve të ndryshme dhe jemi gati të shohim shembuj të rinj, i pari prej të cilëve është teknologjia seccomp, e cila lejon me anë të filtrave BPF të menaxhojmë shumë dhe grupin e argumenteve të thirrjeve të sistemit, të disponueshme për këtë proces dhe pasardhësit e tij.
seccomp
Versioni i parĂ« i seccomp u shtua nĂ« bĂ«rthamĂ« nĂ« vitin 2005 dhe nuk gĂ«zoi shumĂ« popullaritet, pasi ofronte vetĂ«m mundĂ«sinĂ« e vetme â kufizimin e grupit tĂ« thirrjeve tĂ« sistemit tĂ« disponueshme pĂ«r procesin, nĂ« vijim:
sigreturn read, write, exit dhe , dhe procesi, qĂ« shkelte rregullat, do tĂ« vritej me anĂ« tĂ«, dhe procesi, qĂ« shkelte rregullat u shkatĂ«rrua me SIGKILL. MegjithatĂ«, nĂ« vitin 2012, seccomp mori mundĂ«sinĂ« pĂ«r tĂ« pĂ«rdorur filtre BPF, qĂ« lejojnĂ« pĂ«rcaktimin e shumĂ« thirrjeve tĂ« lejuara tĂ« sistemit dhe madje kryerjen e kontrolleve mbi argumentet e tyre. (ĂshtĂ« interesante se njĂ« nga pĂ«rdoruesit e parĂ« tĂ« kĂ«saj funksionaliteti ishte Chrome, dhe aktualisht, njerĂ«zit nga Chrome po zhvillojnĂ« mekanizmin KRSI, i bazuar nĂ« njĂ« version tĂ« ri tĂ« BPF qĂ« lejon personalizimin e Moduleve tĂ« SigurisĂ« sĂ« Linuxit.) Lidhje pĂ«r dokumentacionin shtesĂ« mund tĂ« gjenden nĂ« fund tĂ« artikullit.
Vlen të theksohet se në Habrë tashmë ka pasur artikuj mbi përdorimin e seccomp, ndoshta dikush do të dëshirojë t'i lexojë ato para (ose në vend të) leximit të nënseksioneve të ardhshme. Në artikullin janë dhënë shembuj të përdorimit të seccomp, si versioni i vitit 2007, ashtu si dhe versioni që përdor BPF (filtrat gjenerohen me ndihmën e libseccomp), diskutohet lidhja e seccomp me Docker, si dhe janë dhënë shumë lidhje të dobishme. Në artikullin diskutohet, përveç kësaj, se si të shtoni lista të zeza ose të bardha të thirrjeve të sistemit për demonët nën menaxhimin e systemd.
Më pas do të shohim si të shkruajmë dhe ngarkojmë filtrat për seccomp në C të pastruar dhe me ndihmën e bibliotekës libseccomp dhe cilat janë avantazhet dhe disavantazhet e çdo varianti, dhe përfundimisht do të shikojmë si përdoret seccomp nga programi strace.
Shkruajmë dhe ngarkojmë filtrat për seccomp
Ne tashmë dimë si të shkruajmë programe BPF dhe kështu që do të shohim fillimisht ndërfaqen programore të seccomp. Filtri mund të vendoset në nivelin e procesit, ku të gjitha proceset fëmijë do të trashëgojnë kufizimet. Kjo bëhet përmes thirrjes së sistemit :
seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)ku &filter â Ă«shtĂ« njĂ« tregues nĂ« strukturĂ«n qĂ« tashmĂ« e njohim struct sock_fprog, dmth. programi BPF.
ĂfarĂ« ndryshon midis programeve pĂ«r seccomp dhe atyre pĂ«r soketet? Konteksti i transmetuar. NĂ« rastin e soketeve, na u dha njĂ« zonĂ« memorjeje qĂ« pĂ«rmbante njĂ« paketĂ«, ndĂ«rsa nĂ« rastin e seccomp, na jepet njĂ« strukturĂ« e tipit
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};KĂ«tu nr â Ă«shtĂ« numri i thirrjes sĂ« sistemit qĂ« po ekzekutohet, arch â arkitektura aktuale (pĂ«r kĂ«tĂ« mĂ« poshtĂ«), args â deri nĂ« gjashtĂ« argumente tĂ« thirrjes sĂ« sistemit, dhe instruction_pointer â Ă«shtĂ« njĂ« tregues i njĂ« udhĂ«zimi nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit qĂ« bĂ«ri kĂ«tĂ« thirrje tĂ« sistemit. Pra, pĂ«r shembull, pĂ«r tĂ« ngarkuar numrin e thirrjes sĂ« sistemit nĂ« regjistĂ«r A duhet tĂ« themi
ldw [0]Për programet seccomp ekzistojnë dhe veçori të tjera, për shembull, akses në kontekstin është e mundur vetëm për mbushjen 32-bit dhe nuk lejohet ngarkimi i gjysmë fjali apo bajt - në përpjekje për të ngarkuar filtrin ldh [0] thirrje sistemike seccomp will return EINVAL. Kontrollin e filtrave të ngarkuara e kryen funksioni i bërjes. (Nga të qeshurat, në commit-in origjinal që shtoi funksionalitetin seccomp, u harrua të shtohej miratimi për përdorimin e udhëzimit mod (mbetja nga ndarja) dhe tani ajo nuk është e disponueshme për programet seccomp BPF, pasi shtimi i saj ABI.)
Në parim, ne tashmë e dimë gjithçka për të shkruar dhe lexuar programet seccomp. Zakonisht logjika e programit është e organizuar si një listë të bardhë ose të zezë të thirrjeve të sistemit, 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 #0kontrollon listën e zezë të katër thirrjeve të sistemit me numra 304, 176, 239, 279. Cila është kjo thirrje e sistemit? Ne nuk mund të themi saktësisht, pasi nuk dimë për cilën arkitekturë është shkruar programi. Prandaj autorët e seccomp duhet të fillojnë të gjitha programet me kontrollin e arkitekturës (arkitektura aktuale është e specifikuar në kontekst si një fushë arch struktura struct seccomp_data). Me kontrollin 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 të sistemit do të merrnin vlera të caktuara.
Shkruajmë dhe ngarkojmë filtrat për seccomp duke përdorur libseccomp
Shkrimi i filtrave në kodin e makinës ose për assembler BPF lejon që të kemi kontroll të plotë mbi rezultatin, por njëkohësisht ndonjëherë është e preferuar të kemi kod të transportueshëm dhe/ose të lexueshëm. Në këtë na ndihmon biblioteka , e cila ofron një ndërfaqe standarde për të shkruar filtrat e zeza ose të bardha.
Le të shkruajmë, për shembull, një program që ekzekuton një skedë binar sipas zgjedhjes së përdoruesit, duke vendosur, paraprakisht, një listë të zezë të thirrjeve të sistemit nga (programi është i thjeshtuar për lexueshmëri më të madhe, 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ë array sys_numbers me më shumë se 40 numra thirrjesh të sistemit për të bllokuar. Pastaj, inicializojmë kontekstin ctx dhe i themi bibliotekës që duam të lejojmë (SCMP_ACT_ALLOW) të gjitha thirrjet sistemore ndaj parazgjedhjes (ndërtimi i listave të bardha është më i thjeshtë). Pastaj, një nga një, ne shtojmë të gjitha thirrjet sistemore në listën e zezë. Si reagim ndaj thirrjes sistemore nga lista, ne 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 sistemore ka shkelur rregullat. Në fund, ne ngarkojmë programin në bërthamë përmes seccomp_load, e cila do të kompilohet programin dhe do ta lidhë atë me procesin përmes thirrjes sistemore seccomp(2).
Për të kompaktuar me sukses programin, duhet ta lidhim 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 keqePërdorni 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} ---
+++ u ndal nga SIGSYS (bërë dump të bërthamës) +++
Thirrje sistemore e keqeku mund të mësojmë se programi u mbyll për shkak të përdorimit të thirrjes sistemore të ndaluar mount(2).
Pra, kemi shkruar një filtrues përmes bibliotekës libseccomp, duke përfshirë kodin jo trivial në katër rreshta. Në shembullin më sipër, me një numër të madh thirrjesh sistemore, koha e ekzekutimit mund të zvogëlohet ndjeshëm, pasi verifikimi është thjesht një listë krahasimesh. Për optimizim, kohët e fundit në libseccomp është , që shton mbështetje për atributin e filtrit SCMP_FLTATR_CTL_OPTIMIZE. Nëse e vendosni këtë atribut në 2, atëherë filtri do të kthehet në një program për kërkimin binar.
Nëse dëshironi të shihni si janë filtrat me kërkim binar, atëherë shikoni , që gjeneron këto programe në assembler BPF me një grup numrash thirrjesh sistemore, për shembull:
$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, keq
jgt #6, kontrollo8
jeq #1, keq
jeq #3, keq
ret #0x7fff0000
kontrollo8:
jeq #8, keq
jeq #13, keq
ret #0x7fff0000
keq: ret #0Nuk ka asgjë thelbësisht më të shpejtë për të shkruar, pasi programet BPF nuk mund të bëjnë kalime në atë mënyrë (nuk mund të bëjmë, për shembull, jmp A ose jmp [label+X]) dhe prandaj të gjitha kalimet janë statike.
seccomp dhe strace
TĂ« gjithĂ« e dinĂ« utilitarin strace â njĂ« instrument i padiskutueshĂ«m nĂ« hetimin e sjelljes sĂ« proceseve nĂ« Linux. MegjithatĂ«, shumĂ« gjithashtu kanĂ« dĂ«gjuar pĂ«r nĂ« pĂ«rputhje me kĂ«tĂ« vegĂ«l. E vĂ«rteta Ă«shtĂ« se strace implementuar pĂ«rmes ptrace(2), dhe nĂ« kĂ«tĂ« mekanizĂ«m nuk mund tĂ« specifikojmĂ« se nĂ« cilĂ«n sĂ«rĂ« thirrjesh sistemore duam tĂ« ndalim 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.800sarrihen në një kohë të ngjashme, edhe pse në rastin e dytë ne duam të ndjekim vetëm një thirrje sistemore.
Opsioni i ri --seccomp-bpf, e cila është shtuar në strace versionin 5.3, lejon të përshpejtohet procesi shumëfish dhe koha e nisjes nën ndjekje të një thirrjeje sistemore tashmë është e krahasueshme me kohën e një nisjeje normale:
$ 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 në faktin se ne po ndjekim jo thirrjen sistemore kryesore të kësaj komande. Sikur të ndjekim, për shembull, newfsstat, nuk është e nevojshme, madje, ajo jep një gabim sintaksor strace do të ngadalësonte po aq shumë sa edhe pa --seccomp-bpf.)
Si funksionon kjo mundësi? Pa të strace lidhet me procesin dhe e nis atë përmes PTRACE_SYSCALL. Kur procesi i menaxhuar nis një (çdo) thirrje sistemore, kontrolli kalon strace, i cili shikon argumentet e thirrjes sistemore dhe e nis atë përmes PTRACE_SYSCALL. Pas një kohe, procesi përfundon thirrjen sistemore dhe kur del nga ajo, kontrolli përsëri kalon strace, i cili shikon vlerat e kthimit dhe nis procesin përmes PTRACE_SYSCALL, etj.

PĂ«rmes seccomp, megjithatĂ«, ky proces mund tĂ« optimizohet pikĂ«risht siç do tĂ« dĂ«shironim. Konkretisht, nĂ«se duam tĂ« shohim vetĂ«m thirrjen sistemore X, mund tĂ« shkruajmĂ« njĂ« filtrin BPF, i cili pĂ«r X kthen vlerĂ«n SECCOMP_RET_TRACE, ndĂ«rsa pĂ«r thirrjet qĂ« nuk na interesojnĂ« â SECCOMP_RET_ALLOW:
ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000Në këtë rast strace fillimisht nis procesin si PTRACE_CONT, për çdo thirrje sistemore ekzekutohet filtrin ynë, nëse thirrja sistemore nuk është X, atëherë procesi vazhdon punën, por nëse është X, atëherë seccomp do të kalojë kontrollin strace, i cili do të shikojë argumentet dhe do të nisë procesin si PTRACE_SYSCALL (pasi në seccomp nuk ka mundësi për të nisur një program kur del nga thirrja sistemore). Kur thirrja sistemore të kthehet, strace do ta rinisë procesin përmes PTRACE_CONT dhe do të presë mesazhe të reja nga seccomp.

Nën përdorimin e mundësisë --seccomp-bpf ka ka dy kufizime. Së pari, nuk është e mundur të bashkohesh me një proces ekzistues (opsioni -p për punë me lloje të ndryshme grafikë. Për iOS ka mjaft shumë prej tyre. strace), pasi kjo nuk mbështetet nga seccomp. Së dyti, nuk ka mundësi jo të shikosh proceset fëmijë, pasi filtrat seccomp trashëgohen nga të gjithë proceset fëmijë pa mundësi për t'i çaktivizuar ato.
Pak më shumë hollësi mbi se si saktësisht strace punon me seccomp mund të merret nga . Për ne, fakti më interesant është se klasa e BPF me emrin seccomp vazhdon të ketë përdorim edhe sot.
xt_bpf
Tani le të kthehemi në botën e rrjeteve.
Historiku: shumë kohë më parë, në vitin 2007, në bërthamë u moduli xt_u32 për netfilter. Ai ishte shkruar sipas analogjisë me klasifikuesin e trafikut më të lashtë cls_u32 dhe lejonte të shkruheshin rregulla binare të rastësishme për iptables duke përdorur operacione të thjeshta: ngarko 32 bit nga paketa dhe kryej me ta 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Ă« e headerit IP, duke filluar nga offset 6, dhe aplikon maskĂ«n 0xFF (merr bajtin mĂ« tĂ« vogĂ«l). Ky Ă«shtĂ« njĂ« fushĂ« protocol e headerit IP dhe ne e krahasojmĂ« atĂ« me 1 (ICMP). NĂ« njĂ« rregull mund tĂ« kombinohen shumĂ« verifikime, dhe gjithashtu mund tĂ« kryhet operatori @ â kaloni X byte nĂ« tĂ« djathtĂ«n. PĂ«r shembull, rregulli
iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"kontrollon nëse Numri i Sekuencës TCP 0x29. Nuk do të thellohesha më tej, pasi tashmë është e qartë që të shkruash këto rregulla manualisht nuk është shumë e përshtatshme. Në artikullin , ka disa lidhje me shembuj të përdorimit dhe gjenerimit të rregullave për xt_u32. Shih gjithashtu lidhjet në fund të këtij artikulli.
Që nga viti 2013, moduli në vend të modulit xt_u32 mund të përdoret një modul i bazuar në BPF. xt_bpfTë gjithë ata që e lexuan deri këtu duhet të kenë kuptuar principin e funksionit të tij: ekzekutimi i kodit të BPF si rregulla iptables. Një rregull të ri mund të krijohet, për shembull, kështu:
iptables -A INPUT -m bpf --bytecode -j LOGkĂ«tu <баĐčŃĐșĐŸĐŽ> â ky Ă«shtĂ« kodi nĂ« formatin e daljes sĂ« assembler bpf_asm nĂ« mĂ«nyrĂ« default, 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Ă« modulin xt_bpf, sigurisht, tregon tĂ« dhĂ«nat e paketĂ«s, nĂ« rastin e iptables â nĂ« fillimin e headerit IPv4. Vlera e kthyer nga programi BPF index false tregon se paketa nuk pĂ«rputhet.
E qartë se moduli xt_bpf mbështet filtre më komplekse se në shembullin e mësipërm. Le të shikojmë shembujt e vërtetë nga kompania Cloudfare. Deri kohët e fundit ata përdornin modulin xt_bpf për mbrojtjen nga sulmet DDoS. Në artikullin ata tregojnë si (dhe pse) gjenerojnë filtre BPF dhe publikojnë lidhje në një set mjetesh për krijimin e atyre filtrave. Për shembull, me ndihmën e mjeteve bpfgen mund të krijoni një program BPF që përputhet me kërkesën DNS 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ë regjistër X adresën e fillimit të vargut x04habrx03comx00 brenda UDP-datagramit dhe pastaj kontrollojmë kërkesën: 0x04686162 "x04hab" etj.
Pak më vonë Cloudfare publikoi kodin e kompilatorit p0f -> BPF. Në artikullin ata flasin për atë se çfarë është p0f dhe si të konvertojnë 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Ă« aktualitet Cloudfare nuk e pĂ«rdor mĂ« xt_bpf, pasi ata kaluan nĂ« XDP â njĂ« nga opsionet pĂ«r tĂ« pĂ«rdorur versionin e ri tĂ« BPF, shih. .
cls_bpf
Shembulli i fundit i përdorimit të BPF klasik në bërthamë është klasifikuesi cls_bpf për nënsystemin e kontrollit të trafikut në Linux, i shtuar në Linux në fund të vitit 2013 dhe konceptualisht zëvendësoi të vjetrin cls_u32.
MegjithatĂ«, ne nuk do ta pĂ«rshkruajmĂ« tani funksionimin e cls_bpf, sepse nga kĂ«ndvĂ«shtrimi i njohurive mbi BPF klasik kjo nuk do tĂ« na sjellĂ« asgjĂ« â ne tashmĂ« kemi njohur tĂ« gjithĂ« funksionalitetin. PĂ«r mĂ« tepĂ«r, nĂ« artikujt e ardhshĂ«m qĂ« flasin pĂ«r BPF tĂ« Zgjeruar, ne do tĂ« hasim sĂ«rish kĂ«tĂ« klasifikues.
Një tjetër arsye për të mos folur për përdorimin e BPF klasik me cls_bpf është se krahasuar me BPF të Zgjeruar, në këtë rast fusha e aplikueshmërisë radikalisht ngushtohet: programet klasike nuk mund të ndryshojnë përmbajtjen e paketimeve dhe nuk mund të ruajnë gjendjen mes thirrjeve.
Kështu që ka ardhur koha të përshëndetemi me BPF klasike dhe të shohim të ardhmen.
Lamtumirë BPF klasik
Ne kemi shikuar se si teknologjia BPF, e zhvilluar nĂ« fillim tĂ« viteve â90, ka mbijetuar pĂ«r njĂ« çerek shekulli dhe vazhdimisht ka gjetur aplikime tĂ« reja. MegjithatĂ«, ashtu si kalimi nga makinat me stekĂ« nĂ« RISC, qĂ« shĂ«rbeu si njĂ« nxitje pĂ«r zhvillimin e BPF klasic, nĂ« vitet 2000 ndodhi kalimi nga makinat 32-bit nĂ« ato 64-bit dhe BPF klasic filloi tĂ« mbetet pas. PĂ«rveç kĂ«saj, mundĂ«sitĂ« e BPF klasic janĂ« tĂ« kufizuara dhe pĂ«rveç arkitekturĂ«s sĂ« tejkaluar - nuk kemi mundĂ«si pĂ«r tĂ« ruajtur gjendjen midis thirrjeve tĂ« programeve BPF, nuk ka mĂ«nyrĂ« pĂ«r ndĂ«rveprim tĂ« drejtpĂ«rdrejtĂ« me pĂ«rdoruesin, nuk ka mundĂ«si pĂ«r ndĂ«rveprim me bĂ«rthamĂ«n, pĂ«rveç leximit tĂ« njĂ« numri tĂ« kufizuar tĂ« fushave tĂ« strukturĂ«s. sk_buff dhe pĂ«r tĂ« drejtuar funksione ndihmĂ«s tĂ« thjeshta, nuk Ă«shtĂ« e mundur ndryshimi i pĂ«rmbajtjes sĂ« pakove dhe ridrejtoni ato.
Në të vërtetë, në këtë moment nga BPF klasic në Linux ka mbetur vetëm ndërfaqja API, ndërsa brenda bërthamës të gjitha programet klasike, qofshin ato filtra soketesh apo filtra seccomp, automatikisht përkthehen në formatin e ri, BPF i Zgjatur. (Do të flasim rreth se si ndodh kjo në artikullin e ardhshëm.)
Kalimi në arkitekturën e re filloi në vitin 2013, kur Aleksei Starovoitov propozoi një skemë për përmirësimin e BPF. Në vitin 2014, patches përkatëse në bërthamë. Sa e kuptoj unë, fillimisht ishte planifikuar vetëm optimizimi i arkitekturës dhe kompiluesit JIT për një punë më efektive në makinat 64-bit, por përkundrazi, këto optimizime çuan në një kapitull të ri në zhvillimin e Linux.
Artikujt e ardhshë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 BPF i brendshëm, pastaj BPF i zgjatur, dhe tani thjesht si BPF.
Linket
- Steven McCanne dhe Van Jacobson, "Filtri i Paketave BSD: Një Arkitekturë e Re për Kapjen e Paketave në Nivelin Përdorues",
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ë mbi 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
