BPF para sa maliliit, part zero: classic BPF

Ang Berkeley Packet Filters (BPF) ay isang Linux kernel technology na nasa mga front page ng English-language tech publication sa loob ng ilang taon na ngayon. Ang mga kumperensya ay puno ng mga ulat sa paggamit at pagpapaunlad ng BPF. Si David Miller, Linux network subsystem maintainer, ay tumawag sa kanyang talumpati sa Linux Plumbers 2018 "Ang usapan na ito ay hindi tungkol sa XDP" (Ang XDP ay isang use case para sa BPF). Si Brendan Gregg ay nagbibigay ng mga pahayag na pinamagatang Linux BPF Superpowers. Toke HΓΈiland-JΓΈrgensen tumatawana ang kernel ay isang microkernel na ngayon. Itinataguyod ni Thomas Graf ang ideya na Ang BPF ay javascript para sa kernel.

Wala pa ring sistematikong paglalarawan ng BPF sa HabrΓ©, at samakatuwid sa isang serye ng mga artikulo ay susubukan kong pag-usapan ang tungkol sa kasaysayan ng teknolohiya, ilarawan ang mga kasangkapan sa arkitektura at pag-unlad, at balangkasin ang mga lugar ng aplikasyon at kasanayan sa paggamit ng BPF. Ang artikulong ito, zero, sa serye, ay nagsasabi sa kasaysayan at arkitektura ng klasikong BPF, at inilalantad din ang mga lihim ng mga prinsipyo ng pagpapatakbo nito. tcpdump, seccomp, strace, at marami pang iba.

Ang pagbuo ng BPF ay kinokontrol ng Linux networking community, ang pangunahing umiiral na mga application ng BPF ay nauugnay sa mga network at samakatuwid, na may pahintulot @eucariot, tinawag ko ang serye na "BPF para sa mga maliliit", bilang parangal sa mahusay na serye "Mga network para sa maliliit na bata".

Isang maikling kurso sa kasaysayan ng BPF(c)

Ang modernong teknolohiya ng BPF ay isang pinahusay at pinalawak na bersyon ng lumang teknolohiya na may parehong pangalan, na tinatawag na ngayong klasikong BPF upang maiwasan ang pagkalito. Ang isang kilalang utility ay nilikha batay sa klasikong BPF tcpdump, mekanismo seccomp, pati na rin ang hindi gaanong kilalang mga module xt_bpf para sa iptables at classifier cls_bpf. Sa modernong Linux, ang mga klasikong BPF na programa ay awtomatikong isinasalin sa bagong anyo, gayunpaman, mula sa pananaw ng user, ang API ay nanatili sa lugar at ang mga bagong gamit para sa klasikong BPF, tulad ng makikita natin sa artikulong ito, ay matatagpuan pa rin. Para sa kadahilanang ito, at dahil din sa pagsunod sa kasaysayan ng pag-unlad ng klasikal na BPF sa Linux, magiging mas malinaw kung paano at bakit ito umunlad sa modernong anyo nito, nagpasya akong magsimula sa isang artikulo tungkol sa klasikal na BPF.

Sa pagtatapos ng dekada otsenta ng huling siglo, ang mga inhinyero mula sa sikat na Lawrence Berkeley Laboratory ay naging interesado sa tanong kung paano maayos na i-filter ang mga packet ng network sa hardware na moderno noong huling bahagi ng eytis ng huling siglo. Ang pangunahing ideya ng pag-filter, na orihinal na ipinatupad sa CSPF (CMU/Stanford Packet Filter) na teknolohiya, ay upang i-filter ang mga hindi kinakailangang packet sa lalong madaling panahon, i.e. sa kernel space, dahil iniiwasan nito ang pagkopya ng hindi kinakailangang data sa espasyo ng gumagamit. Upang magbigay ng seguridad sa runtime para sa pagpapatakbo ng user code sa kernel space, ginamit ang isang sandboxed virtual machine.

Gayunpaman, ang mga virtual machine para sa mga umiiral nang filter ay idinisenyo upang tumakbo sa mga stack-based na makina at hindi gumana nang kasinghusay sa mas bagong RISC machine. Bilang resulta, sa pamamagitan ng mga pagsisikap ng mga inhinyero mula sa Berkeley Labs, isang bagong teknolohiya ng BPF (Berkeley Packet Filters) ang binuo, ang arkitektura ng virtual machine kung saan idinisenyo batay sa processor ng Motorola 6502 - ang workhorse ng mga kilalang produkto tulad ng Apple II o NES. Ang bagong virtual machine ay nagpataas ng pagganap ng filter ng sampu-sampung beses kumpara sa mga kasalukuyang solusyon.

Arkitektura ng makina ng BPF

Makikilala natin ang arkitektura sa isang gumaganang paraan, pag-aaral ng mga halimbawa. Gayunpaman, upang magsimula, sabihin nating ang makina ay may dalawang 32-bit na rehistro na naa-access ng gumagamit, isang nagtitipon. A at rehistro ng index X, 64 bytes ng memorya (16 na salita), magagamit para sa pagsulat at kasunod na pagbabasa, at isang maliit na sistema ng mga utos para sa pagtatrabaho sa mga bagay na ito. Ang mga tagubilin sa jump para sa pagpapatupad ng mga conditional expression ay magagamit din sa mga programa, ngunit upang magarantiya ang napapanahong pagkumpleto ng programa, ang mga jump ay maaari lamang gawin pasulong, ibig sabihin, sa partikular, ipinagbabawal na lumikha ng mga loop.

