BPF për të vegjlit, pjesa zero: BPF klasik

Berkeley Packet Filters (BPF) është një teknologji kernel Linux që ka qenë në faqet e para të botimeve të teknologjisë në gjuhën angleze për disa vite tani. Konferencat janë të mbushura me raporte mbi përdorimin dhe zhvillimin e BPF. David Miller, mirëmbajtësi i nënsistemit të rrjetit Linux, e quan fjalimin e tij në Linux Plumbers 2018 "Kjo bisedë nuk ka të bëjë me XDP" (XDP është një rast përdorimi për BPF). Brendan Gregg mban fjalime me titull Superfuqitë e Linux BPF. Toke Høiland-Jørgensen qeshse kerneli tani është një mikrokernel. Thomas Graf promovon idenë se BPF është javascript për kernelin.

Nuk ka ende një përshkrim sistematik të BPF në Habré, dhe për këtë arsye në një seri artikujsh do të përpiqem të flas për historinë e teknologjisë, të përshkruaj arkitekturën dhe mjetet e zhvillimit dhe të përshkruaj fushat e aplikimit dhe praktikës së përdorimit të BPF. Ky artikull, zero, në seri, tregon historinë dhe arkitekturën e BPF klasike, dhe gjithashtu zbulon sekretet e parimeve të tij të funksionimit. tcpdump, seccomp, strace, edhe me shume.

Zhvillimi i BPF kontrollohet nga komuniteti i rrjeteve Linux, aplikacionet kryesore ekzistuese të BPF janë të lidhura me rrjetet dhe për këtë arsye, me leje @eucariot, e quajta serialin “BPF për të vegjlit”, për nder të serialit të madh "Rrjetet për të vegjlit".

Një kurs i shkurtër në 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, që tani quhet BPF klasik për të shmangur konfuzionin. Një mjet i njohur u krijua bazuar në BPF klasik tcpdump, mekanizëm seccomp, si dhe module më pak të njohura xt_bpf për iptables dhe klasifikues cls_bpf. Në Linux-in modern, programet klasike BPF përkthehen automatikisht në formën e re, megjithatë, nga pikëpamja e përdoruesit, API ka mbetur në vend dhe përdorime të reja për BPF klasike, siç do të shohim në këtë artikull, janë ende duke u gjetur. Për këtë arsye, dhe gjithashtu sepse duke ndjekur historinë e zhvillimit të BPF klasik në Linux, do të bëhet më e qartë se si dhe pse u zhvillua në formën e tij moderne, vendosa të filloj me një artikull rreth BPF klasike.

Në fund të viteve tetëdhjetë të shekullit të kaluar, inxhinierët nga Laboratori i famshëm Lawrence Berkeley u interesuan për pyetjen se si të filtrohen siç duhet paketat e rrjetit në pajisje moderne në fund të viteve tetëdhjetë të shekullit të kaluar. Ideja bazë e filtrimit, e zbatuar fillimisht në teknologjinë CSPF (CMU/Stanford Packet Filter), ishte filtrimi i paketave të panevojshme sa më shpejt që të ishte e mundur, d.m.th. në hapësirën e kernelit, pasi kjo shmang kopjimin e të dhënave të panevojshme në hapësirën e përdoruesit. Për të ofruar siguri në kohën e ekzekutimit për ekzekutimin e kodit të përdoruesit në hapësirën e kernelit, u përdor një makinë virtuale me sandbox.

Megjithatë, makinat virtuale për filtrat ekzistues u krijuan për të funksionuar në makinat e bazuara në pirg dhe nuk funksionuan me aq efikasitet në makinat më të reja RISC. Si rezultat, përmes përpjekjeve të inxhinierëve nga Berkeley Labs, u zhvillua një teknologji e re BPF (Berkeley Packet Filters), arkitektura e makinës virtuale e së cilës u krijua bazuar në procesorin Motorola 6502 - kali i punës i produkteve të tilla të njohura si. Apple II ose SHKP. Makina e re virtuale rriti performancën e filtrit dhjetëra herë në krahasim me zgjidhjet ekzistuese.

Arkitektura e makinës BPF

Ne do të njihemi me arkitekturën në mënyrë pune, duke analizuar shembuj. Sidoqoftë, për të filluar, le të themi se makina kishte dy regjistra 32-bitësh të aksesueshëm nga përdoruesi, një akumulator A dhe regjistri i indeksit X, 64 bajt memorie (16 fjalë), të disponueshme për shkrim dhe lexim pasues, dhe një sistem i vogël komandash për të punuar me këto objekte. Udhëzimet e kërcimit për zbatimin e shprehjeve të kushtëzuara ishin gjithashtu të disponueshme në programe, por për të garantuar përfundimin në kohë të programit, kërcimet mund të bëheshin vetëm përpara, d.m.th., në veçanti, ishte e ndaluar të krijoheshin sythe.

Skema e përgjithshme për fillimin e makinës është si më poshtë. Përdoruesi krijon një program për arkitekturën BPF dhe, duke përdorur disa mekanizmi i kernelit (si thirrja e sistemit), ngarkon dhe lidh programin me të tek disa te gjeneratori i ngjarjeve në kernel (për shembull, një ngjarje është ardhja e paketës tjetër në kartën e rrjetit). Kur ndodh një ngjarje, kerneli ekzekuton programin (për shembull, në një përkthyes) dhe memoria e makinës korrespondon me tek disa rajoni i kujtesës së kernelit (për shembull, të dhënat e një pakete hyrëse).