Ang pangkalahatang pamamaraan para sa pagsisimula ng makina ay ang mga sumusunod. Lumilikha ang user ng isang programa para sa arkitektura ng BPF at, gamit ilang kernel mechanism (tulad ng system call), naglo-load at nagkokonekta sa program sa sa iba sa generator ng kaganapan sa kernel (halimbawa, ang isang kaganapan ay ang pagdating ng susunod na packet sa network card). Kapag nangyari ang isang kaganapan, pinapatakbo ng kernel ang programa (halimbawa, sa isang interpreter), at ang memorya ng makina ay tumutugma sa sa iba rehiyon ng memorya ng kernel (halimbawa, data ng isang papasok na packet).

Ang nasa itaas ay sapat na para magsimula tayong tumingin sa mga halimbawa: makikilala natin ang system at format ng command kung kinakailangan. Kung nais mong agad na pag-aralan ang command system ng isang virtual machine at alamin ang tungkol sa lahat ng mga kakayahan nito, maaari mong basahin ang orihinal na artikulo Ang BSD Packet Filter at/o ang unang kalahati ng file Dokumentasyon/networking/filter.txt mula sa dokumentasyon ng kernel. Bilang karagdagan, maaari mong pag-aralan ang pagtatanghal libpcap: Isang Arkitektura at Pamamaraan sa Pag-optimize para sa Pagkuha ng Packet, kung saan si McCanne, isa sa mga may-akda ng BPF, ay nag-uusap tungkol sa kasaysayan ng paglikha libpcap.

Nagpapatuloy kami ngayon upang isaalang-alang ang lahat ng mahahalagang halimbawa ng paggamit ng klasikong BPF sa Linux: tcpdump (libpcap), seccom, xt_bpf, cls_bpf.

tcpdump

Ang pagbuo ng BPF ay isinagawa kasabay ng pagbuo ng frontend para sa packet filtering - isang kilalang utility tcpdump. At, dahil ito ang pinakaluma at pinakatanyag na halimbawa ng paggamit ng klasikong BPF, na magagamit sa maraming operating system, sisimulan namin ang aming pag-aaral ng teknolohiya dito.

(Pinatakbo ko ang lahat ng mga halimbawa sa artikulong ito sa Linux 5.6.0-rc6. Ang output ng ilang mga utos ay na-edit para sa mas mahusay na pagiging madaling mabasa.)

Halimbawa: pagmamasid sa mga IPv6 packet

Isipin natin na gusto nating tingnan ang lahat ng IPv6 packet sa isang interface eth0. Upang gawin ito maaari naming patakbuhin ang programa tcpdump na may simpleng filter ip6:

$ sudo tcpdump -i eth0 ip6

Sa kasong ito, tcpdump kino-compile ang filter ip6 sa BPF architecture bytecode at ipadala ito sa kernel (tingnan ang mga detalye sa seksyon Tcpdump: naglo-load). Ang na-load na filter ay tatakbo para sa bawat packet na dumadaan sa interface eth0. Kung nagbabalik ang filter ng hindi zero na halaga n, pagkatapos ay hanggang sa n Ang mga byte ng packet ay makokopya sa espasyo ng gumagamit at makikita natin ito sa output tcpdump.

BPF para sa maliliit, part zero: classic BPF

Lumalabas na madali nating malalaman kung aling bytecode ang ipinadala sa kernel tcpdump sa tulong ng tcpdump, kung patakbuhin natin ito gamit ang opsyon -d:

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

Sa linya zero pinapatakbo namin ang command ldh [12], na nangangahulugang "load into register A kalahating salita (16 bits) na matatagpuan sa address na 12” at ang tanging tanong ay kung anong uri ng memorya ang ating tinutugunan? Ang sagot ay sa x nagsisimula (x+1)ika byte ng nasuri na network packet. Nagbabasa kami ng mga packet mula sa interface ng Ethernet eth0, at ito paraanna ang packet ay ganito ang hitsura (para sa pagiging simple, ipinapalagay namin na walang mga VLAN tag sa packet):

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

Kaya pagkatapos isagawa ang utos ldh [12] sa rehistro A magkakaroon ng field Ether Type β€” ang uri ng packet na ipinadala sa Ethernet frame na ito. Sa linya 1 inihambing namin ang mga nilalaman ng rehistro A (uri ng pakete) c 0x86dd, at ito at mayroon Ang uri na interesado kami ay IPv6. Sa linya 1, bilang karagdagan sa utos ng paghahambing, mayroong dalawa pang mga haligi - jt 2 ΠΈ jf 3 β€” mga marka na kailangan mong puntahan kung matagumpay ang paghahambing (A == 0x86dd) at hindi nagtagumpay. Kaya, sa isang matagumpay na kaso (IPv6) pumunta kami sa linya 2, at sa isang hindi matagumpay na kaso - sa linya 3. Sa linya 3 ang programa ay nagtatapos sa code 0 (huwag kopyahin ang packet), sa linya 2 ang programa ay nagtatapos sa code 262144 (kopyahin ako ng maximum na 256 kilobytes na pakete).

Isang mas kumplikadong halimbawa: tinitingnan namin ang mga TCP packet sa pamamagitan ng destination port

Tingnan natin kung ano ang hitsura ng isang filter na kinokopya ang lahat ng TCP packet na may destination port 666. Isasaalang-alang namin ang IPv4 case, dahil mas simple ang IPv6 case. Pagkatapos pag-aralan ang halimbawang ito, maaari mong tuklasin ang filter ng IPv6 sa iyong sarili bilang isang ehersisyo (ip6 and tcp dst port 666) at isang filter para sa pangkalahatang kaso (tcp dst port 666). Kaya, ang filter na interesado kami ay ganito ang hitsura:

$ 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

Alam na natin kung ano ang ginagawa ng mga linya 0 at 1. Sa linya 2 nasuri na namin na ito ay isang IPv4 packet (Ether Type = 0x800) at i-load ito sa rehistro A Ika-24 na byte ng packet. Ang aming pakete ay parang

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