Sa më sipër do të mjaftojë që të fillojmë të shikojmë shembuj: do të njihemi me sistemin dhe formatin e komandës sipas nevojës. Nëse dëshironi të studioni menjëherë sistemin e komandës së një makine virtuale dhe të mësoni për të gjitha aftësitë e tij, atëherë mund të lexoni artikullin origjinal Filtri i paketës BSD dhe/ose gjysmën e parë të dosjes Documentation/networking/filter.txt nga dokumentacioni i kernelit. Përveç kësaj, ju mund të studioni prezantimin libpcap: Një arkitekturë dhe metodologji optimizimi për kapjen e paketave, në të cilën McCanne, një nga autorët e BPF, flet për historinë e krijimit libpcap.

Tani vazhdojmë të shqyrtojmë të gjithë shembujt domethënës të përdorimit të BPF klasik në Linux: tcpdump (libpcap), seccomp, xt_bpf, cls_bpf.

tcpdump

Zhvillimi i BPF u krye paralelisht me zhvillimin e frontendit për filtrimin e paketave - një mjet i njohur tcpdump. Dhe, meqenëse ky është shembulli më i vjetër dhe më i famshëm i përdorimit të BPF klasik, i disponueshëm në shumë sisteme operative, ne do të fillojmë studimin tonë të teknologjisë me të.

(Kam drejtuar të gjithë shembujt në këtë artikull në Linux 5.6.0-rc6. Prodhimi i disa komandave është modifikuar për lexueshmëri më të mirë.)

Shembull: vëzhgimi i paketave IPv6

Le të imagjinojmë se duam të shohim të gjitha paketat IPv6 në një ndërfaqe eth0. Për ta bërë këtë, ne mund të ekzekutojmë programin tcpdump me një filtër të thjeshtë ip6:

$ sudo tcpdump -i eth0 ip6

Në këtë rast, tcpdump përpilon filtrin ip6 në bytekodin e arkitekturës BPF dhe dërgojeni atë në kernel (shih detajet në seksion Tcpdump: duke u ngarkuar). Filtri i ngarkuar do të ekzekutohet për çdo paketë që kalon përmes ndërfaqes eth0. Nëse filtri kthen një vlerë jo zero n, pastaj deri në n bajtët e paketës do të kopjohen në hapësirën e përdoruesit dhe ne do ta shohim atë në dalje tcpdump.

BPF për të vegjlit, pjesa zero: BPF klasik

Rezulton se ne mund të zbulojmë lehtësisht se cili bajtkod është dërguar në kernel tcpdump me ndihmën e tcpdump, nëse e ekzekutojmë me opsionin -d:

$ sudo tcpdump -i eth0 -d ip6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 3
(002) ret      #262144
(003) ret      #0

Në linjën zero ekzekutojmë komandën ldh [12], që do të thotë "ngarkim në regjistër A gjysmë fjale (16 bit) e vendosur në adresën 12” dhe pyetja e vetme është se çfarë lloj memorie po i drejtohemi? Përgjigja është se në x fillon (x+1)bajtin e paketës së rrjetit të analizuar. Ne lexojmë paketa nga ndërfaqja Ethernet eth0, dhe kjo mjeteqë paketa duket kështu (për thjeshtësi, supozojmë se nuk ka etiketa VLAN në paketë):

       6              6          2
|Destination MAC|Source MAC|Ether Type|...|

Pra pas ekzekutimit të komandës ldh [12] në regjistër A do të ketë një fushë Ether Type — lloji i paketës së transmetuar në këtë kornizë Ethernet. Në rreshtin 1 krahasojmë përmbajtjen e regjistrit A (lloji i paketës) c 0x86dd, dhe kjo dhe kanë Lloji për të cilin ne jemi të interesuar është IPv6. Në rreshtin 1, përveç komandës së krahasimit, ka edhe dy kolona të tjera - jt 2 и jf 3 — shenjat në të cilat duhet të shkoni nëse krahasimi është i suksesshëm (A == 0x86dd) dhe të pasuksesshme. Pra, në një rast të suksesshëm (IPv6) shkojmë në rreshtin 2, dhe në një rast të pasuksesshëm - në rreshtin 3. Në rreshtin 3 programi përfundon me kodin 0 (mos e kopjoni paketën), në rreshtin 2 programi përfundon me kodin 262144 (më kopjoni një paketë maksimumi 256 kilobajt).

Një shembull më i ndërlikuar: ne shikojmë paketat TCP sipas portit të destinacionit

Le të shohim se si duket një filtër që kopjon të gjitha paketat TCP me portin e destinacionit 666. Ne do të shqyrtojmë rastin IPv4, pasi rasti IPv6 është më i thjeshtë. Pasi të keni studiuar këtë shembull, mund të eksploroni vetë filtrin IPv6 si një ushtrim (ip6 and tcp dst port 666) dhe një filtër për rastin e përgjithshëm (tcp dst port 666). Pra, filtri për të cilin ne jemi të interesuar duket si ky:

$ 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

Ne tashmë e dimë se çfarë bëjnë linjat 0 dhe 1. Në rreshtin 2 ne kemi kontrolluar tashmë që kjo është një paketë IPv4 (Lloji Ether = 0x800) dhe ngarkojeni në regjistër A 24 bajt i paketës. Paketa jonë duket si

       14            8      1     1