na nangangahulugang naglo-load kami sa rehistro A ang Protocol field ng IP header, na lohikal, dahil gusto naming kopyahin lamang ang mga TCP packet. Inihahambing namin ang Protocol sa 0x6 (IPPROTO_TCP) sa linya 3.

Sa linya 4 at 5 nilo-load namin ang mga halfword na matatagpuan sa address 20 at ginagamit ang command jset suriin kung ang isa sa tatlo ay nakatakda mga watawat - pagsusuot ng maskara na ibinigay jset ang tatlong pinaka makabuluhang bit ay na-clear. Dalawa sa tatlong bit ang nagsasabi sa amin kung ang packet ay bahagi ng isang fragmented IP packet, at kung gayon, kung ito ang huling fragment. Ang ikatlong bit ay nakalaan at dapat ay zero. Hindi namin nais na suriin ang alinman sa hindi kumpleto o sirang mga packet, kaya tinitingnan namin ang lahat ng tatlong piraso.

Ang Linya 6 ang pinakakawili-wili sa listahang ito. Pagpapahayag ldxb 4*([14]&0xf) ibig sabihin nagloload tayo sa rehistro X ang hindi bababa sa makabuluhang apat na bit ng ikalabinlimang byte ng packet na pinarami ng 4. Ang hindi bababa sa makabuluhang apat na bit ng ikalabinlimang byte ay ang field Haba ng Header ng Internet IPv4 header, na nag-iimbak ng haba ng header sa mga salita, kaya kailangan mong i-multiply sa 4. Kapansin-pansin, ang expression 4*([14]&0xf) ay isang pagtatalaga para sa isang espesyal na pamamaraan ng pagtugon na magagamit lamang sa form na ito at para lamang sa isang rehistro X, ibig sabihin. hindi rin natin masasabi ldb 4*([14]&0xf) ni ldxb 5*([14]&0xf) (maaari lamang kaming tumukoy ng ibang offset, halimbawa, ldxb 4*([16]&0xf)). Malinaw na ang pamamaraan ng pagtugon na ito ay idinagdag sa BPF nang eksakto upang makatanggap X (index register) haba ng IPv4 header.

Kaya sa linya 7 sinusubukan naming mag-load ng kalahating salita sa (X+16). Pag-alala na ang 14 na byte ay inookupahan ng Ethernet header, at X naglalaman ng haba ng IPv4 header, naiintindihan namin na sa A Na-load ang patutunguhang port ng TCP:

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

Sa wakas, sa linya 8 inihambing namin ang patutunguhang port sa nais na halaga at sa mga linya 9 o 10 ibabalik namin ang resulta - kung kopyahin ang packet o hindi.

Tcpdump: naglo-load

Sa mga nakaraang halimbawa, partikular na hindi namin pinag-isipan nang detalyado kung paano namin ini-load ang BPF bytecode sa kernel para sa packet filtering. Pangkalahatang pananalita, tcpdump naka-port sa maraming system at para sa pagtatrabaho sa mga filter tcpdump gumagamit ng aklatan libpcap. Sa madaling sabi, upang maglagay ng filter sa isang interface gamit libpcap, kailangan mong gawin ang sumusunod:

Upang makita kung paano ang function pcap_setfilter ipinatupad sa Linux, ginagamit namin strace (inalis ang ilang linya):

$ 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
...

Sa unang dalawang linya ng output na nilikha namin hilaw na saksakan para basahin ang lahat ng Ethernet frame at itali ito sa interface eth0. Mula sa ang aming unang halimbawa alam namin na ang filter ip ay binubuo ng apat na tagubilin ng BPF, at sa ikatlong linya ay makikita natin kung paano gamitin ang opsyon SO_ATTACH_FILTER tawag sa sistema setsockopt naglo-load kami at nagkokonekta ng isang filter na may haba na 4. Ito ang aming filter.

Kapansin-pansin na sa klasikong BPF, ang paglo-load at pagkonekta ng isang filter ay palaging nangyayari bilang isang atomic na operasyon, at sa bagong bersyon ng BPF, ang paglo-load ng programa at pagbubuklod nito sa generator ng kaganapan ay pinaghihiwalay sa oras.

Nakatagong Katotohanan

Ang isang bahagyang mas kumpletong bersyon ng output ay ganito ang hitsura:

$ 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
...

Tulad ng nabanggit sa itaas, nilo-load at ikinonekta namin ang aming filter sa socket sa linya 5, ngunit ano ang nangyayari sa mga linya 3 at 4? Ito pala ito libpcap nag-aalaga sa amin - upang ang output ng aming filter ay hindi kasama ang mga packet na hindi nasiyahan dito, ang library nag-uugnay dummy filter ret #0 (i-drop ang lahat ng packet), inililipat ang socket sa non-blocking mode at subukang ibawas ang lahat ng packet na maaaring manatili sa mga nakaraang filter.

Sa kabuuan, upang i-filter ang mga pakete sa Linux gamit ang klasikong BPF, kailangan mong magkaroon ng isang filter sa anyo ng isang istraktura tulad ng struct sock_fprog at isang bukas na socket, pagkatapos nito ay maaaring ikabit ang filter sa socket gamit ang isang system call setsockopt.

Kapansin-pansin, ang filter ay maaaring ikabit sa anumang socket, hindi lamang raw. Dito halimbawa isang program na pinuputol ang lahat maliban sa unang dalawang byte mula sa lahat ng mga papasok na datagram ng UDP. (Nagdagdag ako ng mga komento sa code upang hindi kalat ang artikulo.)