|ethernet header|ip fields|ttl|protocol|...|

që do të thotë se ne ngarkojmë në regjistër A fushën e Protokollit të kokës IP, e cila është logjike, sepse duam të kopjojmë vetëm paketat TCP. Ne krahasojmë Protokollin me 0x6 (IPPROTO_TCP) në rreshtin 3.

Në rreshtat 4 dhe 5 ngarkojmë gjysmëfjalët e vendosura në adresën 20 dhe përdorim komandën jset kontrolloni nëse një nga tre është vendosur flamuj - mbajtja e maskës së lëshuar jset tre bitet më të rëndësishme pastrohen. Dy nga tre bitet na tregojnë nëse paketa është pjesë e një pakete IP të fragmentuar dhe nëse po, nëse është fragmenti i fundit. Biti i tretë është i rezervuar dhe duhet të jetë zero. Ne nuk duam të kontrollojmë paketat e paplota ose të prishura, kështu që kontrollojmë të tre bitet.

Rreshti 6 është më interesantja në këtë listë. Shprehje ldxb 4*([14]&0xf) do të thotë që ne ngarkojmë në regjistër X katër bitet më pak të rëndësishme të bajtit të pesëmbëdhjetë të paketës shumëzuar me 4. Katër bitët më pak të rëndësishëm të bajtit të pesëmbëdhjetë është fusha Gjatësia e kokës së internetit Titulli IPv4, i cili ruan gjatësinë e kokës me fjalë, kështu që ju duhet të shumëzoni me 4. Është interesante që shprehja 4*([14]&0xf) është një emërtim për një skemë të veçantë adresimi që mund të përdoret vetëm në këtë formë dhe vetëm për një regjistër X, d.m.th. nuk mund të themi as ldb 4*([14]&0xf) ose ldxb 5*([14]&0xf) (mund të specifikojmë vetëm një kompensim të ndryshëm, për shembull, ldxb 4*([16]&0xf)). Është e qartë se kjo skemë adresimi është shtuar në BPF pikërisht për të marrë X (regjistri i indeksit) Gjatësia e kokës IPv4.

Pra, në rreshtin 7 ne përpiqemi të ngarkojmë gjysmë fjale në (X+16). Duke kujtuar se 14 bajt janë të zëna nga kreu i Ethernetit, dhe X përmban gjatësinë e kokës IPv4, ne e kuptojmë se në A Porta e destinacionit TCP është ngarkuar:

       14           X           2             2
|ethernet header|ip header|source port|destination port|

Së fundi, në rreshtin 8 krahasojmë portin e destinacionit me vlerën e dëshiruar dhe në rreshtat 9 ose 10 kthejmë rezultatin - nëse duhet kopjuar paketën apo jo.

Tcpdump: duke u ngarkuar

Në shembujt e mëparshëm, ne në mënyrë specifike nuk u ndalëm në detaje se si e ngarkojmë bajtkodin BPF në kernel për filtrimin e paketave. Në përgjithësi, tcpdump transferuar në shumë sisteme dhe për të punuar me filtra tcpdump përdor bibliotekën libpcap. Shkurtimisht, për të vendosur një filtër në një ndërfaqe duke përdorur libpcap, duhet të bëni sa më poshtë:

Për të parë se si funksionon pcap_setfilter zbatuar në Linux, ne përdorim strace (disa rreshta janë hequr):

$ 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 linjat e para të prodhimit ne krijojmë fole e papërpunuar për të lexuar të gjitha kornizat Ethernet dhe për ta lidhur atë me ndërfaqen eth0... Nga shembulli ynë i parë ne e dimë se filtri ip do të përbëhet nga katër udhëzime BPF, dhe në rreshtin e tretë ne shohim se si përdoret opsioni SO_ATTACH_FILTER thirrje sistemi setsockopt ngarkojmë dhe lidhim një filtër me gjatësi 4. Ky është filtri ynë.

Vlen të përmendet se në BPF klasik, ngarkimi dhe lidhja e një filtri ndodh gjithmonë si një operacion atomik, dhe në versionin e ri të BPF, ngarkimi i programit dhe lidhja e tij me gjeneratorin e ngjarjeve ndahen në kohë.

E Vërteta e Fshehur

Një version pak 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 (Resource temporarily unavailable)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

Siç u përmend më lart, ne ngarkojmë dhe lidhim filtrin tonë me prizën në linjën 5, por çfarë ndodh në linjat 3 dhe 4? Rezulton se kjo libpcap kujdeset për ne - në mënyrë që prodhimi i filtrit tonë të mos përfshijë pako që nuk e kënaqin atë, biblioteka lidh filtër bedel ret #0 (hiqni të gjitha paketat), kalon prizën në modalitetin jo-bllokues dhe përpiqet të zbresë të gjitha paketat që mund të mbeten nga filtrat e mëparshëm.

Në total, për të filtruar paketat në Linux duke përdorur BPF klasik, duhet të keni një filtër në formën e një strukture si struct sock_fprog dhe një prizë e hapur, pas së cilës filtri mund të ngjitet në prizë duke përdorur një thirrje sistemi setsockopt.

Është interesante se filtri mund të ngjitet në çdo prizë, jo vetëm të papërpunuar. Këtu shembull një program që ndërpret të gjithë, përveç dy bajtëve të parë, nga të gjitha datagramet hyrëse të UDP. (Kam shtuar komente në kod në mënyrë që të mos rrëmbej artikullin.)