Higit pang mga detalye tungkol sa paggamit setsockopt para sa pagkonekta ng mga filter, tingnan socket(7), ngunit tungkol sa pagsulat ng sarili mong mga filter tulad ng struct sock_fprog walang tulong tcpdump mag usap tayo sa section Programming BPF gamit ang aming sariling mga kamay.

Klasikong BPF at ang ika-XNUMX siglo

Ang BPF ay kasama sa Linux noong 1997 at nanatiling workhorse sa mahabang panahon libpcap nang walang anumang mga espesyal na pagbabago (mga pagbabagong partikular sa Linux, siyempre, ay, ngunit hindi nila binago ang pandaigdigang larawan). Ang mga unang seryosong senyales na magbabago ang BPF ay dumating noong 2011, nang magmungkahi si Eric Dumazet tambalan, na nagdaragdag ng Just In Time Compiler sa kernel - isang tagasalin para sa pag-convert ng BPF bytecode sa native x86_64 code.

Ang JIT compiler ang una sa hanay ng mga pagbabago: noong 2012 lumitaw kakayahang magsulat ng mga filter para sa seccom, gamit ang BPF, noong Enero 2013 nagkaroon dagdag pa module xt_bpf, na nagbibigay-daan sa iyong magsulat ng mga panuntunan para sa iptables sa tulong ng BPF, at noong Oktubre 2013 ay dagdag pa isang modyul din cls_bpf, na nagbibigay-daan sa iyong magsulat ng mga classifier ng trapiko gamit ang BPF.

Titingnan natin ang lahat ng mga halimbawang ito nang mas detalyado sa lalong madaling panahon, ngunit una ay magiging kapaki-pakinabang para sa amin na matutunan kung paano magsulat at mag-compile ng mga arbitrary na programa para sa BPF, dahil ang mga kakayahan na ibinigay ng library libpcap limitado (simpleng halimbawa: nabuong filter libpcap maaaring magbalik lamang ng dalawang halaga - 0 o 0x40000) o sa pangkalahatan, tulad ng sa kaso ng seccomp, ay hindi naaangkop.

Programming BPF gamit ang aming sariling mga kamay

Kilalanin natin ang binary na format ng mga tagubilin sa BPF, ito ay napaka-simple:

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

Ang bawat pagtuturo ay sumasakop ng 64 bits, kung saan ang unang 16 bits ay ang instruction code, pagkatapos ay mayroong dalawang walong bit na indent, jt ΠΈ jf, at 32 bits para sa argumento K, ang layunin nito ay nag-iiba-iba sa bawat utos. Halimbawa, ang utos ret, na nagtatapos sa programa ay mayroong code 6, at ang return value ay kinuha mula sa constant K. Sa C, ang isang pagtuturo ng BPF ay kinakatawan bilang isang istraktura

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

at ang buong programa ay nasa anyo ng isang istraktura

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

Kaya, maaari na tayong magsulat ng mga programa (halimbawa, alam natin ang mga code ng pagtuturo mula sa [1]). Ito ang magiging hitsura ng filter ip6 ng ang aming unang halimbawa:

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

programa prog maaari naming legal na gamitin sa isang tawag

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

Ang pagsusulat ng mga programa sa anyo ng mga code ng makina ay hindi masyadong maginhawa, ngunit kung minsan ito ay kinakailangan (halimbawa, para sa pag-debug, paglikha ng mga pagsubok sa yunit, pagsulat ng mga artikulo sa HabrΓ©, atbp.). Para sa kaginhawahan, sa file <linux/filter.h> Tinukoy ang mga helper macro - ang parehong halimbawa sa itaas ay maaaring muling isulat bilang

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

Gayunpaman, ang pagpipiliang ito ay hindi masyadong maginhawa. Ito ang dahilan ng mga programmer ng Linux kernel, at samakatuwid ay nasa direktoryo tools/bpf kernels maaari kang makahanap ng isang assembler at debugger para sa pagtatrabaho sa klasikong BPF.

Ang wika ng pagpupulong ay halos kapareho sa output ng pag-debug tcpdump, ngunit bilang karagdagan maaari naming tukuyin ang mga simbolikong label. Halimbawa, narito ang isang programa na nag-drop ng lahat ng mga packet maliban sa TCP/IPv4:

$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0

Bilang default, ang assembler ay bumubuo ng code sa format <количСство инструкций>,<code1> <jt1> <jf1> <k1>,..., para sa aming halimbawa sa TCP ito ay magiging

$ 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,

Para sa kaginhawahan ng mga C programmer, maaaring gumamit ng ibang format ng output:

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

Maaaring kopyahin ang tekstong ito sa kahulugan ng istraktura ng uri struct sock_filter, tulad ng ginawa namin sa simula ng seksyong ito.

Linux at netsniff-ng mga extension

Bilang karagdagan sa karaniwang BPF, Linux at tools/bpf/bpf_asm suporta at hindi karaniwang hanay. Karaniwan, ang mga tagubilin ay ginagamit upang ma-access ang mga patlang ng isang istraktura struct sk_buff, na naglalarawan ng network packet sa kernel. Gayunpaman, mayroon ding iba pang mga uri ng mga tagubilin sa katulong, halimbawa ldw cpu maglo-load sa rehistro A resulta ng pagpapatakbo ng kernel function raw_smp_processor_id(). (Sa bagong bersyon ng BPF, ang mga hindi karaniwang extension na ito ay pinalawak upang magbigay ng mga programa ng isang hanay ng mga kernel helper para sa pag-access ng memorya, mga istruktura, at pagbuo ng mga kaganapan.) Narito ang isang kawili-wiling halimbawa ng isang filter kung saan kinokopya lang namin ang packet header sa espasyo ng gumagamit gamit ang extension poff, payload offset:

ld poff
ret a

Ang mga extension ng BPF ay hindi maaaring gamitin sa tcpdump, ngunit ito ay isang magandang dahilan upang maging pamilyar sa utility package netsniff-ng, na, bukod sa iba pang mga bagay, ay naglalaman ng isang advanced na programa netsniff-ng, na, bilang karagdagan sa pag-filter gamit ang BPF, ay naglalaman din ng isang epektibong generator ng trapiko, at mas advanced kaysa tools/bpf/bpf_asm, tinawag ang isang BPF assembler bpfc. Ang pakete ay naglalaman ng medyo detalyadong dokumentasyon, tingnan din ang mga link sa dulo ng artikulo.

seccom

Kaya, alam na natin kung paano magsulat ng mga programang BPF na may di-makatwirang pagiging kumplikado at handang tumingin sa mga bagong halimbawa, ang una ay ang teknolohiyang seccomp, na nagpapahintulot, gamit ang mga filter ng BPF, na pamahalaan ang hanay at hanay ng mga argumento ng system call na magagamit sa isang ibinigay na proseso at ang mga inapo nito.

Ang unang bersyon ng seccomp ay idinagdag sa kernel noong 2005 at hindi masyadong sikat, dahil nagbigay lamang ito ng isang pagpipilian - upang limitahan ang hanay ng mga tawag sa system na magagamit sa isang proseso sa mga sumusunod: read, write, exit ΠΈ sigreturn, at ang prosesong lumabag sa mga panuntunan ay pinatay gamit SIGKILL. Gayunpaman, noong 2012, idinagdag ng seccomp ang kakayahang gumamit ng mga filter ng BPF, na nagbibigay-daan sa iyong tukuyin ang isang hanay ng mga pinapayagang tawag sa system at kahit na magsagawa ng mga pagsusuri sa kanilang mga argumento. (Kapansin-pansin, ang Chrome ay isa sa mga unang gumagamit ng functionality na ito, at ang mga taong Chrome ay kasalukuyang bumubuo ng isang mekanismo ng KRSI batay sa isang bagong bersyon ng BPF at pinapayagan ang pag-customize ng Linux Security Modules.) Ang mga link sa karagdagang dokumentasyon ay matatagpuan sa dulo ng artikulo.

Tandaan na mayroon nang mga artikulo sa hub tungkol sa paggamit ng seccom, maaaring may gustong basahin ang mga ito bago (o sa halip na) basahin ang mga sumusunod na subsection. Sa artikulo Mga lalagyan at seguridad: seccom nagbibigay ng mga halimbawa ng paggamit ng seccomp, parehong 2007 na bersyon at ang bersyon na gumagamit ng BPF (mga filter ay nabuo gamit ang libseccomp), nagsasalita tungkol sa koneksyon ng seccomp sa Docker, at nagbibigay din ng maraming kapaki-pakinabang na link. Sa artikulo Pagbukod ng mga daemon gamit ang systemd o "hindi mo kailangan ng Docker para dito!" Sinasaklaw nito, sa partikular, kung paano magdagdag ng mga blacklist o whitelist ng mga tawag sa system para sa mga daemon na tumatakbo sa systemd.

Susunod na makikita natin kung paano magsulat at mag-load ng mga filter para sa seccomp sa hubad na C at gamit ang aklatan libseccomp at ano ang mga kalamangan at kahinaan ng bawat opsyon, at sa wakas, tingnan natin kung paano ginagamit ng programa ang seccomp strace.

Pagsusulat at pag-load ng mga filter para sa seccomp

Alam na natin kung paano magsulat ng mga programa ng BPF, kaya tingnan muna natin ang interface ng seccom programming. Maaari kang magtakda ng filter sa antas ng proseso, at lahat ng proseso ng bata ay magmamana ng mga paghihigpit. Ginagawa ito gamit ang isang system call seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

saan &filter - ito ay isang pointer sa isang istraktura na pamilyar sa amin struct sock_fprog, ibig sabihin. programa ng BPF.

Paano naiiba ang mga programa para sa seccom sa mga programa para sa mga socket? Ipinadala na konteksto. Sa kaso ng mga socket, binigyan kami ng isang lugar ng memorya na naglalaman ng packet, at sa kaso ng seccomp binigyan kami ng isang istraktura tulad ng

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

Dito nr ay ang numero ng system call na ilulunsad, arch - kasalukuyang arkitektura (higit pa dito sa ibaba), args - hanggang anim na argumento ng system call, at instruction_pointer ay isang pointer sa pagtuturo sa espasyo ng user na gumawa ng system call. Kaya, halimbawa, upang i-load ang numero ng tawag sa system sa rehistro A kailangan nating sabihin

ldw [0]

Mayroong iba pang mga tampok para sa mga programa ng seccomp, halimbawa, ang konteksto ay maa-access lamang sa pamamagitan ng 32-bit na pagkakahanay at hindi ka makakapag-load ng kalahating salita o isang byte - kapag sinusubukang mag-load ng filter ldh [0] tawag sa sistema seccomp babalik EINVAL. Sinusuri ng function ang mga na-load na filter seccomp_check_filter() mga butil. (Nakakatuwa, sa orihinal na commit na nagdagdag ng seccomp functionality, nakalimutan nilang magdagdag ng pahintulot na gamitin ang pagtuturo sa function na ito. mod (natitira sa dibisyon) at ngayon ay hindi magagamit para sa mga programa ng seccom BPF, mula nang idagdag ito masisira ABI.)

Talaga, alam na natin ang lahat ng bagay upang magsulat at magbasa ng mga programang seccom. Karaniwan ang logic ng programa ay nakaayos bilang isang puti o itim na listahan ng mga tawag sa system, halimbawa ang programa

ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0