Më shumë detaje rreth përdorimit setsockopt për lidhjen e filtrave, shih fole (7), por në lidhje me shkrimin e filtrave tuaj si struct sock_fprog pa ndihmë tcpdump do të flasim në seksion Programimi i BPF me duart tona.

BPF klasike dhe shekulli i 21-të

BPF u përfshi në Linux në 1997 dhe ka mbetur një punëtor për një kohë të gjatë libpcap pa ndonjë ndryshim të veçantë (ndryshime specifike për Linux, natyrisht, ishin, por ato nuk ndryshuan pamjen globale). Shenjat e para serioze që BPF do të evoluonte erdhën në vitin 2011, kur Eric Dumazet propozoi patch, i cili shton përpiluesin Just In Time në kernel - një përkthyes për konvertimin e bytekodit BPF në vendas x86_64 kodi.

Përpiluesi JIT ishte i pari në zinxhirin e ndryshimeve: në 2012 u shfaq aftësia për të shkruar filtra për seccomp, duke përdorur BPF, në janar 2013 ka pasur shtuar modul xt_bpf, e cila ju lejon të shkruani rregulla për iptables me ndihmën e BPF, dhe në tetor 2013 ishte shtuar gjithashtu një modul cls_bpf, i cili ju lejon të shkruani klasifikuesit e trafikut duke përdorur BPF.

Së shpejti do t'i shikojmë të gjithë këta shembuj më në detaje, por së pari do të jetë e dobishme për ne të mësojmë se si të shkruajmë dhe përpilojmë programe arbitrare për BPF, meqenëse aftësitë e ofruara nga biblioteka libpcap i kufizuar (shembull i thjeshtë: filtri i krijuar libpcap mund të kthejë vetëm dy vlera - 0 ose 0x40000) ose në përgjithësi, si në rastin e seccomp, nuk janë të zbatueshme.

Programimi i BPF me duart tona

Le të njihemi me formatin binar të udhëzimeve BPF, është shumë e thjeshtë:

   16    8    8     32
| code | jt | jf |  k  |

Çdo instruksion zë 64 bit, në të cilin 16 bitët e parë janë kodi i instruksionit, pastaj ka dy dhëmbëzime tetë-bitësh, jt и jf, dhe 32 bit për argumentin K, qëllimi i së cilës ndryshon nga komanda në komandë. Për shembull, komanda ret, e cila përfundon programi ka kodin 6, dhe vlera e kthimit merret nga konstantja K. Në C, një udhëzim i vetëm BPF përfaqësohet si një strukturë

struct sock_filter {
        __u16   code;
        __u8    jt;
        __u8    jf;
        __u32   k;
}

dhe i gjithë programi është në formë strukture

struct sock_fprog {
        unsigned short len;
        struct sock_filter *filter;
}

Kështu, ne tashmë mund të shkruajmë programe (për shembull, ne i dimë kodet e udhëzimeve nga [1]). Kështu do të duket filtri ip6 nga shembulli ynë i parë:

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

program prog ne mund të përdorim ligjërisht në një telefonatë

setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))

Shkrimi i programeve në formën e kodeve të makinës nuk është shumë i përshtatshëm, por ndonjëherë është i nevojshëm (për shembull, për korrigjimin, krijimin e testeve të njësive, shkrimin e artikujve në Habré, etj.). Për lehtësi, në dosje <linux/filter.h> Përcaktohen makrot ndihmëse - i njëjti shembull si më sipër mund të rishkruhet 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),
}

Sidoqoftë, ky opsion nuk është shumë i përshtatshëm. Kjo është ajo që arsyetuan programuesit e kernelit Linux, dhe për këtë arsye në drejtori tools/bpf kernelet mund të gjeni një montues dhe korrigjues për të punuar me BPF klasik.

Gjuha e Asamblesë është shumë e ngjashme me daljen e korrigjimit tcpdump, por përveç kësaj mund të specifikojmë etiketat simbolike. Për shembull, këtu është një program që hedh 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 #0

Si parazgjedhje, asembleri gjeneron kodin në format <количество инструкций>,<code1> <jt1> <jf1> <k1>,..., për shembullin tonë me TCP do të jetë

$ 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, siç bëmë në fillim të këtij seksioni.

Zgjerime Linux dhe netsniff-ng

Përveç BPF standarde, Linux dhe tools/bpf/bpf_asm mbështetje dhe grup jo standard. Në thelb, udhëzimet përdoren për të hyrë në fushat e një strukture struct sk_buff, i cili përshkruan një paketë rrjeti në kernel. Megjithatë, ka edhe lloje të tjera udhëzimesh ndihmëse, për shembull ldw cpu do të ngarkohet në regjistër A rezultat i ekzekutimit të një funksioni kernel raw_smp_processor_id(). (Në versionin e ri të BPF, këto shtesa jo standarde janë zgjeruar për të ofruar programe me një grup ndihmësish të kernelit për të hyrë në kujtesë, struktura dhe gjenerim ngjarjesh.) Këtu është një shembull interesant i një filtri në të cilin ne kopjojmë vetëm titujt e paketave në hapësirën e përdoruesit duke përdorur shtesën poff, kompensimi i ngarkesës:

ld poff
ret a

Shtesat BPF nuk mund të përdoren në tcpdump, por kjo është një arsye e mirë për t'u njohur me paketën e shërbimeve netsniff-ng, i cili ndër të tjera përmban një program të avancuar netsniff-ng, i cili, përveç filtrimit duke përdorur BPF, përmban gjithashtu një gjenerues efektiv trafiku dhe më të avancuar se tools/bpf/bpf_asm, thirri një montues BPF bpfc. Paketa përmban dokumentacion mjaft të detajuar, shikoni edhe lidhjet në fund të artikullit.

seccomp

Pra, ne tashmë dimë se si të shkruajmë programe BPF me kompleksitet arbitrar dhe jemi gati të shikojmë shembuj të rinj, i pari prej të cilëve është teknologjia seccomp, e cila lejon, duke përdorur filtrat BPF, të menaxhojë grupin dhe grupin e argumenteve të thirrjeve të sistemit të disponueshëm për një proces të caktuar dhe pasardhësit e tij.

Versioni i parë i seccomp u shtua në kernel në 2005 dhe nuk ishte shumë i popullarizuar, pasi ofronte vetëm një opsion të vetëm - për të kufizuar grupin e thirrjeve të sistemit të disponueshëm për një proces në sa vijon: read, write, exit и sigreturn, dhe procesi që shkeli rregullat u vra duke përdorur SIGKILL. Sidoqoftë, në vitin 2012, seccomp shtoi aftësinë për të përdorur filtrat BPF, duke ju lejuar të përcaktoni një grup thirrjesh të lejuara të sistemit dhe madje të kryeni kontrolle mbi argumentet e tyre. (Interesante, Chrome ishte një nga përdoruesit e parë të këtij funksioni dhe njerëzit e Chrome aktualisht po zhvillojnë një mekanizëm KRSI të bazuar në një version të ri të BPF dhe duke lejuar personalizimin e moduleve të sigurisë Linux.) Lidhjet me dokumentacionin shtesë mund të gjenden në fund të artikullit.

Vini re se tashmë ka pasur artikuj në qendër në lidhje me përdorimin e seccomp, ndoshta dikush do të dëshirojë t'i lexojë ato përpara (ose në vend të) të lexojë nënseksionet e mëposhtme. Në artikull Kontejnerët dhe siguria: seccomp ofron shembuj të përdorimit të seccomp, si versioni 2007 ashtu edhe versioni që përdor BPF (filtrat krijohen duke përdorur libseccomp), flet për lidhjen e seccomp me Docker, dhe gjithashtu ofron shumë lidhje të dobishme. Në artikull Izolimi i demonëve me systemd ose "nuk të duhet Docker për këtë!" Ai mbulon, në veçanti, si të shtoni listat e zeza ose listat e bardha të thirrjeve të sistemit për demonët që ekzekutojnë systemd.

Më pas do të shohim se si të shkruajmë dhe ngarkojmë filtra për seccomp në C të zhveshur dhe duke përdorur bibliotekën libseccomp dhe cilat janë të mirat dhe të këqijat e secilit opsion, dhe së fundi, le të shohim se si seccomp përdoret nga programi strace.

Shkrimi dhe ngarkimi i filtrave për seccomp

Ne tashmë dimë se si të shkruajmë programe BPF, kështu që le të shohim së pari ndërfaqen e programimit seccomp. Mund të vendosni një filtër në nivelin e procesit dhe të gjitha proceset e fëmijëve do të trashëgojnë kufizimet. Kjo bëhet duke përdorur një thirrje sistemi seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

ku &filter - ky është një tregues për një strukturë tashmë të njohur për ne struct sock_fprog, d.m.th. Programi BPF.

Si ndryshojnë programet për seccomp nga programet për bazat? Konteksti i transmetuar. Në rastin e prizave, na është dhënë një zonë memorie që përmban paketën, dhe në rastin e seccomp na është dhënë një strukturë si

struct seccomp_data {
    int   nr;
    __u32 arch;
    __u64 instruction_pointer;
    __u64 args[6];
};

Këtu nr është numri i thirrjes së sistemit që do të hapet, arch - arkitektura aktuale (më shumë për këtë më poshtë), args - deri në gjashtë argumente të thirrjes së sistemit, dhe instruction_pointer është një tregues për udhëzimin e hapësirës së përdoruesit që bëri thirrjen e sistemit. Kështu, për shembull, për të ngarkuar numrin e thirrjes së sistemit në regjistër A duhet të themi

ldw [0]

Ka veçori të tjera për programet seccomp, për shembull, konteksti mund të arrihet vetëm me shtrirje 32-bit dhe nuk mund të ngarkoni gjysmë fjale ose një bajt - kur përpiqeni të ngarkoni një filtër ldh [0] thirrje sistemi seccomp do te kthehen EINVAL. Funksioni kontrollon filtrat e ngarkuar seccomp_check_filter() bërthamat. (Gjëja qesharake është se në commit origjinal që shtoi funksionalitetin seccomp, ata harruan të shtonin leje për të përdorur udhëzimet në këtë funksion mod (mbetja e ndarjes) dhe tani nuk është e disponueshme për programet seccomp BPF, që nga shtimi i tij do të thyhet ABI.)

Në thelb, ne tashmë dimë gjithçka për të shkruar dhe lexuar programe seccomp. Zakonisht logjika e programit rregullohet si një listë e bardhë ose e zezë e 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 #0