sinusuri ang blacklist ng apat na system call na may numerong 304, 176, 239, 279. Ano ang mga system call na ito? Hindi natin masasabing sigurado, dahil hindi natin alam kung saang arkitektura isinulat ang programa. Samakatuwid, ang mga may-akda ng seccom alok simulan ang lahat ng mga programa na may pagsusuri sa arkitektura (ang kasalukuyang arkitektura ay ipinahiwatig sa konteksto bilang isang field arch istruktura struct seccomp_data). Kapag nasuri ang arkitektura, ang simula ng halimbawa ay magiging ganito:

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

at pagkatapos ang aming mga numero ng system call ay makakakuha ng ilang partikular na halaga.

Sumulat kami at naglo-load ng mga filter para sa paggamit ng seccom libseccomp

Ang pagsusulat ng mga filter sa native code o sa BPF assembly ay nagbibigay-daan sa iyo na magkaroon ng ganap na kontrol sa resulta, ngunit sa parehong oras, kung minsan ay mas mainam na magkaroon ng portable at/o readable code. Tutulungan tayo ng library dito libseccomp, na nagbibigay ng karaniwang interface para sa pagsulat ng mga itim o puting filter.

Halimbawa, magsulat tayo ng program na nagpapatakbo ng binary file na pinili ng user, na dati nang nag-install ng blacklist ng mga tawag sa system mula sa ang artikulo sa itaas (Ang programa ay pinasimple para sa higit na pagiging madaling mabasa, ang buong bersyon ay matatagpuan dito):

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

Una naming tukuyin ang isang array sys_numbers ng 40+ system call number na harangan. Pagkatapos, simulan ang konteksto ctx at sabihin sa library kung ano ang gusto naming payagan (SCMP_ACT_ALLOW) lahat ng tawag sa system bilang default (mas madaling bumuo ng mga blacklist). Pagkatapos, isa-isa, idinaragdag namin ang lahat ng tawag sa system mula sa blacklist. Bilang tugon sa isang tawag sa system mula sa listahan, hinihiling namin SCMP_ACT_TRAP, sa kasong ito, magpapadala ang seccom ng signal sa proseso SIGSYS na may paglalarawan kung aling system call ang lumabag sa mga panuntunan. Sa wakas, ini-load namin ang programa sa kernel gamit seccomp_load, na magsasama-sama ng programa at ilakip ito sa proseso gamit ang isang tawag sa system seccomp(2).

Para sa matagumpay na compilation, ang programa ay dapat na naka-link sa library libseccomp, halimbawa:

cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccomp

Halimbawa ng matagumpay na paglulunsad:

$ ./seccomp_lib echo ok
ok

Halimbawa ng isang naka-block na tawag sa system:

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

Gumamit stracepara sa mga detalye:

$ 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

paano natin malalaman na na-terminate ang program dahil sa paggamit ng illegal system call mount(2).

Kaya, nagsulat kami ng isang filter gamit ang library libseccomp, paglalagay ng hindi walang kuwentang code sa apat na linya. Sa halimbawa sa itaas, kung mayroong isang malaking bilang ng mga tawag sa system, ang oras ng pagpapatupad ay maaaring kapansin-pansing mabawasan, dahil ang tseke ay isang listahan lamang ng mga paghahambing. Para sa pag-optimize, ang libseccomp kamakailan ay nagkaroon kasama ang patch, na nagdaragdag ng suporta para sa katangian ng filter SCMP_FLTATR_CTL_OPTIMIZE. Ang pagtatakda ng attribute na ito sa 2 ay magko-convert sa filter sa isang binary search program.

Kung gusto mong makita kung paano gumagana ang mga binary search filter, tingnan simpleng script, na bumubuo ng mga naturang programa sa BPF assembler sa pamamagitan ng pag-dial sa mga numero ng tawag sa system, halimbawa:

$ 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

Imposibleng magsulat ng anumang bagay na mas mabilis, dahil ang mga programa ng BPF ay hindi maaaring magsagawa ng mga indentation jump (hindi namin magagawa, halimbawa, jmp A o jmp [label+X]) at samakatuwid ang lahat ng mga transition ay static.

seccom at strace

Alam ng lahat ang utility strace ay isang kailangang-kailangan na tool para sa pag-aaral ng gawi ng mga proseso sa Linux. Gayunpaman, marami rin ang nakarinig tungkol sa mga isyu sa pagganap kapag ginagamit ang utility na ito. Sa katotohanan ay strace ipinatupad gamit ang ptrace(2), at sa mekanismong ito hindi namin matukoy kung anong hanay ng mga tawag sa system ang kailangan naming ihinto ang proseso, ibig sabihin, halimbawa, mga utos

$ 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

ay naproseso sa humigit-kumulang sa parehong oras, bagaman sa pangalawang kaso gusto naming subaybayan lamang ang isang system call.

Bagong opsyon --seccomp-bpf, idinagdag sa strace bersyon 5.3, ay nagbibigay-daan sa iyong pabilisin ang proseso ng maraming beses at ang oras ng pagsisimula sa ilalim ng bakas ng isang tawag sa system ay maihahambing na sa oras ng isang regular na pagsisimula:

$ 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

(Dito, siyempre, may kaunting panlilinlang na hindi natin sinusubaybayan ang pangunahing tawag ng sistema ng utos na ito. Kung sinusubaybayan natin, halimbawa, newfsstat, Pagkatapos strace magpreno kasing lakas ng wala --seccomp-bpf.)