kontrollon një listë të zezë me katër telefonata sistemore me numër 304, 176, 239, 279. Cilat janë këto thirrje sistemore? Nuk mund të themi me siguri, pasi nuk e dimë se për cilën arkitekturë është shkruar programi. Prandaj, autorët e seccomp ofroj nisni të gjitha programet me një kontroll të arkitekturës (arkitektura aktuale tregohet në kontekst si fushë arch struktura struct seccomp_data). Me arkitekturën e kontrolluar, fillimi i shembullit do të duket kështu:

ld [4]
jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64

dhe më pas numrat tanë të thirrjes së sistemit do të merrnin vlera të caktuara.

Ne shkruajmë dhe ngarkojmë filtra për seccomp duke përdorur libseccomp

Shkrimi i filtrave në kodin vendas ose në montimin BPF ju lejon të keni kontroll të plotë mbi rezultatin, por në të njëjtën kohë, ndonjëherë preferohet të keni kod të lëvizshëm dhe/ose të lexueshëm. Biblioteka do të na ndihmojë për këtë libseccomp, e cila ofron një ndërfaqe standarde për të shkruar filtra të zinj ose të bardhë.

Le të shkruajmë, për shembull, një program që ekzekuton një skedar binar sipas zgjedhjes së përdoruesit, pasi të ketë instaluar më parë një listë të zezë të thirrjeve të sistemit nga artikullin e mësipërm (programi është thjeshtuar për lexueshmëri më të madhe, versioni i plotë mund të gjendet këtu):

#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]);
}

Së pari ne përcaktojmë një grup sys_numbers mbi 40 numra telefonatash sistemi për të bllokuar. Pastaj, inicializoni kontekstin ctx dhe tregojini bibliotekës se çfarë duam të lejojmë (SCMP_ACT_ALLOW) të gjitha thirrjet e sistemit si parazgjedhje (është më e lehtë të ndërtosh lista të zeza). Më pas, një nga një, shtojmë të gjitha thirrjet e sistemit nga lista e zezë. Në përgjigje të një thirrjeje sistemi nga lista, ne kërkojmë SCMP_ACT_TRAP, në këtë rast seccomp do t'i dërgojë një sinjal procesit SIGSYS me një përshkrim se cila thirrje sistemi ka shkelur rregullat. Së fundi, ne ngarkojmë programin në kernel duke përdorur seccomp_load, i cili do të përpilojë programin dhe do ta bashkangjisë atë në proces duke përdorur një thirrje sistemi seccomp(2).

Për një përpilim të suksesshëm, programi duhet të lidhet 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 -lseccomp

Shembull i një nisjeje të suksesshme:

$ ./seccomp_lib echo ok
ok

Shembull i një telefonate të bllokuar të sistemit:

$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Bad system call

Ne përdorim straceper detaje:

$ 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} ---
+++ killed by SIGSYS (core dumped) +++
Bad system call

si mund ta dimë se programi është ndërprerë për shkak të përdorimit të një thirrje të paligjshme të sistemit mount(2).

Pra, ne kemi shkruar një filtër duke përdorur bibliotekën libseccomp, duke vendosur kodin jo të parëndësishëm në katër rreshta. Në shembullin e mësipërm, nëse ka një numër të madh të thirrjeve të sistemit, koha e ekzekutimit mund të zvogëlohet ndjeshëm, pasi kontrolli është vetëm një listë krahasimesh. Për optimizim, libseccomp kishte kohët e fundit patch i përfshirë, e cila shton mbështetje për atributin e filtrit SCMP_FLTATR_CTL_OPTIMIZE. Vendosja e këtij atributi në 2 do ta shndërrojë filtrin në një program kërkimi binar.

Nëse dëshironi të shihni se si funksionojnë filtrat binar të kërkimit, hidhini një sy skenar i thjeshtë, i cili gjeneron programe të tilla në assembler BPF duke thirrur numrat e thirrjeve të sistemit, për shembull:

$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, bad
jgt #6, check8
jeq #1, bad
jeq #3, bad
ret #0x7fff0000
check8:
jeq #8, bad
jeq #13, bad
ret #0x7fff0000
bad: ret #0

Ju nuk do të jeni në gjendje të shkruani asgjë shumë më shpejt, pasi programet BPF nuk mund të kryejnë kërcime me dhëmbëzim (ne nuk mund të bëjmë, për shembull, jmp A ose jmp [label+X]) dhe për këtë arsye të gjitha tranzicionet janë statike.

seccomp dhe strace

Të gjithë e dinë dobinë strace është një mjet i domosdoshëm për të studiuar sjelljen e proceseve në Linux. Megjithatë, shumë kanë dëgjuar gjithashtu për çështjet e performancës kur përdorni këtë mjet. Fakti është se strace zbatohet duke përdorur ptrace(2), dhe në këtë mekanizëm ne nuk mund të specifikojmë se në çfarë grupi thirrjesh të sistemit duhet të ndalojmë procesin, p.sh., komandat

$ time strace du /usr/share/ >/dev/null 2>&1

real    0m3.081s
user    0m0.531s
sys     0m2.073s

и

$ time strace -e open du /usr/share/ >/dev/null 2>&1

real    0m2.404s
user    0m0.193s
sys     0m1.800s

përpunohen afërsisht në të njëjtën kohë, edhe pse në rastin e dytë duam të gjurmojmë vetëm një thirrje sistemore.

Opsion i ri --seccomp-bpf, shtuar në strace versioni 5.3, ju lejon të shpejtoni procesin shumë herë dhe koha e nisjes nën gjurmën e një telefonate sistemi është tashmë e krahasueshme me kohën e një fillimi të rregullt:

$ 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, natyrisht, ka një mashtrim të vogël në atë që ne nuk po gjurmojmë thirrjen kryesore të sistemit të kësaj komande. Nëse do të gjurmonim, për shembull, newfsstat, Pastaj strace do të frenonte po aq fort sa pa --seccomp-bpf.)

Si funksionon ky opsion? Pa të strace lidhet me procesin dhe fillon ta përdorë atë PTRACE_SYSCALL. Kur një proces i menaxhuar lëshon një (ndonjë) thirrje sistemi, kontrolli transferohet te strace, i cili shikon argumentet e thirrjes së sistemit dhe e ekzekuton atë me të PTRACE_SYSCALL. Pas njëfarë kohe, procesi përfundon thirrjen e sistemit dhe kur dilni prej tij, kontrolli transferohet përsëri strace, i cili shikon vlerat e kthimit dhe fillon procesin duke përdorur PTRACE_SYSCALL, dhe kështu me radhë.

BPF për të vegjlit, pjesa zero: BPF klasik

Me seccomp, megjithatë, ky proces mund të optimizohet saktësisht siç do të dëshironim. Domethënë, nëse duam të shikojmë vetëm thirrjen e sistemit X, atëherë mund të shkruajmë një filtër BPF që për X kthen vlerën SECCOMP_RET_TRACE, dhe për thirrjet që nuk janë me interes për ne - SECCOMP_RET_ALLOW:

ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000

Në këtë rast, strace fillimisht fillon procesin si PTRACE_CONT, filtri ynë përpunohet për çdo thirrje sistemi, nëse thirrja e sistemit nuk është X, atëherë procesi vazhdon të ecë, por nëse kjo X, atëherë seccomp do të transferojë kontrollin stracee cila do të shikojë argumentet dhe do të fillojë procesin si PTRACE_SYSCALL (pasi seccomp nuk ka aftësinë për të ekzekutuar një program në dalje nga një thirrje sistemi). Kur kthehet thirrja e sistemit, strace do të rifillojë procesin duke përdorur PTRACE_CONT dhe do të presë për mesazhe të reja nga seccomp.

BPF për të vegjlit, pjesa zero: BPF klasik

Kur përdorni opsionin --seccomp-bpf ka dy kufizime. Së pari, nuk do të jetë e mundur të bashkoheni me një proces tashmë ekzistues (opsion -p programet strace), pasi kjo nuk mbështetet nga seccomp. Së dyti, nuk ka asnjë mundësi jo shikoni proceset e fëmijëve, pasi filtrat seccomp trashëgohen nga të gjitha proceset fëmijë pa aftësinë për ta çaktivizuar këtë.

Pak më shumë detaje se si saktësisht strace punon me seccomp mund të gjendet nga raporti i fundit. Për ne, fakti më interesant është se BPF klasik i përfaqësuar nga seccomp përdoret edhe sot.

xt_bpf

Le të kthehemi tani në botën e rrjeteve.

Sfondi: shumë kohë më parë, në vitin 2007, thelbi ishte shtuar modul xt_u32 për netfilter. Është shkruar për analogji me një klasifikues trafiku edhe më të lashtë cls_u32 dhe ju lejoi të shkruani rregulla arbitrare binare për iptables duke përdorur veprimet e mëposhtme të thjeshta: ngarkoni 32 bit nga një paketë dhe kryeni një sërë veprimesh aritmetike mbi to. Për shembull,

sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"

Ngarkon 32 bitet e kokës së IP-së, duke filluar nga mbushja 6, dhe aplikon një maskë për to 0xFF (merr bajtin e ulët). Kjo fushë protocol Kreu i IP-së dhe ne e krahasojmë atë me 1 (ICMP). Ju mund të kombinoni shumë kontrolle në një rregull, dhe gjithashtu mund të ekzekutoni operatorin @ — zhvendosni X bajt në të djathtë. Për shembull, rregulli

iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"

kontrollon nëse Numri i Sekuencës TCP nuk është i barabartë 0x29. Nuk do të hyj në detaje më tej, pasi tashmë është e qartë se shkrimi i rregullave të tilla me dorë nuk është shumë i përshtatshëm. Në artikull BPF - bytekodi i harruar, ka disa lidhje me shembuj të përdorimit dhe gjenerimit të rregullave për xt_u32. Shihni edhe lidhjet në fund të këtij artikulli.

Që nga viti 2013 modul në vend të modulit xt_u32 ju mund të përdorni një modul të bazuar në BPF xt_bpf. Kushdo që ka lexuar deri këtu duhet të jetë tashmë i qartë për parimin e funksionimit të tij: ekzekutoni bytecode BPF siç rregullon iptables. Ju mund të krijoni një rregull të ri, për shembull, si ky:

iptables -A INPUT -m bpf --bytecode <байткод> -j LOG

këtu <байткод> - ky është kodi në formatin e daljes së asamblerit bpf_asm si parazgjedhje, 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 LOG

Në këtë shembull ne po filtrojmë të gjitha paketat UDP. Konteksti për një program BPF në një modul xt_bpf, natyrisht, tregon për të dhënat e paketës, në rastin e iptables, në fillim të kokës IPv4. Vlera e kthimit nga programi BPF logjikeKu false do të thotë se paketa nuk përputhej.