Paano gumagana ang opsyong ito? Kung wala siya strace kumokonekta sa proseso at sinimulan itong gamitin PTRACE_SYSCALL. Kapag ang isang pinamamahalaang proseso ay nag-isyu ng (anumang) system call, ang kontrol ay ililipat sa strace, na tumitingin sa mga argumento ng system call at pinapagana ito PTRACE_SYSCALL. Pagkaraan ng ilang oras, nakumpleto ng proseso ang tawag sa system at kapag lalabas dito, ililipat muli ang kontrol strace, na tumitingin sa mga halaga ng pagbabalik at sinimulan ang proseso gamit PTRACE_SYSCALL, at iba pa.

BPF para sa maliliit, part zero: classic BPF

Sa pamamagitan ng seccom, gayunpaman, ang prosesong ito ay maaaring i-optimize nang eksakto tulad ng gusto namin. Namely, kung gusto nating tingnan lang ang system call X, pagkatapos ay maaari tayong magsulat ng BPF filter na para sa X nagbabalik ng halaga SECCOMP_RET_TRACE, at para sa mga tawag na hindi interesado sa amin - SECCOMP_RET_ALLOW:

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

Sa kasong ito strace sa simula ay nagsisimula ang proseso bilang PTRACE_CONT, ang aming filter ay pinoproseso para sa bawat system call, kung ang system call ay hindi X, kung gayon ang proseso ay patuloy na tatakbo, ngunit kung ito X, pagkatapos ay ililipat ng seccom ang kontrol stracena titingnan ang mga argumento at simulan ang proseso tulad ng PTRACE_SYSCALL (dahil ang seccomp ay walang kakayahang magpatakbo ng isang programa sa paglabas mula sa isang tawag sa system). Kapag bumalik ang system call, strace ay muling simulan ang proseso gamit ang PTRACE_CONT at maghihintay ng mga bagong mensahe mula sa seccom.

BPF para sa maliliit, part zero: classic BPF

Kapag ginagamit ang opsyon --seccomp-bpf may dalawang paghihigpit. Una, hindi posibleng sumali sa isang umiiral nang proseso (opsyon -p mga programa strace), dahil hindi ito sinusuportahan ng seccom. Pangalawa, walang posibilidad hindi tingnan ang mga proseso ng bata, dahil ang mga filter ng seccomp ay minana ng lahat ng mga proseso ng bata nang walang kakayahang i-disable ito.

Kaunting detalye kung paano eksakto strace gumagana sa seccomp ay matatagpuan mula sa kamakailang ulat. Para sa amin, ang pinakakawili-wiling katotohanan ay ang klasikong BPF na kinakatawan ng seccomp ay ginagamit pa rin ngayon.

xt_bpf

Bumalik tayo ngayon sa mundo ng mga network.

Background: matagal na ang nakalipas, noong 2007, ang core ay dagdag pa module xt_u32 para sa netfilter. Isinulat ito sa pamamagitan ng pagkakatulad sa isang mas sinaunang classifier ng trapiko cls_u32 at pinahintulutan kang magsulat ng mga arbitrary na binary rules para sa mga iptable gamit ang mga sumusunod na simpleng operasyon: mag-load ng 32 bits mula sa isang package at magsagawa ng isang set ng mga aritmetika na operasyon sa mga ito. Halimbawa,

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

Nilo-load ang 32 bits ng IP header, simula sa padding 6, at naglalagay ng mask sa kanila 0xFF (kunin ang mababang byte). Ang patlang na ito protocol IP header at inihambing namin ito sa 1 (ICMP). Maaari mong pagsamahin ang maraming mga tseke sa isang panuntunan, at maaari mo ring isagawa ang operator @ β€” ilipat ang X byte sa kanan. Halimbawa, ang panuntunan

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

sinusuri kung hindi pantay ang TCP Sequence Number 0x29. Hindi na ako magdedetalye pa, dahil malinaw na na ang pagsulat ng mga panuntunang iyon sa pamamagitan ng kamay ay hindi masyadong maginhawa. Sa artikulo BPF - ang nakalimutang bytecode, mayroong ilang mga link na may mga halimbawa ng paggamit at pagbuo ng panuntunan para sa xt_u32. Tingnan din ang mga link sa dulo ng artikulong ito.

Mula noong 2013 module sa halip na module xt_u32 maaari kang gumamit ng BPF based module xt_bpf. Ang sinumang nakabasa nito ay dapat na malinaw na tungkol sa prinsipyo ng pagpapatakbo nito: patakbuhin ang BPF bytecode bilang mga panuntunan ng iptables. Maaari kang lumikha ng bagong panuntunan, halimbawa, tulad nito:

iptables -A INPUT -m bpf --bytecode <Π±Π°ΠΉΡ‚ΠΊΠΎΠ΄> -j LOG

dito <Π±Π°ΠΉΡ‚ΠΊΠΎΠ΄> - ito ang code sa assembler output format bpf_asm bilang default, halimbawa,

$ 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

Sa halimbawang ito, sinasala namin ang lahat ng UDP packet. Konteksto para sa isang BPF program sa isang module xt_bpf, siyempre, tumuturo sa packet data, sa kaso ng mga iptable, sa simula ng IPv4 header. Return value mula sa BPF program booleanSaan false ibig sabihin hindi tumugma ang packet.

Malinaw na ang modyul xt_bpf sumusuporta sa mas kumplikadong mga filter kaysa sa halimbawa sa itaas. Tingnan natin ang mga tunay na halimbawa mula sa Cloudfare. Hanggang kamakailan lamang ay ginamit nila ang modyul xt_bpf upang maprotektahan laban sa mga pag-atake ng DDoS. Sa artikulo Ipinapakilala ang BPF Tools ipinapaliwanag nila kung paano (at bakit) bumubuo sila ng mga filter ng BPF at nag-publish ng mga link sa isang hanay ng mga utility para sa paglikha ng mga naturang filter. Halimbawa, gamit ang utility bpfgen maaari kang lumikha ng isang BPF program na tumutugma sa isang DNS query para sa isang pangalan 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