Është e qartë se moduli xt_bpf mbështet filtra më komplekse se shembulli i mësipërm. Le të shohim shembuj të vërtetë nga Cloudfare. Deri vonë ata përdorën modulin xt_bpf për të mbrojtur kundër sulmeve DDoS. Në artikull Prezantimi i mjeteve BPF ata shpjegojnë se si (dhe pse) gjenerojnë filtra BPF dhe publikojnë lidhje me një grup shërbimesh për krijimin e filtrave të tillë. Për shembull, duke përdorur programin bpfgen mund të krijoni një program BPF që përputhet me një pyetje DNS për një emër 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 #0

Në program ne fillimisht ngarkojmë në regjistër X adresa e fillimit të linjës x04habrx03comx00 brenda një datagrami UDP dhe më pas kontrolloni kërkesën: 0x04686162 <-> "x04hab" etj

Pak më vonë, Cloudfare publikoi kodin e përpiluesit p0f -> BPF. Në artikull Prezantimi i përpiluesit p0f BPF ata flasin se çfarë është p0f dhe si të konvertohen 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,
...

Aktualisht nuk përdor më Cloudfare xt_bpf, pasi ata u zhvendosën në XDP - një nga opsionet për përdorimin e versionit të ri të BPF, shih. L4Drop: Zbutjet e XDP DDoS.

cls_bpf

Shembulli i fundit i përdorimit të BPF klasik në kernel është klasifikuesi cls_bpf për nënsistemin e kontrollit të trafikut në Linux, i shtuar në Linux në fund të 2013 dhe duke zëvendësuar konceptualisht të lashtën cls_u32.

Sidoqoftë, ne nuk do ta përshkruajmë tani punën cls_bpf, pasi nga pikëpamja e njohurive për BPF klasike kjo nuk do të na japë asgjë - ne tashmë jemi njohur me të gjithë funksionalitetin. Për më tepër, në artikujt vijues që flasin për BPF të zgjeruar, ne do ta takojmë këtë klasifikues më shumë se një herë.

Një arsye tjetër për të mos folur për përdorimin klasik të BPF c cls_bpf Problemi është se, në krahasim me Extended BPF, fushëveprimi i zbatueshmërisë në këtë rast është ngushtuar rrënjësisht: programet klasike nuk mund të ndryshojnë përmbajtjen e paketave dhe nuk mund të ruajnë gjendjen midis thirrjeve.

Pra, është koha për t'i thënë lamtumirë BPF klasike dhe për të parë nga e ardhmja.

Lamtumirë BPF klasike

Ne shikuam sesi teknologjia BPF, e zhvilluar në fillim të viteve nëntëdhjetë, jetoi me sukses për një çerek shekulli dhe deri në fund gjeti aplikime të reja. Sidoqoftë, ngjashëm me kalimin nga makineritë e stivës në RISC, i cili shërbeu si një shtysë për zhvillimin e BPF klasike, në vitet 32 pati një kalim nga makinat 64-bit në XNUMX-bit dhe BPF klasik filloi të vjetërohej. Për më tepër, aftësitë e BPF-së klasike janë shumë të kufizuara, dhe përveç arkitekturës së vjetëruar - ne nuk kemi aftësinë për të ruajtur gjendjen midis thirrjeve në programet BPF, nuk ka mundësi të ndërveprimit të drejtpërdrejtë me përdoruesit, nuk ka mundësi ndërveprimi me kernel, me përjashtim të leximit të një numri të kufizuar fushash strukturore sk_buff dhe duke nisur funksionet më të thjeshta ndihmëse, nuk mund të ndryshoni përmbajtjen e paketave dhe t'i ridrejtoni ato.

Në fakt, aktualisht gjithçka që mbetet nga BPF-ja klasike në Linux është ndërfaqja API, dhe brenda kernelit të gjitha programet klasike, qofshin ato filtra prizë apo filtra seccomp, përkthehen automatikisht në një format të ri, Extended BPF. (Ne do të flasim saktësisht se si ndodh kjo në artikullin vijues.)

Kalimi në një arkitekturë të re filloi në vitin 2013, kur Alexey Starovoitov propozoi një skemë të përditësimit të BPF. Në vitin 2014 arna përkatëse filloi të shfaqej në thelb. Me sa kuptoj unë, plani fillestar ishte vetëm optimizimi i arkitekturës dhe përpiluesit JIT për të ekzekutuar në mënyrë më efikase në makinat 64-bit, por në vend të kësaj këto optimizime shënuan fillimin e një kapitulli të ri në zhvillimin e Linux.

Artikuj të mëtejshëm në këtë seri do të mbulojnë arkitekturën dhe aplikimet e teknologjisë së re, e njohur fillimisht si BPF e brendshme, më pas BPF e zgjeruar dhe tani thjesht BPF.

Referencat

  1. 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
  2. Steven McCanne, "libpcap: Një arkitekturë dhe metodologji e optimizmit për kapjen e paketave", https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. Udhëzues për ndeshjen IPtable U32.
  5. BPF - bytekodi i harruar: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. Prezantimi i mjetit BPF: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. Një përmbledhje e dytë: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: Kontejnerët dhe siguria: seccomp
  11. habr: Izolimi i demonëve me systemd ose "nuk të duhet Docker për këtë!"
  12. Paul Chaignon, "strace --seccomp-bpf: një vështrim nën kapuç", https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. netsniff-ng: http://netsniff-ng.org/

Burimi: www.habr.com

Shto një koment