Sa programa una kaming nag-load sa rehistro X simula ng linya ng address x04habrx03comx00 sa loob ng isang UDP datagram at pagkatapos ay suriin ang kahilingan: 0x04686162 <-> "x04hab" at iba pa

Maya-maya, inilathala ng Cloudfare ang p0f -> BPF compiler code. Sa artikulo Ipinapakilala ang p0f BPF compiler pinag-uusapan nila kung ano ang p0f at kung paano i-convert ang mga pirma ng p0f sa 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,
...

Kasalukuyang hindi na gumagamit ng Cloudfare xt_bpf, dahil lumipat sila sa XDP - isa sa mga opsyon para sa paggamit ng bagong bersyon ng BPF, tingnan. L4Drop: XDP DDoS Mitigations.

cls_bpf

Ang huling halimbawa ng paggamit ng klasikong BPF sa kernel ay ang classifier cls_bpf para sa subsystem ng kontrol sa trapiko sa Linux, idinagdag sa Linux sa katapusan ng 2013 at pinapalitan ng konsepto ang sinaunang cls_u32.

Gayunpaman, hindi namin ilalarawan ngayon ang gawain cls_bpf, dahil mula sa punto ng view ng kaalaman tungkol sa klasikong BPF hindi ito magbibigay sa amin ng anuman - naging pamilyar na kami sa lahat ng pag-andar. Bilang karagdagan, sa mga kasunod na artikulong pinag-uusapan ang tungkol sa Extended BPF, makikilala natin ang classifier na ito nang higit sa isang beses.

Isa pang dahilan na huwag pag-usapan ang paggamit ng klasikong BPF c cls_bpf Ang problema ay na, kumpara sa Extended BPF, ang saklaw ng applicability sa kasong ito ay radikal na makitid: ang mga klasikal na programa ay hindi maaaring baguhin ang mga nilalaman ng mga pakete at hindi mai-save ang estado sa pagitan ng mga tawag.

Kaya oras na para magpaalam sa klasikong BPF at tumingin sa hinaharap.

Paalam sa klasikong BPF

Tiningnan namin kung paano matagumpay na nabuhay ang teknolohiya ng BPF, na binuo noong unang bahagi ng nineties sa loob ng isang-kapat ng isang siglo at hanggang sa katapusan ay nakahanap ng mga bagong aplikasyon. Gayunpaman, katulad ng paglipat mula sa mga stack machine patungo sa RISC, na nagsilbing impetus para sa pagbuo ng klasikong BPF, noong 32s nagkaroon ng paglipat mula sa 64-bit hanggang XNUMX-bit na mga makina at ang klasikong BPF ay nagsimulang maging lipas na. Bilang karagdagan, ang mga kakayahan ng klasikong BPF ay napakalimitado, at bilang karagdagan sa hindi napapanahong arkitektura - wala kaming kakayahang i-save ang estado sa pagitan ng mga tawag sa mga programa ng BPF, walang posibilidad ng direktang pakikipag-ugnayan ng gumagamit, walang posibilidad na makipag-ugnayan kasama ang kernel, maliban sa pagbabasa ng limitadong bilang ng mga patlang ng istraktura sk_buff at paglulunsad ng pinakasimpleng mga function ng helper, hindi mo mababago ang mga nilalaman ng mga packet at i-redirect ang mga ito.

Sa katunayan, sa kasalukuyan ang lahat ng natitira sa klasikong BPF sa Linux ay ang interface ng API, at sa loob ng kernel ang lahat ng mga klasikong programa, maging ito ay mga socket filter o seccomp filter, ay awtomatikong isinalin sa isang bagong format, Extended BPF. (Tatalakayin natin ang eksaktong paraan kung paano ito nangyayari sa susunod na artikulo.)

Ang paglipat sa isang bagong arkitektura ay nagsimula noong 2013, nang iminungkahi ni Alexey Starovoitov ang isang scheme ng pag-update ng BPF. Noong 2014 ang kaukulang mga patch nagsimulang lumitaw sa kaibuturan. Sa pagkakaintindi ko, ang paunang plano ay para lamang ma-optimize ang arkitektura at JIT compiler upang tumakbo nang mas mahusay sa 64-bit na mga makina, ngunit sa halip ang mga pag-optimize na ito ay minarkahan ang simula ng isang bagong kabanata sa pag-unlad ng Linux.

Sasaklawin ng mga karagdagang artikulo sa seryeng ito ang arkitektura at mga aplikasyon ng bagong teknolohiya, na unang kilala bilang panloob na BPF, pagkatapos ay pinalawig na BPF, at ngayon ay BPF na lang.

sanggunian

  1. Steven McCanne at Van Jacobson, "Ang BSD Packet Filter: Isang Bagong Arkitektura para sa User-level Packet Capture", https://www.tcpdump.org/papers/bpf-usenix93.pdf
  2. Steven McCanne, "libpcap: Isang Arkitektura at Pamamaraan sa Pag-optimize para sa Pagkuha ng Packet", https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. IPtable U32 Match Tutorial.
  5. BPF - ang nakalimutang bytecode: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. Ipinapakilala ang BPF Tool: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. Isang pangkalahatang-ideya ng seccom: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: Mga lalagyan at seguridad: seccom
  11. habr: Pagbukod ng mga daemon gamit ang systemd o "hindi mo kailangan ng Docker para dito!"
  12. Paul Chaignon, "strace --seccomp-bpf: a look under the hood", https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. netsniff-ng: http://netsniff-ng.org/

Pinagmulan: www.habr.com

Magdagdag ng komento