BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Yn y dechrau roedd technoleg ac fe'i gelwir yn BPF. Edrychon ni arni blaenorol, Erthygl Hen Destament y gyfres hon. Yn 2013, trwy ymdrechion Alexei Starovoitov a Daniel Borkman, datblygwyd fersiwn well ohono, wedi'i optimeiddio ar gyfer peiriannau 64-bit modern, a'i gynnwys yn y cnewyllyn Linux. Galwyd y dechnoleg newydd hon yn fyr yn BPF Mewnol, yna fe'i hailenwyd yn BPF Estynedig, ac yn awr, ar ôl sawl blwyddyn, mae pawb yn ei alw'n BPF.

Yn fras, mae BPF yn caniatáu ichi redeg cod mympwyol a gyflenwir gan ddefnyddwyr yn y gofod cnewyllyn Linux, a daeth y bensaernïaeth newydd mor llwyddiannus fel y bydd angen dwsin yn fwy o erthyglau arnom i ddisgrifio ei holl gymwysiadau. (Yr unig beth na wnaeth y datblygwyr yn dda, fel y gwelwch yn y cod perfformiad isod, oedd creu logo gweddus.)

Mae'r erthygl hon yn disgrifio strwythur y peiriant rhithwir BPF, rhyngwynebau cnewyllyn ar gyfer gweithio gyda BPF, offer datblygu, yn ogystal â throsolwg byr, byr iawn o alluoedd presennol, h.y. popeth y bydd ei angen arnom yn y dyfodol ar gyfer astudiaeth ddyfnach o gymwysiadau ymarferol BPF.
BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Crynodeb o'r erthygl

Cyflwyniad i bensaernïaeth BPF. Yn gyntaf, byddwn yn cymryd golwg aderyn o bensaernïaeth BPF ac yn amlinellu'r prif gydrannau.

Cofrestrau a system orchymyn y peiriant rhithwir BPF. Eisoes yn cael syniad o'r bensaernïaeth yn ei gyfanrwydd, byddwn yn disgrifio strwythur y peiriant rhithwir BPF.

Cylch bywyd gwrthrychau BPF, system ffeiliau bpffs. Yn yr adran hon, byddwn yn edrych yn agosach ar gylch bywyd gwrthrychau BPF - rhaglenni a mapiau.

Rheoli gwrthrychau gan ddefnyddio'r alwad system bpf. Gyda pheth dealltwriaeth o'r system eisoes yn ei lle, byddwn yn edrych o'r diwedd ar sut i greu a thrin gwrthrychau o ofod defnyddwyr gan ddefnyddio galwad system arbennig − bpf(2).

Пишем программы BPF с помощью libbpf. Wrth gwrs, gallwch chi ysgrifennu rhaglenni gan ddefnyddio galwad system. Ond mae'n anodd. Ar gyfer senario mwy realistig, datblygodd rhaglenwyr niwclear lyfrgell libbpf. Byddwn yn creu sgerbwd cais BPF sylfaenol y byddwn yn ei ddefnyddio mewn enghreifftiau dilynol.

Cynorthwywyr Cnewyllyn. Yma byddwn yn dysgu sut y gall rhaglenni BPF gael mynediad at swyddogaethau cynorthwyydd cnewyllyn - offeryn sydd, ynghyd â mapiau, yn ehangu galluoedd y BPF newydd yn sylfaenol o'i gymharu â'r un clasurol.

Mynediad at fapiau o raglenni BPF. Erbyn hyn, byddwn yn gwybod digon i ddeall yn union sut y gallwn greu rhaglenni sy'n defnyddio mapiau. A gadewch i ni hyd yn oed gymryd cipolwg cyflym ar y dilysydd gwych a nerthol.

Offer datblygu. Adran gymorth ar sut i gydosod y cyfleustodau a'r cnewyllyn gofynnol ar gyfer arbrofion.

Casgliad. Ar ddiwedd yr erthygl, bydd y rhai sy'n darllen mor bell yn dod o hyd i eiriau ysgogol a disgrifiad byr o'r hyn fydd yn digwydd yn yr erthyglau canlynol. Byddwn hefyd yn rhestru nifer o ddolenni ar gyfer hunan-astudio ar gyfer y rhai nad oes ganddynt yr awydd na'r gallu i aros am y parhad.

Cyflwyniad i Bensaernïaeth BPF

Cyn i ni ddechrau ystyried pensaernïaeth BPF, byddwn yn cyfeirio un tro olaf (oh) at BPF clasurol, a ddatblygwyd fel ymateb i ddyfodiad peiriannau RISC a datrysodd y broblem o hidlo pecynnau effeithlon. Trodd y bensaernïaeth mor llwyddiannus fel, ar ôl cael ei geni yn y nawdegau serth yn Berkeley UNIX, fe'i porthwyd i'r rhan fwyaf o systemau gweithredu presennol, goroesodd i'r ugeiniau gwallgof ac mae'n dal i ddod o hyd i gymwysiadau newydd.

Datblygwyd y BPF newydd fel ymateb i hollbresenoldeb peiriannau 64-bit, gwasanaethau cwmwl a'r angen cynyddol am offer ar gyfer creu SDN (Soffer-dcoeth networking). Wedi'i ddatblygu gan beirianwyr rhwydwaith cnewyllyn fel disodli gwell ar gyfer y BPF clasurol, daeth y BPF newydd yn llythrennol chwe mis yn ddiweddarach o hyd i gymwysiadau yn y dasg anodd o olrhain systemau Linux, ac yn awr, chwe blynedd ar ôl ei ymddangosiad, bydd angen erthygl nesaf gyfan arnom yn unig i rhestru'r gwahanol fathau o raglenni.

Lluniau doniol

Yn greiddiol iddo, mae BPF yn beiriant rhithwir blwch tywod sy'n eich galluogi i redeg cod “mympwyol” yn y gofod cnewyllyn heb beryglu diogelwch. Mae rhaglenni BPF yn cael eu creu yng ngofod defnyddwyr, eu llwytho i mewn i'r cnewyllyn, a'u cysylltu â rhyw ffynhonnell digwyddiad. Gallai digwyddiad gynnwys, er enghraifft, danfon pecyn i ryngwyneb rhwydwaith, lansio rhyw swyddogaeth cnewyllyn, ac ati. Yn achos pecyn, bydd gan y rhaglen BPF fynediad at ddata a metadata'r pecyn (ar gyfer darllen ac, o bosibl, ysgrifennu, yn dibynnu ar y math o raglen); yn achos rhedeg swyddogaeth cnewyllyn, dadleuon y swyddogaeth, gan gynnwys awgrymiadau i gof cnewyllyn, ac ati.

Gadewch i ni edrych yn agosach ar y broses hon. I ddechrau, gadewch i ni siarad am y gwahaniaeth cyntaf o'r BPF clasurol, y mae rhaglenni wedi'u hysgrifennu yn y cydosodwr ar eu cyfer. Yn y fersiwn newydd, ehangwyd y bensaernïaeth fel y gellid ysgrifennu rhaglenni mewn ieithoedd lefel uchel, yn bennaf, wrth gwrs, yn C. Ar gyfer hyn, datblygwyd backend ar gyfer llvm, sy'n eich galluogi i gynhyrchu beitcode ar gyfer pensaernïaeth BPF.

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Cynlluniwyd pensaernïaeth BPF, yn rhannol, i redeg yn effeithlon ar beiriannau modern. Er mwyn gwneud i hyn weithio'n ymarferol, mae'r cod beit BPF, ar ôl ei lwytho i'r cnewyllyn, yn cael ei drosi i god brodorol gan ddefnyddio cydran o'r enw casglwr JIT (Just In Time). Nesaf, os cofiwch, yn BPF clasurol, llwythwyd y rhaglen i'r cnewyllyn a'i gysylltu â ffynhonnell y digwyddiad yn atomig - yng nghyd-destun galwad system sengl. Yn y bensaernïaeth newydd, mae hyn yn digwydd mewn dau gam - yn gyntaf, mae'r cod yn cael ei lwytho i mewn i'r cnewyllyn gan ddefnyddio galwad system bpf(2)ac yna, yn ddiweddarach, trwy fecanweithiau eraill sy'n amrywio yn dibynnu ar y math o raglen, mae'r rhaglen yn cysylltu â ffynhonnell y digwyddiad.

Yma efallai y bydd gan y darllenydd gwestiwn: a oedd yn bosibl? Sut mae diogelwch gweithredu cod o'r fath wedi'i warantu? Mae diogelwch gweithredu wedi'i warantu i ni gan y cam o lwytho rhaglenni BPF o'r enw verifier (yn Saesneg gelwir y cam hwn yn verifier a byddaf yn parhau i ddefnyddio'r gair Saesneg):

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Mae dilysydd yn ddadansoddwr statig sy'n sicrhau nad yw rhaglen yn amharu ar weithrediad arferol y cnewyllyn. Nid yw hyn, gyda llaw, yn golygu na all y rhaglen ymyrryd â gweithrediad y system - gall rhaglenni BPF, yn dibynnu ar y math, ddarllen ac ailysgrifennu adrannau o gof cnewyllyn, dychwelyd gwerthoedd swyddogaethau, trimio, atodi, ailysgrifennu a hyd yn oed pecynnau rhwydwaith ymlaen. Mae'r dilysydd yn gwarantu na fydd rhedeg rhaglen BPF yn chwalu'r cnewyllyn ac na fydd rhaglen sydd, yn unol â'r rheolau, â mynediad ysgrifennu, er enghraifft, data pecyn sy'n mynd allan, yn gallu trosysgrifo'r cof cnewyllyn y tu allan i'r pecyn. Byddwn yn edrych ar y dilysydd ychydig yn fwy manwl yn yr adran gyfatebol, ar ôl i ni ddod yn gyfarwydd â holl gydrannau eraill BPF.

Felly beth ydyn ni wedi'i ddysgu hyd yn hyn? Mae'r defnyddiwr yn ysgrifennu rhaglen yn C, yn ei llwytho i mewn i'r cnewyllyn gan ddefnyddio galwad system bpf(2), lle caiff ei wirio gan ddilysydd a'i gyfieithu i god byte brodorol. Yna mae'r un defnyddiwr neu ddefnyddiwr arall yn cysylltu'r rhaglen â ffynhonnell y digwyddiad ac mae'n dechrau gweithredu. Mae angen gwahanu cist a chysylltiad am sawl rheswm. Yn gyntaf, mae rhedeg dilysydd yn gymharol ddrud a thrwy lawrlwytho'r un rhaglen sawl gwaith rydym yn gwastraffu amser cyfrifiadur. Yn ail, mae union sut mae rhaglen wedi'i chysylltu yn dibynnu ar ei math, ac efallai na fydd un rhyngwyneb “cyffredinol” a ddatblygwyd flwyddyn yn ôl yn addas ar gyfer mathau newydd o raglenni. (Er nawr bod y bensaernïaeth yn dod yn fwy aeddfed, mae yna syniad i uno'r rhyngwyneb hwn ar y lefel libbpf.)

Efallai y bydd y darllenydd astud yn sylwi nad ydym wedi gorffen gyda'r lluniau eto. Yn wir, nid yw pob un o'r uchod yn esbonio sut mae BPF yn newid y darlun yn sylfaenol o'i gymharu â BPF clasurol. Dau arloesiad sy'n ehangu cwmpas cymhwysedd yn sylweddol yw'r gallu i ddefnyddio swyddogaethau cynorthwyydd cof a chnewyllyn a rennir. Yn BPF, mae cof a rennir yn cael ei weithredu gan ddefnyddio mapiau fel y'u gelwir - strwythurau data a rennir gydag API penodol. Mae'n debyg iddynt gael yr enw hwn oherwydd y math cyntaf o fap i ymddangos oedd tabl stwnsh. Yna ymddangosodd araeau, tablau stwnsh lleol (fesul-CPU) ac araeau lleol, coed chwilio, mapiau yn cynnwys awgrymiadau i raglenni BPF a llawer mwy. Yr hyn sy'n ddiddorol i ni nawr yw bod gan raglenni BPF bellach y gallu i barhau cyflwr rhwng galwadau a'i rannu gyda rhaglenni eraill a gyda gofod defnyddwyr.

Gellir cyrchu mapiau o brosesau defnyddwyr gan ddefnyddio galwad system bpf(2), ac o raglenni BPF sy'n rhedeg yn y cnewyllyn gan ddefnyddio swyddogaethau cynorthwyydd. Ar ben hynny, mae cynorthwywyr yn bodoli nid yn unig i weithio gyda mapiau, ond hefyd i gael mynediad at alluoedd cnewyllyn eraill. Er enghraifft, gall rhaglenni BPF ddefnyddio swyddogaethau cynorthwyydd i anfon pecynnau ymlaen i ryngwynebau eraill, cynhyrchu digwyddiadau perf, cyrchu strwythurau cnewyllyn, ac ati.

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

I grynhoi, mae BPF yn darparu'r gallu i lwytho cod defnyddiwr mympwyol, h.y., wedi'i brofi gan ddilysydd, i ofod cnewyllyn. Gall y cod hwn arbed cyflwr rhwng galwadau a chyfnewid data gyda gofod defnyddwyr, ac mae ganddo hefyd fynediad i is-systemau cnewyllyn a ganiateir gan y math hwn o raglen.

Mae hyn eisoes yn debyg i'r galluoedd a ddarperir gan fodiwlau cnewyllyn, y mae gan BPF rai manteision o'u cymharu â hwy (wrth gwrs, dim ond cymwysiadau tebyg y gallwch eu cymharu, er enghraifft, olrhain system - ni allwch ysgrifennu gyrrwr mympwyol gyda BPF). Gallwch nodi trothwy mynediad is (nid yw rhai cyfleustodau sy'n defnyddio BPF yn ei gwneud yn ofynnol i'r defnyddiwr feddu ar sgiliau rhaglennu cnewyllyn, na sgiliau rhaglennu yn gyffredinol), diogelwch amser rhedeg (codwch eich llaw yn y sylwadau ar gyfer y rhai nad ydynt wedi torri'r system wrth ysgrifennu neu fodiwlau profi), atomigedd - mae amser segur wrth ail-lwytho modiwlau, ac mae'r is-system BPF yn sicrhau na chaiff unrhyw ddigwyddiadau eu methu (a bod yn deg, nid yw hyn yn wir ar gyfer pob math o raglenni BPF).

Mae presenoldeb galluoedd o'r fath yn gwneud BPF yn offeryn cyffredinol ar gyfer ehangu'r cnewyllyn, a gadarnheir yn ymarferol: mae mwy a mwy o fathau newydd o raglenni yn cael eu hychwanegu at BPF, mae mwy a mwy o gwmnïau mawr yn defnyddio BPF ar weinyddion ymladd 24 × 7, mwy a mwy mae busnesau newydd yn adeiladu eu busnes ar atebion sy'n seiliedig ar BPF. Defnyddir BPF ym mhobman: wrth amddiffyn rhag ymosodiadau DDoS, creu SDN (er enghraifft, gweithredu rhwydweithiau ar gyfer kubernetes), fel prif offeryn olrhain system a chasglwr ystadegau, mewn systemau canfod ymyrraeth a systemau blychau tywod, ac ati.

Gadewch i ni orffen rhan trosolwg yr erthygl yma ac edrych ar y peiriant rhithwir a'r ecosystem BPF yn fwy manwl.

Digression: cyfleustodau

Er mwyn gallu rhedeg yr enghreifftiau yn yr adrannau canlynol, efallai y bydd angen nifer o gyfleustodau, o leiaf llvm/clang gyda chefnogaeth bpf a bpftool. Yn adran Offer datblygu Gallwch ddarllen y cyfarwyddiadau ar gyfer cydosod y cyfleustodau, yn ogystal â'ch cnewyllyn. Rhoddir yr adran hon isod er mwyn peidio ag aflonyddu ar gytgord ein cyflwyniad.

Cofrestrau Peiriannau Rhithwir BPF a System Gyfarwyddiadau

Datblygwyd pensaernïaeth a system orchymyn BPF gan ystyried y ffaith y bydd rhaglenni'n cael eu hysgrifennu yn yr iaith C ac, ar ôl eu llwytho i'r cnewyllyn, eu cyfieithu i'r cod brodorol. Felly, dewiswyd nifer y cofrestrau a'r set o orchmynion gyda llygad ar groesffordd, yn yr ystyr fathemategol, o alluoedd peiriannau modern. Yn ogystal, gosodwyd cyfyngiadau amrywiol ar raglenni, er enghraifft, tan yn ddiweddar nid oedd yn bosibl ysgrifennu dolenni ac is-reolweithiau, a chyfyngwyd nifer y cyfarwyddiadau i 4096 (bellach gall rhaglenni breintiedig lwytho hyd at filiwn o gyfarwyddiadau).

Mae gan BPF un ar ddeg o gofrestrau 64-bit hygyrch i ddefnyddwyr r0-r10 a chownter rhaglen. Cofrestrwch r10 yn cynnwys pwyntydd ffrâm ac yn ddarllenadwy yn unig. Mae gan raglenni fynediad at bentwr 512-beit ar amser rhedeg a swm diderfyn o gof a rennir ar ffurf mapiau.

Caniateir i raglenni BPF redeg set benodol o gynorthwywyr cnewyllyn tebyg i raglen ac, yn fwy diweddar, swyddogaethau rheolaidd. Gall pob swyddogaeth a elwir gymryd hyd at bum dadl, wedi'u pasio mewn cofrestri r1-r5, ac mae'r gwerth dychwelyd yn cael ei drosglwyddo i r0. Mae'n cael ei warantu bod ar ôl dychwelyd o'r swyddogaeth, cynnwys y cofrestri r6-r9 Ni fydd yn newid.

Ar gyfer cyfieithu rhaglen yn effeithlon, cofrestri r0-r11 ar gyfer pob pensaernïaeth a gefnogir wedi'u mapio'n unigryw i gofrestrau real, gan ystyried nodweddion ABI y bensaernïaeth gyfredol. Er enghraifft, ar gyfer x86_64 cofrestri r1-r5, a ddefnyddir i basio paramedrau swyddogaeth, yn cael eu harddangos ar rdi, rsi, rdx, rcx, r8, a ddefnyddir i drosglwyddo paramedrau i swyddogaethau ymlaen x86_64. Er enghraifft, mae'r cod ar y chwith yn trosi i'r cod ar y dde fel hyn:

1:  (b7) r1 = 1                    mov    $0x1,%rdi
2:  (b7) r2 = 2                    mov    $0x2,%rsi
3:  (b7) r3 = 3                    mov    $0x3,%rdx
4:  (b7) r4 = 4                    mov    $0x4,%rcx
5:  (b7) r5 = 5                    mov    $0x5,%r8
6:  (85) call pc+1                 callq  0x0000000000001ee8

Cofrestrwch r0 hefyd yn cael ei ddefnyddio i ddychwelyd canlyniad gweithredu rhaglen, ac yn y gofrestr r1 mae'r rhaglen yn cael ei throsglwyddo pwyntydd i'r cyd-destun - yn dibynnu ar y math o raglen, gallai hyn fod, er enghraifft, yn strwythur struct xdp_md (ar gyfer XDP) neu strwythur struct __sk_buff (ar gyfer rhaglenni rhwydwaith gwahanol) neu strwythur struct pt_regs (ar gyfer gwahanol fathau o raglenni olrhain), ac ati.

Felly, roedd gennym ni set o gofrestrau, cynorthwywyr cnewyllyn, pentwr, pwyntydd cyd-destun a chof a rennir ar ffurf mapiau. Nid bod hyn i gyd yn gwbl angenrheidiol ar y daith, ond...

Gadewch i ni barhau â'r disgrifiad a siarad am y system orchymyn ar gyfer gweithio gyda'r gwrthrychau hyn. I gyd (Bron i gyd) Mae gan gyfarwyddiadau BPF faint 64-did sefydlog. Os edrychwch chi ar un cyfarwyddyd ar beiriant Big Endian 64-did fe welwch chi

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Yma Code - dyma amgodio'r cyfarwyddyd, Dst/Src yw amgodiadau'r derbynnydd a'r ffynhonnell, yn y drefn honno, Off - mewnoliad 16-did wedi'i lofnodi, a Imm yn gyfanrif wedi'i lofnodi 32-did a ddefnyddir mewn rhai cyfarwyddiadau (yn debyg i gysonyn cBPF K). Amgodio Code mae ganddo un o ddau fath:

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Mae dosbarthiadau cyfarwyddyd 0, 1, 2, 3 yn diffinio gorchmynion ar gyfer gweithio gyda chof. Hwy yn cael eu galw, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, yn y drefn honno. Dosbarthiadau 4, 7 (BPF_ALU, BPF_ALU64) yn cynnwys set o gyfarwyddiadau ALU. Dosbarthiadau 5, 6 (BPF_JMP, BPF_JMP32) cynnwys cyfarwyddiadau naid.

Mae'r cynllun pellach ar gyfer astudio system gyfarwyddiadau BPF fel a ganlyn: yn lle rhestru'r holl gyfarwyddiadau a'u paramedrau'n fanwl, byddwn yn edrych ar ychydig o enghreifftiau yn yr adran hon ac oddi wrthynt bydd yn dod yn glir sut mae'r cyfarwyddiadau'n gweithio mewn gwirionedd a sut i dadosod unrhyw ffeil ddeuaidd ar gyfer BPF â llaw. Er mwyn cydgrynhoi'r deunydd yn ddiweddarach yn yr erthygl, byddwn hefyd yn cwrdd â chyfarwyddiadau unigol yn yr adrannau am y Dilysydd, casglwr JIT, cyfieithu BPF clasurol, yn ogystal ag wrth astudio mapiau, swyddogaethau galw, ac ati.

Pan fyddwn yn siarad am gyfarwyddiadau unigol, byddwn yn cyfeirio at y ffeiliau craidd bpf.h и bpf_common.h, sy'n diffinio codau rhifiadol cyfarwyddiadau BPF. Wrth astudio pensaernïaeth ar eich pen eich hun a/neu dosrannu deuaidd, gallwch ddod o hyd i semanteg yn y ffynonellau canlynol, wedi'u didoli yn nhrefn cymhlethdod: Manyleb answyddogol eBPF, Canllaw Cyfeirio BPF a XDP, Set Cyfarwyddiadau, Dogfennaeth/rhwydweithio/filter.txt ac, wrth gwrs, yn y cod ffynhonnell Linux - dilysydd, JIT, cyfieithydd BPF.

Enghraifft: dadosod BPF yn eich pen

Gadewch i ni edrych ar enghraifft lle rydym yn llunio rhaglen readelf-example.c ac edrych ar y deuaidd canlyniadol. Byddwn yn datgelu'r cynnwys gwreiddiol readelf-example.c isod, ar ôl i ni adfer ei resymeg o godau deuaidd:

$ clang -target bpf -c readelf-example.c -o readelf-example.o -O2
$ llvm-readelf -x .text readelf-example.o
Hex dump of section '.text':
0x00000000 b7000000 01000000 15010100 00000000 ................
0x00000010 b7000000 02000000 95000000 00000000 ................

Colofn gyntaf yn yr allbwn readelf yn fewnoliad ac felly mae ein rhaglen yn cynnwys pedwar gorchymyn:

Code Dst Src Off  Imm
b7   0   0   0000 01000000
15   0   1   0100 00000000
b7   0   0   0000 02000000
95   0   0   0000 00000000

Mae codau gorchymyn yn gyfartal b7, 15, b7 и 95. Dwyn i gof mai'r tri rhan lleiaf arwyddocaol yw'r dosbarth cyfarwyddyd. Yn ein hachos ni, mae pedwerydd rhan yr holl gyfarwyddiadau yn wag, felly mae'r dosbarthiadau cyfarwyddiadau yn 7, 5, 7, 5, yn y drefn honno. Dosbarth 7 yw BPF_ALU64, a 5 yn BPF_JMP. Ar gyfer y ddau ddosbarth, mae'r fformat cyfarwyddyd yr un peth (gweler uchod) a gallwn ailysgrifennu ein rhaglen fel hyn (ar yr un pryd byddwn yn ailysgrifennu'r colofnau sy'n weddill ar ffurf ddynol):

Op S  Class   Dst Src Off  Imm
b  0  ALU64   0   0   0    1
1  0  JMP     0   1   1    0
b  0  ALU64   0   0   0    2
9  0  JMP     0   0   0    0

Gweithredu b dosbarth ALU64 - A yw BPF_MOV. Mae'n aseinio gwerth i'r gofrestr cyrchfan. Os gosodir y did s (ffynhonnell), yna cymerir y gwerth o'r gofrestr ffynhonnell, ac os, fel yn ein hachos ni, nad yw wedi'i osod, yna cymerir y gwerth o'r maes Imm. Felly yn y cyfarwyddiadau cyntaf a'r trydydd cyfarwyddiadau rydym yn perfformio'r llawdriniaeth r0 = Imm. Ymhellach, mae gweithrediad dosbarth 1 JMP yn BPF_JEQ (neidio os yn gyfartal). Yn ein hachos ni, ers y darn S yn sero, mae'n cymharu gwerth y gofrestr ffynhonnell gyda'r maes Imm. Os yw'r gwerthoedd yn cyd-daro, yna mae'r trawsnewidiad yn digwydd i PC + Offlle PC, fel arferol, yn cynnwys cyfeiriad y cyfarwyddyd nesaf. Yn olaf, mae Gweithrediad Dosbarth 9 JMP yn BPF_EXIT. Mae'r cyfarwyddyd hwn yn terfynu'r rhaglen, gan ddychwelyd i'r cnewyllyn r0. Gadewch i ni ychwanegu colofn newydd at ein tabl:

Op    S  Class   Dst Src Off  Imm    Disassm
MOV   0  ALU64   0   0   0    1      r0 = 1
JEQ   0  JMP     0   1   1    0      if (r1 == 0) goto pc+1
MOV   0  ALU64   0   0   0    2      r0 = 2
EXIT  0  JMP     0   0   0    0      exit

Gallwn ailysgrifennu hwn mewn ffurf fwy cyfleus:

     r0 = 1
     if (r1 == 0) goto END
     r0 = 2
END:
     exit

Os cofiwn beth sydd yn y gofrestr r1 mae'r rhaglen yn cael ei drosglwyddo pwyntydd i'r cyd-destun o'r cnewyllyn, ac yn y gofrestr r0 dychwelir y gwerth i'r cnewyllyn, yna gallwn weld os yw'r pwyntydd i'r cyd-destun yn sero, yna byddwn yn dychwelyd 1, ac fel arall - 2. Gadewch i ni wirio ein bod yn iawn trwy edrych ar y ffynhonnell:

$ cat readelf-example.c
int foo(void *ctx)
{
        return ctx ? 2 : 1;
}

Ydy, mae'n rhaglen ddiystyr, ond mae'n trosi'n bedwar cyfarwyddyd syml yn unig.

Enghraifft o eithriad: cyfarwyddyd 16-beit

Soniasom yn gynharach fod rhai cyfarwyddiadau yn cymryd mwy na 64 did. Mae hyn yn berthnasol, er enghraifft, i gyfarwyddiadau lddw (Cod = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — llwythwch air dwbl o'r meysydd i'r gofrestr Imm... Y gwir yw hynny Imm Mae ganddo faint o 32, a gair dwbl yw 64 did, felly ni fydd llwytho gwerth uniongyrchol 64-did i gofrestr mewn un cyfarwyddyd 64-did yn gweithio. I wneud hyn, defnyddir dau gyfarwyddiad cyfagos i storio ail ran y gwerth 64-did yn y maes Imm. Enghraifft:

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

Dim ond dau gyfarwyddiad sydd mewn rhaglen ddeuaidd:

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

Byddwn yn cyfarfod eto gyda chyfarwyddiadau lddw, pan fyddwn yn sôn am adleoli a gweithio gyda mapiau.

Enghraifft: dadosod BPF gan ddefnyddio offer safonol

Felly, rydym wedi dysgu darllen codau deuaidd BPF ac rydym yn barod i ddosrannu unrhyw gyfarwyddyd os oes angen. Fodd bynnag, mae'n werth dweud ei bod yn ymarferol yn fwy cyfleus ac yn gyflymach i ddadosod rhaglenni gan ddefnyddio offer safonol, er enghraifft:

$ llvm-objdump -d x64.o

Disassembly of section .text:

0000000000000000 <foo>:
 0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
 2: 95 00 00 00 00 00 00 00 exit

Cylch bywyd gwrthrychau BPF, system ffeiliau bpffs

(Dysgais rai o'r manylion a ddisgrifir yn yr isadran hon yn gyntaf oddi wrth ymprydio Alexei Starovoytov i mewn Blog BPF.)

Mae gwrthrychau BPF - rhaglenni a mapiau - yn cael eu creu o ofod defnyddwyr gan ddefnyddio gorchmynion BPF_PROG_LOAD и BPF_MAP_CREATE galwad system bpf(2), byddwn yn siarad am sut yn union y mae hyn yn digwydd yn yr adran nesaf. Mae hyn yn creu strwythurau data cnewyllyn ac ar gyfer pob un ohonynt refcount (cyfrif cyfeirnod) wedi'i osod i un, a disgrifydd ffeil sy'n pwyntio at y gwrthrych yn cael ei ddychwelyd i'r defnyddiwr. Ar ôl i'r handlen gael ei chau refcount gostyngir y gwrthrych gan un, a phan fydd yn cyrraedd sero, caiff y gwrthrych ei ddinistrio.

Os yw'r rhaglen yn defnyddio mapiau, yna refcount cynyddir y mapiau hyn o un ar ôl llwytho'r rhaglen, h.y. gellir cau eu disgrifyddion ffeil o'r broses defnyddiwr a llonydd refcount ni fydd yn dod yn sero:

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Ar ôl llwytho rhaglen yn llwyddiannus, byddwn fel arfer yn ei hatodi i ryw fath o gynhyrchydd digwyddiad. Er enghraifft, gallwn ei roi ar ryngwyneb rhwydwaith i brosesu pecynnau sy'n dod i mewn neu ei gysylltu â rhai tracepoint yn y craidd. Ar y pwynt hwn, bydd y rhifydd cyfeirio hefyd yn cynyddu o un a byddwn yn gallu cau'r disgrifydd ffeil yn y rhaglen llwythwr.

Beth sy'n digwydd os byddwn nawr yn cau'r cychwynnydd? Mae'n dibynnu ar y math o generadur digwyddiad (bachyn). Bydd yr holl fachau rhwydwaith yn bodoli ar ôl i'r llwythwr gwblhau, dyma'r bachau byd-eang fel y'u gelwir. Ac, er enghraifft, bydd rhaglenni olrhain yn cael eu rhyddhau ar ôl i’r broses a’u creodd ddod i ben (ac felly fe’u gelwir yn lleol, o “leol i’r broses”). Yn dechnegol, mae gan fachau lleol bob amser ddisgrifydd ffeil cyfatebol yn y gofod defnyddiwr ac felly'n cau pan fydd y broses ar gau, ond nid oes gan fachau byd-eang. Yn y ffigur canlynol, gan ddefnyddio croesau coch, rwy'n ceisio dangos sut mae terfynu'r rhaglen llwythwr yn effeithio ar oes gwrthrychau yn achos bachau lleol a byd-eang.

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Pam mae gwahaniaeth rhwng bachau lleol a byd-eang? Mae rhedeg rhai mathau o raglenni rhwydwaith yn gwneud synnwyr heb ofod defnyddiwr, er enghraifft, dychmygwch amddiffyniad DDoS - mae'r cychwynnwr yn ysgrifennu'r rheolau ac yn cysylltu'r rhaglen BPF â'r rhyngwyneb rhwydwaith, ac ar ôl hynny gall y cychwynnwr fynd a lladd ei hun. Ar y llaw arall, dychmygwch raglen olrhain dadfygio y gwnaethoch chi ei hysgrifennu ar eich pengliniau mewn deng munud - pan fydd wedi'i chwblhau, hoffech chi pe bai dim sothach ar ôl yn y system, a bydd bachau lleol yn sicrhau hynny.

Ar y llaw arall, dychmygwch eich bod am gysylltu â man olrhain yn y cnewyllyn a chasglu ystadegau dros nifer o flynyddoedd. Yn yr achos hwn, byddech am gwblhau'r rhan defnyddiwr a dychwelyd at yr ystadegau o bryd i'w gilydd. Mae'r system ffeiliau bpf yn rhoi'r cyfle hwn. Mae'n system ffug-ffeil er cof yn unig sy'n caniatáu creu ffeiliau sy'n cyfeirio at wrthrychau BPF a thrwy hynny gynyddu refcount gwrthrychau. Ar ôl hyn, gall y llwythwr adael, a bydd y gwrthrychau a greodd yn aros yn fyw.

BPF ar gyfer y rhai bach, rhan un: BPF estynedig

Gelwir creu ffeiliau mewn bpffs sy'n cyfeirio at wrthrychau BPF yn "binio" (fel yn yr ymadrodd canlynol: "gall proses binio rhaglen neu fap BPF"). Mae creu gwrthrychau ffeil ar gyfer gwrthrychau BPF yn gwneud synnwyr nid yn unig ar gyfer ymestyn bywyd gwrthrychau lleol, ond hefyd ar gyfer defnyddioldeb gwrthrychau byd-eang - gan fynd yn ôl at yr enghraifft gyda'r rhaglen amddiffyn DDoS fyd-eang, rydym am allu dod i edrych ar ystadegau o amser i amser.

Mae system ffeiliau BPF fel arfer wedi'i gosod i mewn /sys/fs/bpf, ond gellir ei osod yn lleol hefyd, er enghraifft, fel hyn:

$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

Mae enwau system ffeil yn cael eu creu gan ddefnyddio'r gorchymyn BPF_OBJ_PIN Galwad system BPF. I ddarlunio, gadewch i ni gymryd rhaglen, ei llunio, ei huwchlwytho, a'i phinio i bpffs. Nid yw ein rhaglen yn gwneud unrhyw beth defnyddiol, dim ond y cod rydyn ni'n ei gyflwyno er mwyn i chi allu atgynhyrchu'r enghraifft:

$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
        return 0;
}

char _license[] __attribute__((section("license"), used)) = "GPL";

Gadewch i ni lunio'r rhaglen hon a chreu copi lleol o'r system ffeiliau bpffs:

$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

Nawr, gadewch i ni lawrlwytho ein rhaglen gan ddefnyddio'r cyfleustodau bpftool ac edrych ar y galwadau system sy'n cyd-fynd â nhw bpf(2) (rhai llinellau amherthnasol wedi'u tynnu o allbwn strace):

$ sudo strace -e bpf bpftool prog load ./test.o bpf-mountpoint/test
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="test", ...}, 120) = 3
bpf(BPF_OBJ_PIN, {pathname="bpf-mountpoint/test", bpf_fd=3}, 120) = 0

Yma rydym wedi llwytho'r rhaglen gan ddefnyddio BPF_PROG_LOAD, wedi derbyn disgrifydd ffeil o'r cnewyllyn 3 a defnyddio'r gorchymyn BPF_OBJ_PIN pinio'r disgrifydd ffeil hwn fel ffeil "bpf-mountpoint/test". Ar ôl hyn y rhaglen cychwynnydd bpftool gorffen gweithio, ond arhosodd ein rhaglen yn y cnewyllyn, er na wnaethom ei hatodi i unrhyw ryngwyneb rhwydwaith:

$ sudo bpftool prog | tail -3
783: xdp  name test  tag 5c8ba0cf164cb46c  gpl
        loaded_at 2020-05-05T13:27:08+0000  uid 0
        xlated 24B  jited 41B  memlock 4096B

Gallwn ddileu gwrthrych y ffeil fel arfer unlink(2) ac ar ôl hynny bydd y rhaglen gyfatebol yn cael ei dileu:

$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory

Dileu gwrthrychau

Wrth siarad am ddileu gwrthrychau, mae angen egluro, ar ôl i ni ddatgysylltu'r rhaglen o'r bachyn (generadur digwyddiad), na fydd un digwyddiad newydd yn sbarduno ei lansiad, fodd bynnag, bydd holl achosion cyfredol y rhaglen yn cael eu cwblhau yn y drefn arferol. .

Mae rhai mathau o raglenni BPF yn caniatáu ichi ddisodli'r rhaglen ar y hedfan, h.y. darparu atomigedd dilyniant replace = detach old program, attach new program. Yn yr achos hwn, bydd pob enghraifft weithredol o'r hen fersiwn o'r rhaglen yn gorffen eu gwaith, a bydd trinwyr digwyddiadau newydd yn cael eu creu o'r rhaglen newydd, ac mae “atomicity” yma yn golygu na fydd un digwyddiad yn cael ei golli.

Atodi rhaglenni i ffynonellau digwyddiadau

Yn yr erthygl hon, ni fyddwn yn disgrifio cysylltu rhaglenni â ffynonellau digwyddiadau ar wahân, gan ei bod yn gwneud synnwyr i astudio hyn yng nghyd-destun math penodol o raglen. Cm. enghraifft isod, lle rydym yn dangos sut mae rhaglenni fel XDP wedi'u cysylltu.

Trin Gwrthrychau Gan Ddefnyddio Galwad y System bpf

rhaglenni BPF

Mae holl wrthrychau BPF yn cael eu creu a'u rheoli o ofod defnyddwyr gan ddefnyddio galwad system bpf, sydd â'r prototeip canlynol:

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

Dyma'r tîm cmd yn un o werthoedd math enum bpf_cmd, attr — pwyntydd i baramedrau ar gyfer rhaglen benodol a size — maint gwrthrych yn ôl y pwyntydd, h.y. hyn fel arfer sizeof(*attr). Yn cnewyllyn 5.8 y system alwad bpf cefnogi 34 o wahanol orchmynion, a diffiniad union bpf_attr yn meddiannu 200 llinell. Ond ni ddylem gael ein dychryn gan hyn, gan y byddwn yn ymgyfarwyddo â'r gorchmynion a'r paramedrau dros gyfnod o sawl erthygl.

Gadewch i ni ddechrau gyda'r tîm BPF_PROG_LOAD, sy'n creu rhaglenni BPF - yn cymryd set o gyfarwyddiadau BPF ac yn ei lwytho i mewn i'r cnewyllyn. Ar hyn o bryd o lwytho, mae'r dilysydd yn cael ei lansio, ac yna'r casglwr JIT ac, ar ôl ei weithredu'n llwyddiannus, mae disgrifydd ffeil y rhaglen yn cael ei ddychwelyd i'r defnyddiwr. Gwelsom beth sy'n digwydd iddo nesaf yn yr adran flaenorol am gylchred bywyd gwrthrychau BPF.

Byddwn nawr yn ysgrifennu rhaglen arferol a fydd yn llwytho rhaglen BPF syml, ond yn gyntaf mae angen i ni benderfynu pa fath o raglen rydyn ni am ei llwytho - bydd yn rhaid i ni ddewis math ac o fewn fframwaith o'r math hwn, ysgrifennu rhaglen a fydd yn pasio'r prawf dilysydd. Fodd bynnag, er mwyn peidio â chymhlethu'r broses, dyma ateb parod: byddwn yn cymryd rhaglen fel BPF_PROG_TYPE_XDP, a fydd yn dychwelyd y gwerth XDP_PASS (hepgor pob pecyn). Yn y cydosodwr BPF mae'n edrych yn syml iawn:

r0 = 2
exit

Ar ôl i ni benderfynu ar y byddwn yn uwchlwytho, gallwn ddweud wrthych sut y byddwn yn ei wneud:

#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

static inline __u64 ptr_to_u64(const void *ptr)
{
        return (__u64) (unsigned long) ptr;
}

int main(void)
{
    struct bpf_insn insns[] = {
        {
            .code = BPF_ALU64 | BPF_MOV | BPF_K,
            .dst_reg = BPF_REG_0,
            .imm = XDP_PASS
        },
        {
            .code = BPF_JMP | BPF_EXIT
        },
    };

    union bpf_attr attr = {
        .prog_type = BPF_PROG_TYPE_XDP,
        .insns     = ptr_to_u64(insns),
        .insn_cnt  = sizeof(insns)/sizeof(insns[0]),
        .license   = ptr_to_u64("GPL"),
    };

    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

Mae digwyddiadau diddorol mewn rhaglen yn dechrau gyda'r diffiniad o arae insns - ein rhaglen BPF mewn cod peiriant. Yn yr achos hwn, mae pob cyfarwyddyd o'r rhaglen BPF wedi'i bacio i mewn i'r strwythur bpf_insn. Elfen gyntaf insns yn cydymffurfio â chyfarwyddiadau r0 = 2, yr ail - exit.

Encilio. Mae'r cnewyllyn yn diffinio macros mwy cyfleus ar gyfer ysgrifennu codau peiriant, a defnyddio'r ffeil pennawd cnewyllyn tools/include/linux/filter.h gallem ysgrifennu

struct bpf_insn insns[] = {
    BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
    BPF_EXIT_INSN()
};

Ond gan mai dim ond ar gyfer ysgrifennu profion yn y cnewyllyn ac erthyglau am BPF y mae ysgrifennu rhaglenni BPF mewn cod brodorol, nid yw absenoldeb y macros hyn yn cymhlethu bywyd y datblygwr mewn gwirionedd.

Ar ôl diffinio'r rhaglen BPF, symudwn ymlaen i'w lwytho i mewn i'r cnewyllyn. Ein set finimalaidd o baramedrau attr yn cynnwys y math o raglen, set a nifer y cyfarwyddiadau, y drwydded ofynnol, ac enw "woo", a ddefnyddiwn i ddod o hyd i'n rhaglen ar y system ar ôl ei lawrlwytho. Mae'r rhaglen, fel yr addawyd, yn cael ei llwytho i mewn i'r system gan ddefnyddio galwad system bpf.

Ar ddiwedd y rhaglen rydym yn y diwedd mewn dolen ddiddiwedd sy'n efelychu'r llwyth tâl. Hebddo, bydd y rhaglen yn cael ei lladd gan y cnewyllyn pan fydd y disgrifydd ffeil y dychwelwyd yr alwad system atom ar gau bpf, ac ni fyddwn yn ei weld yn y system.

Wel, rydym yn barod ar gyfer profi. Gadewch i ni ymgynnull a rhedeg y rhaglen o dan stracei wirio bod popeth yn gweithio fel y dylai:

$ clang -g -O2 simple-prog.c -o simple-prog

$ sudo strace ./simple-prog
execve("./simple-prog", ["./simple-prog"], 0x7ffc7b553480 /* 13 vars */) = 0
...
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0x7ffe03c4ed50, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_V
ERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(

Mae popeth yn iawn, bpf(2) dychwelodd handlen 3 i ni ac aethom i mewn i ddolen anfeidrol gyda pause(). Gadewch i ni geisio dod o hyd i'n rhaglen yn y system. I wneud hyn byddwn yn mynd i derfynell arall ac yn defnyddio'r cyfleustodau bpftool:

# bpftool prog | grep -A3 woo
390: xdp  name woo  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-31T24:66:44+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        pids simple-prog(10381)

Gwelwn fod rhaglen wedi'i llwytho ar y system woo y mae ei ID byd-eang yn 390 ac sydd ar y gweill ar hyn o bryd simple-prog mae disgrifydd ffeil agored yn pwyntio at y rhaglen (ac os simple-prog yn gorffen y swydd, felly woo bydd yn diflannu). Yn ôl y disgwyl, y rhaglen woo yn cymryd 16 beit - dau gyfarwyddiad - o godau deuaidd ym mhensaernïaeth BPF, ond yn ei ffurf frodorol (x86_64) mae eisoes yn 40 bytes. Gadewch i ni edrych ar ein rhaglen yn ei ffurf wreiddiol:

# bpftool prog dump xlated id 390
   0: (b7) r0 = 2
   1: (95) exit

dim syrpreis. Nawr, gadewch i ni edrych ar y cod a gynhyrchir gan y casglwr JIT:

# bpftool prog dump jited id 390
bpf_prog_3b185187f1855c4c_woo:
   0:   nopl   0x0(%rax,%rax,1)
   5:   push   %rbp
   6:   mov    %rsp,%rbp
   9:   sub    $0x0,%rsp
  10:   push   %rbx
  11:   push   %r13
  13:   push   %r14
  15:   push   %r15
  17:   pushq  $0x0
  19:   mov    $0x2,%eax
  1e:   pop    %rbx
  1f:   pop    %r15
  21:   pop    %r14
  23:   pop    %r13
  25:   pop    %rbx
  26:   leaveq
  27:   retq

ddim yn effeithiol iawn ar gyfer exit(2), ond a bod yn deg, mae ein rhaglen yn rhy syml, ac ar gyfer rhaglenni nad ydynt yn ddibwys mae angen y prolog a’r epilog a ychwanegwyd gan y casglwr JIT, wrth gwrs.

Mapiau

Gall rhaglenni BPF ddefnyddio ardaloedd cof strwythuredig sy'n hygyrch i raglenni BPF eraill ac i raglenni yn y gofod defnyddwyr. Gelwir y gwrthrychau hyn yn fapiau ac yn yr adran hon byddwn yn dangos sut i'w trin gan ddefnyddio galwad system bpf.

Gadewch i ni ddweud ar unwaith nad yw galluoedd mapiau yn gyfyngedig i fynediad at gof a rennir yn unig. Mae yna fapiau pwrpas arbennig sy'n cynnwys, er enghraifft, awgrymiadau at raglenni BPF neu awgrymiadau i ryngwynebau rhwydwaith, mapiau ar gyfer gweithio gyda digwyddiadau perf, ac ati. Ni fyddwn yn siarad amdanynt yma, rhag drysu'r darllenydd. Ar wahân i hyn, rydym yn anwybyddu materion cydamseru, gan nad yw hyn yn bwysig ar gyfer ein henghreifftiau. Mae rhestr gyflawn o'r mathau o fapiau sydd ar gael i'w gweld yn <linux/bpf.h>, ac yn yr adran hon byddwn yn cymryd fel enghraifft y math hanesyddol cyntaf, y tabl hash BPF_MAP_TYPE_HASH.

Os ydych chi'n creu tabl hash yn, dywedwch, C++, byddech chi'n dweud unordered_map<int,long> woo, sydd yn Rwsieg yn golygu “Mae angen bwrdd arnaf woo maint diderfyn, y mae ei allweddi o fath int, a'r gwerthoedd yw'r math long" Er mwyn creu tabl stwnsh BPF, mae angen i ni wneud llawer yr un peth, ac eithrio bod yn rhaid i ni nodi maint mwyaf y tabl, ac yn lle nodi'r mathau o allweddi a gwerthoedd, mae angen i ni nodi eu meintiau mewn beitiau. . I greu mapiau defnyddiwch y gorchymyn BPF_MAP_CREATE galwad system bpf. Gadewch i ni edrych ar raglen fach iawn fwy neu lai sy'n creu map. Ar ôl y rhaglen flaenorol sy'n llwytho rhaglenni BPF, dylai'r un hon ymddangos yn syml i chi:

$ cat simple-map.c
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

int main(void)
{
    union bpf_attr attr = {
        .map_type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(int),
        .value_size = sizeof(int),
        .max_entries = 4,
    };
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

Yma rydym yn diffinio set o baramedrau attr, lle rydyn ni'n dweud “Mae angen tabl hash arnaf gydag allweddi a gwerthoedd maint sizeof(int), lle gallaf roi uchafswm o bedair elfen." Wrth greu mapiau BPF, gallwch nodi paramedrau eraill, er enghraifft, yn yr un modd ag yn yr enghraifft gyda'r rhaglen, fe wnaethom nodi enw'r gwrthrych fel "woo".

Gadewch i ni lunio a rhedeg y rhaglen:

$ clang -g -O2 simple-map.c -o simple-map
$ sudo strace ./simple-map
execve("./simple-map", ["./simple-map"], 0x7ffd40a27070 /* 14 vars */) = 0
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_HASH, key_size=4, value_size=4, max_entries=4, map_name="woo", ...}, 72) = 3
pause(

Dyma'r alwad system bpf(2) dychwelyd rhif y map disgrifydd atom 3 ac yna mae'r rhaglen, yn ôl y disgwyl, yn aros am gyfarwyddiadau pellach yn yr alwad system pause(2).

Nawr, gadewch i ni anfon ein rhaglen i'r cefndir neu agor terfynell arall ac edrych ar ein gwrthrych gan ddefnyddio'r cyfleustodau bpftool (gallwn wahaniaethu ein map oddi wrth eraill wrth ei enw):

$ sudo bpftool map
...
114: hash  name woo  flags 0x0
        key 4B  value 4B  max_entries 4  memlock 4096B
...

Y rhif 114 yw ID byd-eang ein gwrthrych. Gall unrhyw raglen ar y system ddefnyddio'r ID hwn i agor map sy'n bodoli eisoes gan ddefnyddio'r gorchymyn BPF_MAP_GET_FD_BY_ID galwad system bpf.

Nawr gallwn chwarae gyda'n bwrdd hash. Edrychwn ar ei gynnwys:

$ sudo bpftool map dump id 114
Found 0 elements

Gwag. Gadewch i ni roi gwerth ynddo hash[1] = 1:

$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0

Edrychwn ar y bwrdd eto:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
Found 1 element

Hwre! Llwyddom i ychwanegu un elfen. Sylwch fod yn rhaid i ni weithio ar y lefel beit i wneud hyn, ers hynny bptftool ddim yn gwybod pa fath yw'r gwerthoedd yn y tabl hash. (Gellir trosglwyddo'r wybodaeth hon iddi gan ddefnyddio BTF, ond mwy am hynny nawr.)

Sut yn union mae bpftool yn darllen ac yn ychwanegu elfennau? Gadewch i ni edrych o dan y cwfl:

$ sudo strace -e bpf bpftool map dump id 114
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=NULL, next_key=0x55856ab65280}, 120) = 0
bpf(BPF_MAP_LOOKUP_ELEM, {map_fd=3, key=0x55856ab65280, value=0x55856ab652a0}, 120) = 0
key: 01 00 00 00  value: 01 00 00 00
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=0x55856ab65280, next_key=0x55856ab65280}, 120) = -1 ENOENT

Yn gyntaf fe wnaethom agor y map yn ôl ei ID byd-eang gan ddefnyddio'r gorchymyn BPF_MAP_GET_FD_BY_ID и bpf(2) dychwelyd disgrifydd 3 i ni. Ymhellach gan ddefnyddio'r gorchymyn BPF_MAP_GET_NEXT_KEY daethom o hyd i'r allwedd gyntaf yn y tabl wrth fynd heibio NULL fel pwyntydd i'r allwedd "blaenorol". Os oes gennym yr allwedd gallwn ei wneud BPF_MAP_LOOKUP_ELEMsy'n dychwelyd gwerth i bwyntydd value. Y cam nesaf yw ceisio dod o hyd i'r elfen nesaf trwy basio pwyntydd i'r allwedd gyfredol, ond dim ond un elfen a'r gorchymyn sydd yn ein tabl BPF_MAP_GET_NEXT_KEY yn dychwelyd ENOENT.

Iawn, gadewch i ni newid y gwerth yn ôl allwedd 1, gadewch i ni ddweud bod ein rhesymeg busnes yn gofyn am gofrestru hash[1] = 2:

$ sudo strace -e bpf bpftool map update id 114 key 1 0 0 0 value 2 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x55dcd72be260, value=0x55dcd72be280, flags=BPF_ANY}, 120) = 0

Yn ôl y disgwyl, mae'n syml iawn: y gorchymyn BPF_MAP_GET_FD_BY_ID yn agor ein map trwy ID, a'r gorchymyn BPF_MAP_UPDATE_ELEM yn trosysgrifo'r elfen.

Felly, ar ôl creu tabl hash o un rhaglen, gallwn ddarllen ac ysgrifennu ei gynnwys o raglen arall. Sylwch, pe baem yn gallu gwneud hyn o'r llinell orchymyn, yna gall unrhyw raglen arall ar y system ei wneud. Yn ogystal â'r gorchmynion a ddisgrifir uchod, ar gyfer gweithio gyda mapiau o ofod defnyddwyr, y canlynol:

  • BPF_MAP_LOOKUP_ELEM: canfod gwerth trwy allwedd
  • BPF_MAP_UPDATE_ELEM: diweddaru/creu gwerth
  • BPF_MAP_DELETE_ELEM: tynnu allwedd
  • BPF_MAP_GET_NEXT_KEY: dod o hyd i'r allwedd nesaf (neu gyntaf).
  • BPF_MAP_GET_NEXT_ID: yn caniatáu ichi fynd trwy'r holl fapiau sy'n bodoli eisoes, dyna sut mae'n gweithio bpftool map
  • BPF_MAP_GET_FD_BY_ID: agor map sy'n bodoli eisoes gan ei ID byd-eang
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: diweddaru gwerth gwrthrych yn atomig a dychwelyd yr hen un
  • BPF_MAP_FREEZE: gwneud y map yn ddigyfnewid o ofod defnyddwyr (ni ellir dadwneud y gweithrediad hwn)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: gweithrediadau torfol. Er enghraifft, BPF_MAP_LOOKUP_AND_DELETE_BATCH - dyma'r unig ffordd ddibynadwy i ddarllen ac ailosod yr holl werthoedd o'r map

Nid yw pob un o'r gorchmynion hyn yn gweithio ar gyfer pob math o fap, ond yn gyffredinol mae gweithio gyda mathau eraill o fapiau o ofod defnyddwyr yn edrych yn union yr un fath â gweithio gyda thablau stwnsh.

Er mwyn trefn, gadewch i ni orffen ein harbrofion bwrdd hash. Cofiwch ein bod wedi creu tabl sy'n gallu cynnwys hyd at bedair allwedd? Gadewch i ni ychwanegu ychydig mwy o elfennau:

$ sudo bpftool map update id 114 key 2 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 3 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 4 0 0 0 value 1 0 0 0

Hyd yn hyn mor dda:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
key: 02 00 00 00  value: 01 00 00 00
key: 04 00 00 00  value: 01 00 00 00
key: 03 00 00 00  value: 01 00 00 00
Found 4 elements

Gadewch i ni geisio ychwanegu un arall:

$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long

Yn ôl y disgwyl, ni wnaethom lwyddo. Edrychwn ar y gwall yn fwy manwl:

$ sudo strace -e bpf bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=80, info=0x7ffe6c626da0}}, 120) = 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x56049ded5260, value=0x56049ded5280, flags=BPF_ANY}, 120) = -1 E2BIG (Argument list too long)
Error: update failed: Argument list too long
+++ exited with 255 +++

Mae popeth yn iawn: yn ôl y disgwyl, y tîm BPF_MAP_UPDATE_ELEM yn ceisio creu newydd, pumed, allweddol, ond damweiniau E2BIG.

Felly, gallwn greu a llwytho rhaglenni BPF, yn ogystal â chreu a rheoli mapiau o ofod defnyddwyr. Nawr mae'n rhesymegol edrych ar sut y gallwn ddefnyddio mapiau o'r rhaglenni BPF eu hunain. Gallem siarad am hyn yn iaith rhaglenni anodd eu darllen mewn codau macro peiriannau, ond mewn gwirionedd mae'r amser wedi dod i ddangos sut mae rhaglenni BPF yn cael eu hysgrifennu a'u cynnal mewn gwirionedd - gan ddefnyddio libbpf.

(Ar gyfer darllenwyr sy'n anfodlon â diffyg enghraifft lefel isel: byddwn yn dadansoddi'n fanwl raglenni sy'n defnyddio mapiau a swyddogaethau cymorth a grëwyd gan ddefnyddio libbpf a dweud wrthych beth sy'n digwydd ar y lefel cyfarwyddyd. Ar gyfer darllenwyr sy'n anfodlon yn fawr iawn, ychwanegasom enghraifft yn y man priodol yn yr erthygl.)

Ysgrifennu rhaglenni BPF gan ddefnyddio libbpf

Gall ysgrifennu rhaglenni BPF gan ddefnyddio codau peiriant fod yn ddiddorol y tro cyntaf yn unig, ac yna mae syrffed bwyd yn dod i mewn. Ar hyn o bryd mae angen i chi droi eich sylw at llvm, sydd â chefndir ar gyfer cynhyrchu cod ar gyfer pensaernïaeth BPF, yn ogystal â llyfrgell libbpf, sy'n eich galluogi i ysgrifennu ochr defnyddiwr ceisiadau BPF a llwytho'r cod o raglenni BPF a gynhyrchir gan ddefnyddio llvm/clang.

Yn wir, fel y gwelwn yn yr erthyglau hyn ac erthyglau dilynol, libbpf yn gwneud cryn dipyn o waith hebddo (neu offer tebyg - iproute2, libbcc, libbpf-go, etc.) mae'n amhosibl byw. Un o nodweddion llofruddiol y prosiect libbpf yw BPF CO-RE (Compile Once, Run Everywhere) - prosiect sy'n eich galluogi i ysgrifennu rhaglenni BPF sy'n gludadwy o un cnewyllyn i'r llall, gyda'r gallu i redeg ar wahanol APIs (er enghraifft, pan fydd strwythur y cnewyllyn yn newid o'r fersiwn i fersiwn). Er mwyn gallu gweithio gyda CO-RE, rhaid i'ch cnewyllyn gael ei lunio gyda chymorth BTF (rydym yn disgrifio sut i wneud hyn yn yr adran Offer datblygu. Gallwch wirio a yw'ch cnewyllyn wedi'i adeiladu gyda BTF ai peidio yn syml iawn - trwy bresenoldeb y ffeil ganlynol:

$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux

Mae'r ffeil hon yn storio gwybodaeth am yr holl fathau o ddata a ddefnyddir yn y cnewyllyn ac fe'i defnyddir yn ein holl enghreifftiau gan ddefnyddio libbpf. Byddwn yn siarad yn fanwl am CO-RE yn yr erthygl nesaf, ond yn yr un hon - dim ond adeiladu cnewyllyn i chi'ch hun CONFIG_DEBUG_INFO_BTF.

Llyfrgell libbpf yn byw reit yn y cyfeiriadur tools/lib/bpf cnewyllyn a'i ddatblygiad yn cael ei wneud trwy'r rhestr bostio [email protected]. Fodd bynnag, cedwir ystorfa ar wahân ar gyfer anghenion ceisiadau sy'n byw y tu allan i'r cnewyllyn https://github.com/libbpf/libbpf lle mae'r llyfrgell cnewyllyn yn cael ei adlewyrchu ar gyfer mynediad darllen mwy neu lai fel y mae.

Yn yr adran hon byddwn yn edrych ar sut y gallwch greu prosiect sy'n defnyddio libbpf, gadewch i ni ysgrifennu nifer o raglenni prawf (mwy neu lai diystyr) a dadansoddi'n fanwl sut mae'r cyfan yn gweithio. Bydd hyn yn ein galluogi i egluro'n haws yn yr adrannau canlynol yn union sut mae rhaglenni BPF yn rhyngweithio â mapiau, cynorthwywyr cnewyllyn, BTF, ac ati.

Yn nodweddiadol prosiectau gan ddefnyddio libbpf ychwanegu ystorfa GitHub fel is-fodiwl git, byddwn yn gwneud yr un peth:

$ mkdir /tmp/libbpf-example
$ cd /tmp/libbpf-example/
$ git init-db
Initialized empty Git repository in /tmp/libbpf-example/.git/
$ git submodule add https://github.com/libbpf/libbpf.git
Cloning into '/tmp/libbpf-example/libbpf'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 3354 (delta 101), reused 118 (delta 79), pack-reused 3154
Receiving objects: 100% (3354/3354), 2.05 MiB | 10.22 MiB/s, done.
Resolving deltas: 100% (2176/2176), done.

Mynd i libbpf syml iawn:

$ cd libbpf/src
$ mkdir build
$ OBJDIR=build DESTDIR=root make -s install
$ find root
root
root/usr
root/usr/include
root/usr/include/bpf
root/usr/include/bpf/bpf_tracing.h
root/usr/include/bpf/xsk.h
root/usr/include/bpf/libbpf_common.h
root/usr/include/bpf/bpf_endian.h
root/usr/include/bpf/bpf_helpers.h
root/usr/include/bpf/btf.h
root/usr/include/bpf/bpf_helper_defs.h
root/usr/include/bpf/bpf.h
root/usr/include/bpf/libbpf_util.h
root/usr/include/bpf/libbpf.h
root/usr/include/bpf/bpf_core_read.h
root/usr/lib64
root/usr/lib64/libbpf.so.0.1.0
root/usr/lib64/libbpf.so.0
root/usr/lib64/libbpf.a
root/usr/lib64/libbpf.so
root/usr/lib64/pkgconfig
root/usr/lib64/pkgconfig/libbpf.pc

Mae ein cynllun nesaf yn yr adran hon fel a ganlyn: byddwn yn ysgrifennu rhaglen BPF fel BPF_PROG_TYPE_XDP, yr un peth ag yn yr enghraifft flaenorol, ond yn C, rydym yn ei lunio gan ddefnyddio clang, ac ysgrifennu rhaglen helpwr a fydd yn ei lwytho i mewn i'r cnewyllyn. Yn yr adrannau canlynol byddwn yn ehangu galluoedd y rhaglen BPF a'r rhaglen gynorthwywyr.

Enghraifft: creu cymhwysiad cyflawn gan ddefnyddio libbpf

I ddechrau, rydym yn defnyddio'r ffeil /sys/kernel/btf/vmlinux, a grybwyllwyd uchod, a chreu'r hyn sy'n cyfateb iddo ar ffurf ffeil pennawd:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

Bydd y ffeil hon yn storio'r holl strwythurau data sydd ar gael yn ein cnewyllyn, er enghraifft, dyma sut mae'r pennawd IPv4 yn cael ei ddiffinio yn y cnewyllyn:

$ grep -A 12 'struct iphdr {' vmlinux.h
struct iphdr {
    __u8 ihl: 4;
    __u8 version: 4;
    __u8 tos;
    __be16 tot_len;
    __be16 id;
    __be16 frag_off;
    __u8 ttl;
    __u8 protocol;
    __sum16 check;
    __be32 saddr;
    __be32 daddr;
};

Nawr byddwn yn ysgrifennu ein rhaglen BPF yn C:

$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
        return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Er bod ein rhaglen wedi troi allan i fod yn syml iawn, mae angen i ni dalu sylw i lawer o fanylion o hyd. Yn gyntaf, y ffeil pennawd cyntaf rydyn ni'n ei chynnwys yw vmlinux.h, yr ydym newydd ei gynhyrchu gan ddefnyddio bpftool btf dump - nawr nid oes angen i ni osod y pecyn penawdau cnewyllyn i ddarganfod sut olwg sydd ar y strwythurau cnewyllyn. Daw'r ffeil pennawd canlynol atom o'r llyfrgell libbpf. Nawr dim ond i ddiffinio'r macro y mae ei angen arnom SEC, sy'n anfon y nod i'r adran briodol o'r ffeil gwrthrych ELF. Mae ein rhaglen wedi'i chynnwys yn yr adran xdp/simple, lle cyn y slaes rydym yn diffinio'r math o raglen BPF - dyma'r confensiwn a ddefnyddir yn libbpf, yn seiliedig ar enw'r adran bydd yn disodli'r math cywir wrth gychwyn bpf(2). Mae'r rhaglen BPF ei hun yn C - syml iawn ac yn cynnwys un llinell return XDP_PASS. Yn olaf, adran ar wahân "license" yn cynnwys enw'r drwydded.

Gallwn lunio ein rhaglen gan ddefnyddio llvm/clang, fersiwn >= 10.0.0, neu well eto, mwy (gweler yr adran Offer datblygu):

$ clang --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git afc287e0abec710398465ee1f86237513f2b5091)
...

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o

Ymhlith y nodweddion diddorol: rydym yn nodi'r bensaernïaeth darged -target bpf a'r llwybr i'r penau libbpf, a osodwyd gennym yn ddiweddar. Hefyd, peidiwch ag anghofio am -O2, heb yr opsiwn hwn efallai y byddwch chi mewn ar gyfer syrpreis yn y dyfodol. Gadewch i ni edrych ar ein cod, a wnaethom lwyddo i ysgrifennu'r rhaglen yr oeddem ei heisiau?

$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       r0 = 2
       1:       exit

Oedd, fe weithiodd! Nawr, mae gennym ffeil ddeuaidd gyda'r rhaglen, ac rydym am greu cymhwysiad a fydd yn ei lwytho i mewn i'r cnewyllyn. At y diben hwn y llyfrgell libbpf yn cynnig dau opsiwn i ni - defnyddiwch API lefel is neu API lefel uwch. Awn yr ail ffordd, gan ein bod am ddysgu sut i ysgrifennu, llwytho a chysylltu rhaglenni BPF heb fawr o ymdrech ar gyfer eu hastudiaeth ddilynol.

Yn gyntaf, mae angen i ni gynhyrchu “sgerbwd” ein rhaglen o'i deuaidd gan ddefnyddio'r un cyfleustodau bpftool - cyllell y Swistir o fyd BPF (y gellir ei chymryd yn llythrennol, gan fod Daniel Borkman, un o grewyr a chynhalwyr BPF, yn Swistir):

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h

Mewn ffeil xdp-simple.skel.h yn cynnwys cod deuaidd ein rhaglen a swyddogaethau ar gyfer rheoli - llwytho, atodi, dileu ein gwrthrych. Yn ein hachos syml ni, mae hyn yn edrych fel gorladdiad, ond mae hefyd yn gweithio yn yr achos lle mae'r ffeil gwrthrych yn cynnwys llawer o raglenni a mapiau BPF ac i lwytho'r ELF enfawr hwn does ond angen i ni gynhyrchu'r sgerbwd a galw un neu ddwy swyddogaeth o'r cymhwysiad arferol rydym ni yn ysgrifennu Gadewch i ni symud ymlaen nawr.

A siarad yn fanwl gywir, mae ein rhaglen llwythwr yn ddibwys:

#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    pause();

    xdp_simple_bpf__destroy(obj);
}

Yma struct xdp_simple_bpf wedi'i ddiffinio yn y ffeil xdp-simple.skel.h ac yn disgrifio ein ffeil gwrthrych:

struct xdp_simple_bpf {
    struct bpf_object_skeleton *skeleton;
    struct bpf_object *obj;
    struct {
        struct bpf_program *simple;
    } progs;
    struct {
        struct bpf_link *simple;
    } links;
};

Gallwn weld olion API lefel isel yma: y strwythur struct bpf_program *simple и struct bpf_link *simple. Mae'r strwythur cyntaf yn disgrifio ein rhaglen yn benodol, a ysgrifennwyd yn yr adran xdp/simple, ac mae'r ail yn disgrifio sut mae'r rhaglen yn cysylltu â ffynhonnell y digwyddiad.

Swyddogaeth xdp_simple_bpf__open_and_load, yn agor gwrthrych ELF, yn ei ddosrannu, yn creu'r holl strwythurau ac is-strwythurau (ar wahân i'r rhaglen, mae ELF hefyd yn cynnwys adrannau eraill - data, data darllen yn unig, gwybodaeth dadfygio, trwydded, ac ati), ac yna'n ei lwytho i mewn i'r cnewyllyn gan ddefnyddio system galw bpf, y gallwn ei wirio trwy lunio a rhedeg y rhaglen:

$ clang -O2 -I ./libbpf/src/root/usr/include/ xdp-simple.c -o xdp-simple ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_BTF_LOAD, 0x7ffdb8fd9670, 120)  = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0xdfd580, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 8, 0), prog_flags=0, prog_name="simple", prog_ifindex=0, expected_attach_type=0x25 /* BPF_??? */, ...}, 120) = 4

Gadewch i ni nawr edrych ar ein rhaglen yn defnyddio bpftool. Dewch i ni ddod o hyd i'w ID:

# bpftool p | grep -A4 simple
463: xdp  name simple  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-01T01:59:49+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        btf_id 185
        pids xdp-simple(16498)

a dymp (defnyddiwn ffurf fyrrach o'r gorchymyn bpftool prog dump xlated):

# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
   0: (b7) r0 = 2
   1: (95) exit

Rhywbeth newydd! Argraffodd y rhaglen ddarnau o'n ffeil ffynhonnell C. Gwnaed hyn gan y llyfrgell libbpf, a ddaeth o hyd i'r adran dadfygio yn y deuaidd, ei lunio i mewn i wrthrych BTF, ei lwytho i mewn i'r cnewyllyn gan ddefnyddio BPF_BTF_LOAD, ac yna pennodd y disgrifydd ffeil canlyniadol wrth lwytho'r rhaglen gyda'r gorchymyn BPG_PROG_LOAD.

Cynorthwywyr Cnewyllyn

Gall rhaglenni BPF redeg swyddogaethau “allanol” - cynorthwywyr cnewyllyn. Mae'r swyddogaethau cynorthwywyr hyn yn caniatáu i raglenni BPF gael mynediad i strwythurau cnewyllyn, rheoli mapiau, a hefyd cyfathrebu â'r “byd go iawn” - creu digwyddiadau perf, rheoli caledwedd (er enghraifft, ailgyfeirio pecynnau), ac ati.

Enghraifft: bpf_get_smp_processor_id

O fewn fframwaith y patrwm “dysgu trwy esiampl”, gadewch i ni ystyried un o'r swyddogaethau cynorthwywyr, bpf_get_smp_processor_id(), sicr mewn ffeil kernel/bpf/helpers.c. Mae'n dychwelyd rhif y prosesydd y mae'r rhaglen BPF a'i galwodd yn rhedeg arno. Ond nid oes gennym gymaint o ddiddordeb yn ei semanteg ag yn y ffaith bod ei weithrediad yn cymryd un llinell:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

Mae diffiniadau swyddogaeth cynorthwyydd BPF yn debyg i ddiffiniadau galwadau system Linux. Yma, er enghraifft, diffinnir swyddogaeth nad oes ganddi unrhyw ddadleuon. (Diffinnir swyddogaeth sy'n cymryd, dyweder, tair dadl gan ddefnyddio'r macro BPF_CALL_3. Uchafswm nifer y dadleuon yw pump.) Fodd bynnag, dim ond rhan gyntaf y diffiniad yw hon. Yr ail ran yw diffinio'r strwythur math struct bpf_func_proto, sy'n cynnwys disgrifiad o'r swyddogaeth cynorthwyydd y mae'r dilysydd yn ei ddeall:

const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
    .func     = bpf_get_smp_processor_id,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

Cofrestru Swyddogaethau Cynorthwyydd

Er mwyn i raglenni BPF o fath arbennig ddefnyddio'r swyddogaeth hon, rhaid iddynt ei chofrestru, er enghraifft ar gyfer y math BPF_PROG_TYPE_XDP diffinnir ffwythiant yn y cnewyllyn xdp_func_proto, sy'n penderfynu o'r ID swyddogaeth cynorthwyydd a yw XDP yn cefnogi'r swyddogaeth hon ai peidio. Ein swyddogaeth yw yn cefnogi:

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    ...
    case BPF_FUNC_get_smp_processor_id:
        return &bpf_get_smp_processor_id_proto;
    ...
    }
}

Mae mathau newydd o raglenni BPF wedi'u "diffinio" yn y ffeil include/linux/bpf_types.h defnyddio macro BPF_PROG_TYPE. Wedi'i ddiffinio mewn dyfyniadau oherwydd ei fod yn ddiffiniad rhesymegol, ac yn nhermau iaith C mae'r diffiniad o set gyfan o strwythurau concrit yn digwydd mewn mannau eraill. Yn benodol, yn y ffeil kernel/bpf/verifier.c pob diffiniad o ffeil bpf_types.h yn cael eu defnyddio i greu amrywiaeth o strwythurau bpf_verifier_ops[]:

static const struct bpf_verifier_ops *const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) 
    [_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
};

Hynny yw, ar gyfer pob math o raglen BPF, diffinnir pwyntydd i strwythur data o'r math struct bpf_verifier_ops, sy'n cael ei gychwyn gyda'r gwerth _name ## _verifier_ops, h.y., xdp_verifier_ops gyfer xdp. Strwythur xdp_verifier_ops yn benderfynol mewn ffeil net/core/filter.c fel a ganlyn:

const struct bpf_verifier_ops xdp_verifier_ops = {
    .get_func_proto     = xdp_func_proto,
    .is_valid_access    = xdp_is_valid_access,
    .convert_ctx_access = xdp_convert_ctx_access,
    .gen_prologue       = bpf_noop_prologue,
};

Yma gwelwn ein swyddogaeth gyfarwydd xdp_func_proto, a fydd yn rhedeg y dilysydd bob tro y daw ar draws her rhyw fath swyddogaethau y tu mewn i raglen BPF, gw verifier.c.

Gadewch i ni edrych ar sut mae rhaglen BPF ddamcaniaethol yn defnyddio'r swyddogaeth bpf_get_smp_processor_id. I wneud hyn, rydym yn ailysgrifennu'r rhaglen o'n hadran flaenorol fel a ganlyn:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
    if (bpf_get_smp_processor_id() != 0)
        return XDP_DROP;
    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Symbol bpf_get_smp_processor_id yn benderfynol в <bpf/bpf_helper_defs.h> llyfrgelloedd libbpf как

static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;

hynny yw, bpf_get_smp_processor_id yn bwyntydd ffwythiant y mae ei werth yn 8, ac 8 yw'r gwerth BPF_FUNC_get_smp_processor_id типа enum bpf_fun_id, a ddiffinnir i ni yn y ffeil vmlinux.h (ffeil bpf_helper_defs.h yn y cnewyllyn yn cael ei gynhyrchu gan sgript, felly mae'r rhifau “hud” yn iawn). Nid yw'r swyddogaeth hon yn cymryd unrhyw ddadleuon ac mae'n dychwelyd gwerth math __u32. Pan fyddwn yn ei redeg yn ein rhaglen, clang yn cynhyrchu cyfarwyddyd BPF_CALL "y math iawn" Gadewch i ni lunio'r rhaglen ac edrych ar yr adran xdp/simple:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ llvm-objdump -D --section=xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       bf 01 00 00 00 00 00 00 r1 = r0
       2:       67 01 00 00 20 00 00 00 r1 <<= 32
       3:       77 01 00 00 20 00 00 00 r1 >>= 32
       4:       b7 00 00 00 02 00 00 00 r0 = 2
       5:       15 01 01 00 00 00 00 00 if r1 == 0 goto +1 <LBB0_2>
       6:       b7 00 00 00 01 00 00 00 r0 = 1

0000000000000038 <LBB0_2>:
       7:       95 00 00 00 00 00 00 00 exit

Yn y llinell gyntaf gwelwn gyfarwyddiadau call, paramedr IMM sy gyfartal i 8, a SRC_REG - sero. Yn ôl y cytundeb ABI a ddefnyddir gan y dilysydd, mae hwn yn swyddogaeth galw i gynorthwyydd rhif wyth. Unwaith y caiff ei lansio, mae'r rhesymeg yn syml. Gwerth dychwelyd o'r gofrestr r0 copi i r1 ac ar linellau 2,3 caiff ei drawsnewid i fath u32 — mae'r 32 did uchaf yn cael eu clirio. Ar linellau 4,5,6,7 dychwelwn 2 (XDP_PASS) neu 1 (XDP_DROP) yn dibynnu a ddychwelodd y swyddogaeth cynorthwyydd o linell 0 werth sero neu ddi-sero.

Gadewch i ni brofi ein hunain: llwythwch y rhaglen ac edrych ar yr allbwn bpftool prog dump xlated:

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple &
[2] 10914

$ sudo bpftool p | grep simple
523: xdp  name simple  tag 44c38a10c657e1b0  gpl
        pids xdp-simple(10915)

$ sudo bpftool p d x id 523
int simple(void *ctx):
; if (bpf_get_smp_processor_id() != 0)
   0: (85) call bpf_get_smp_processor_id#114128
   1: (bf) r1 = r0
   2: (67) r1 <<= 32
   3: (77) r1 >>= 32
   4: (b7) r0 = 2
; }
   5: (15) if r1 == 0x0 goto pc+1
   6: (b7) r0 = 1
   7: (95) exit

Iawn, daeth y dilysydd o hyd i'r cynorthwyydd cnewyllyn cywir.

Enghraifft: pasio dadleuon ac yn olaf rhedeg y rhaglen!

Mae gan bob swyddogaeth cynorthwyydd lefel rhediad brototeip

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

Mae paramedrau i swyddogaethau cynorthwywyr yn cael eu pasio mewn cofrestri r1-r5, a dychwelir y gwerth yn y gofrestr r0. Nid oes unrhyw swyddogaethau sy'n cymryd mwy na phum dadl, ac ni ddisgwylir i gefnogaeth iddynt gael eu hychwanegu yn y dyfodol.

Gadewch i ni edrych ar y cynorthwyydd cnewyllyn newydd a sut mae BPF yn pasio paramedrau. Gadewch i ni ailysgrifennu xdp-simple.bpf.c fel a ganlyn (nid yw gweddill y llinellau wedi newid):

SEC("xdp/simple")
int simple(void *ctx)
{
    bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
    return XDP_PASS;
}

Mae ein rhaglen yn argraffu nifer y CPU y mae'n rhedeg arno. Gadewch i ni ei lunio ac edrych ar y cod:

$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       r1 = 10
       1:       *(u16 *)(r10 - 8) = r1
       2:       r1 = 8441246879787806319 ll
       4:       *(u64 *)(r10 - 16) = r1
       5:       r1 = 2334956330918245746 ll
       7:       *(u64 *)(r10 - 24) = r1
       8:       call 8
       9:       r1 = r10
      10:       r1 += -24
      11:       r2 = 18
      12:       r3 = r0
      13:       call 6
      14:       r0 = 2
      15:       exit

Yn llinellau 0-7 rydyn ni'n ysgrifennu'r llinyn running on CPU%un, ac yna ar linell 8 rydym yn rhedeg yr un cyfarwydd bpf_get_smp_processor_id. Ar linellau 9-12 rydym yn paratoi dadleuon y cynorthwywyr bpf_printk - cofrestri r1, r2, r3. Pam fod yna dri ohonyn nhw ac nid dau? Achos bpf_printkmacro-lapiwr yw hwn o gwmpas y cynorthwy-ydd go iawn bpf_trace_printk, sydd angen pasio maint y llinyn fformat.

Gadewch i ni nawr ychwanegu cwpl o linellau at xdp-simple.cfel bod ein rhaglen yn cysylltu â'r rhyngwyneb lo a dechrau go iawn!

$ cat xdp-simple.c
#include <linux/if_link.h>
#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    __u32 flags = XDP_FLAGS_SKB_MODE;
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    bpf_set_link_xdp_fd(1, -1, flags);
    bpf_set_link_xdp_fd(1, bpf_program__fd(obj->progs.simple), flags);

cleanup:
    xdp_simple_bpf__destroy(obj);
}

Yma rydym yn defnyddio'r swyddogaeth bpf_set_link_xdp_fd, sy'n cysylltu rhaglenni BPF math XDP â rhyngwynebau rhwydwaith. Fe wnaethom ni roi cod caled i rif y rhyngwyneb lo, sydd bob amser yn 1. Rydym yn rhedeg y swyddogaeth ddwywaith i ddatgysylltu'r hen raglen yn gyntaf os oedd ynghlwm. Sylwch nad oes angen her arnom nawr pause neu ddolen ddiddiwedd: bydd ein rhaglen llwythwr yn gadael, ond ni fydd y rhaglen BPF yn cael ei lladd gan ei bod wedi'i chysylltu â ffynhonnell y digwyddiad. Ar ôl lawrlwytho a chysylltu'n llwyddiannus, bydd y rhaglen yn cael ei lansio ar gyfer pob pecyn rhwydwaith sy'n cyrraedd lo.

Gadewch i ni lawrlwytho'r rhaglen ac edrych ar y rhyngwyneb lo:

$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp  name simple  tag 4fca62e77ccb43d6  gpl
$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 669

Mae gan y rhaglen a lawrlwythwyd gennym ID 669 a gwelwn yr un ID ar y rhyngwyneb lo. Byddwn yn anfon cwpl o becynnau i 127.0.0.1 (cais + ateb):

$ ping -c1 localhost

a nawr gadewch i ni edrych ar gynnwys y ffeil rhithwir dadfygio /sys/kernel/debug/tracing/trace_pipe, yn yr hwn bpf_printk yn ysgrifennu ei negeseuon:

# cat /sys/kernel/debug/tracing/trace_pipe
ping-13937 [000] d.s1 442015.377014: bpf_trace_printk: running on CPU0
ping-13937 [000] d.s1 442015.377027: bpf_trace_printk: running on CPU0

Gwelwyd dau becyn ymlaen lo a'i brosesu ar CPU0 - gweithiodd ein rhaglen BPF ddiystyr lawn gyntaf!

Mae'n werth nodi hynny bpf_printk Nid am ddim y mae'n ysgrifennu at y ffeil dadfygio: nid dyma'r cynorthwyydd mwyaf llwyddiannus i'w ddefnyddio wrth gynhyrchu, ond ein nod oedd dangos rhywbeth syml.

Cyrchu mapiau o raglenni BPF

Enghraifft: defnyddio map o'r rhaglen BPF

Yn yr adrannau blaenorol fe wnaethom ddysgu sut i greu a defnyddio mapiau o ofod defnyddwyr, a nawr gadewch i ni edrych ar y rhan cnewyllyn. Gadewch i ni ddechrau, fel arfer, gydag enghraifft. Gadewch i ni ailysgrifennu ein rhaglen xdp-simple.bpf.c fel a ganlyn:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 8);
    __type(key, u32);
    __type(value, u64);
} woo SEC(".maps");

SEC("xdp/simple")
int simple(void *ctx)
{
    u32 key = bpf_get_smp_processor_id();
    u32 *val;

    val = bpf_map_lookup_elem(&woo, &key);
    if (!val)
        return XDP_ABORTED;

    *val += 1;

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Ar ddechrau'r rhaglen ychwanegon ni ddiffiniad map woo: Mae hwn yn arae 8-elfen sy'n storio gwerthoedd fel u64 (yn C byddem yn diffinio arae o'r fath â u64 woo[8]). Mewn rhaglen "xdp/simple" rydym yn cael rhif y prosesydd cyfredol i newidyn key ac yna defnyddio'r swyddogaeth helpwr bpf_map_lookup_element rydym yn cael pwyntydd i'r cofnod cyfatebol yn yr arae, ac rydym yn cynyddu un. Wedi'i gyfieithu i Rwsieg: rydym yn cyfrifo ystadegau ar ba CPU oedd yn prosesu pecynnau sy'n dod i mewn. Gadewch i ni geisio rhedeg y rhaglen:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple

Gadewch i ni wirio ei bod hi wedi gwirioni i fyny i lo ac anfon rhai pecynnau:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 108

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done

Nawr, gadewch i ni edrych ar gynnwys yr arae:

$ sudo bpftool map dump name woo
[
    { "key": 0, "value": 0 },
    { "key": 1, "value": 400 },
    { "key": 2, "value": 0 },
    { "key": 3, "value": 0 },
    { "key": 4, "value": 0 },
    { "key": 5, "value": 0 },
    { "key": 6, "value": 0 },
    { "key": 7, "value": 46400 }
]

Proseswyd bron pob proses ar CPU7. Nid yw hyn yn bwysig i ni, y prif beth yw bod y rhaglen yn gweithio ac rydym yn deall sut i gael mynediad at fapiau o raglenni BPF - gan ddefnyddio хелперов bpf_mp_*.

Mynegai cyfriniol

Felly, gallwn gyrchu'r map o'r rhaglen BPF gan ddefnyddio galwadau fel

val = bpf_map_lookup_elem(&woo, &key);

lle mae'r swyddogaeth cynorthwyydd yn edrych

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

ond yr ydym yn pasio pwyntydd &woo i strwythur dienw struct { ... }...

Os edrychwn ar y cydosodwr rhaglen, gwelwn fod y gwerth &woo heb ei ddiffinio mewn gwirionedd (llinell 4):

llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
...

ac mae wedi'i gynnwys mewn adleoliadau:

$ llvm-readelf -r xdp-simple.bpf.o | head -4

Relocation section '.relxdp/simple' at offset 0xe18 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name
0000000000000020  0000002700000001 R_BPF_64_64            0000000000000000 woo

Ond os edrychwn ar y rhaglen sydd eisoes wedi'i llwytho, fe welwn bwyntydd i'r map cywir (llinell 4):

$ sudo bpftool prog dump x name simple
int simple(void *ctx):
   0: (85) call bpf_get_smp_processor_id#114128
   1: (63) *(u32 *)(r10 -4) = r0
   2: (bf) r2 = r10
   3: (07) r2 += -4
   4: (18) r1 = map[id:64]
...

Felly, gallwn ddod i'r casgliad, ar adeg lansio ein rhaglen llwythwr, y ddolen i &woo ei ddisodli gan rywbeth gyda llyfrgell libbpf. Yn gyntaf byddwn yn edrych ar yr allbwn strace:

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=8, max_entries=8, map_name="woo", ...}, 120) = 4
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="simple", ...}, 120) = 5

Gwelwn hynny libbpf creu map woo ac yna lawrlwytho ein rhaglen simple. Gadewch i ni edrych yn agosach ar sut rydym yn llwytho'r rhaglen:

  • galw xdp_simple_bpf__open_and_load o'r ffeil xdp-simple.skel.h
  • sy'n achosi xdp_simple_bpf__load o'r ffeil xdp-simple.skel.h
  • sy'n achosi bpf_object__load_skeleton o'r ffeil libbpf/src/libbpf.c
  • sy'n achosi bpf_object__load_xattr o libbpf/src/libbpf.c

Bydd y swyddogaeth olaf, ymhlith pethau eraill, yn galw bpf_object__create_maps, sy'n creu neu'n agor mapiau sy'n bodoli eisoes, gan eu troi'n ddisgrifyddion ffeil. (Dyma lle rydyn ni'n gweld BPF_MAP_CREATE yn yr allbwn strace.) Nesaf gelwir y swyddogaeth bpf_object__relocate a hi sydd o ddiddordeb i ni, gan ein bod yn cofio'r hyn a welsom woo yn y tabl adleoli. Wrth ei archwilio, rydym yn y pen draw yn cael ein hunain yn y swyddogaeth bpf_program__relocate, sydd yn delio ag adleoli mapiau:

case RELO_LD64:
    insn[0].src_reg = BPF_PSEUDO_MAP_FD;
    insn[0].imm = obj->maps[relo->map_idx].fd;
    break;

Felly rydym yn cymryd ein cyfarwyddiadau

18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll

a disodli'r gofrestr ffynhonnell sydd ynddo BPF_PSEUDO_MAP_FD, a'r IMM cyntaf i ddisgrifydd ffeil ein map ac, os yw'n hafal i, er enghraifft, 0xdeadbeef, yna o ganlyniad byddwn yn derbyn y cyfarwyddyd

18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll

Dyma sut mae gwybodaeth map yn cael ei throsglwyddo i raglen BPF benodol wedi'i llwytho. Yn yr achos hwn, gellir creu'r map gan ddefnyddio BPF_MAP_CREATE, a agorwyd gan ID gan ddefnyddio BPF_MAP_GET_FD_BY_ID.

Cyfanswm, wrth ddefnyddio libbpf mae'r algorithm fel a ganlyn:

  • yn ystod y cyfnod llunio, mae cofnodion yn cael eu creu yn y tabl adleoli ar gyfer dolenni i fapiau
  • libbpf yn agor llyfr gwrthrychau ELF, yn dod o hyd i'r holl fapiau sydd wedi'u defnyddio ac yn creu disgrifyddion ffeil ar eu cyfer
  • mae disgrifyddion ffeil yn cael eu llwytho i'r cnewyllyn fel rhan o'r cyfarwyddyd LD64

Fel y gallwch ddychmygu, mae mwy i ddod a bydd yn rhaid inni edrych i mewn i'r craidd. Yn ffodus, mae gennym ni gliw - rydyn ni wedi ysgrifennu'r ystyr BPF_PSEUDO_MAP_FD i mewn i'r gofrestr ffynhonnell a gallwn ei gladdu, a fydd yn ein harwain at y sanctaidd yr holl saint - kernel/bpf/verifier.c, lle mae swyddogaeth ag enw nodedig yn disodli disgrifydd ffeil â chyfeiriad strwythur o fath struct bpf_map:

static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env) {
    ...

    f = fdget(insn[0].imm);
    map = __bpf_map_get(f);
    if (insn->src_reg == BPF_PSEUDO_MAP_FD) {
        addr = (unsigned long)map;
    }
    insn[0].imm = (u32)addr;
    insn[1].imm = addr >> 32;

(gellir dod o hyd i'r cod llawn по ссылке). Felly gallwn ehangu ein algorithm:

  • wrth lwytho'r rhaglen, mae'r dilysydd yn gwirio'r defnydd cywir o'r map ac yn ysgrifennu cyfeiriad y strwythur cyfatebol struct bpf_map

Wrth lawrlwytho'r deuaidd ELF gan ddefnyddio libbpf Mae llawer mwy yn digwydd, ond byddwn yn trafod hynny mewn erthyglau eraill.

Llwytho rhaglenni a mapiau heb libbpf

Fel yr addawyd, dyma enghraifft i ddarllenwyr sydd eisiau gwybod sut i greu a llwytho rhaglen sy'n defnyddio mapiau, heb gymorth libbpf. Gall hyn fod yn ddefnyddiol pan fyddwch yn gweithio mewn amgylchedd na allwch adeiladu dibyniaeth ar ei gyfer, neu arbed bob tamaid, neu ysgrifennu rhaglen fel ply, sy'n cynhyrchu cod deuaidd BPF ar y hedfan.

Er mwyn ei gwneud hi'n haws dilyn y rhesymeg, byddwn yn ailysgrifennu ein hesiampl at y dibenion hyn xdp-simple. Gellir dod o hyd i god cyflawn ac ychydig wedi'i ehangu o'r rhaglen a drafodir yn yr enghraifft hon yn hwn byrdwn.

Mae rhesymeg ein cais fel a ganlyn:

  • creu map math BPF_MAP_TYPE_ARRAY gan ddefnyddio'r gorchymyn BPF_MAP_CREATE,
  • creu rhaglen sy'n defnyddio'r map hwn,
  • cysylltu'r rhaglen â'r rhyngwyneb lo,

sy'n cyfieithu i ddynol fel

int main(void)
{
    int map_fd, prog_fd;

    map_fd = map_create();
    if (map_fd < 0)
        err(1, "bpf: BPF_MAP_CREATE");

    prog_fd = prog_load(map_fd);
    if (prog_fd < 0)
        err(1, "bpf: BPF_PROG_LOAD");

    xdp_attach(1, prog_fd);
}

Yma map_create yn creu map yn yr un ffordd ag y gwnaethom yn yr enghraifft gyntaf am yr alwad system bpf - “cnewyllyn, gwnewch fap newydd i mi ar ffurf amrywiaeth o 8 elfen fel __u64 a rhowch y disgrifydd ffeil yn ôl i mi":

static int map_create()
{
    union bpf_attr attr;

    memset(&attr, 0, sizeof(attr));
    attr.map_type = BPF_MAP_TYPE_ARRAY,
    attr.key_size = sizeof(__u32),
    attr.value_size = sizeof(__u64),
    attr.max_entries = 8,
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

Mae'r rhaglen hefyd yn hawdd i'w llwytho:

static int prog_load(int map_fd)
{
    union bpf_attr attr;
    struct bpf_insn insns[] = {
        ...
    };

    memset(&attr, 0, sizeof(attr));
    attr.prog_type = BPF_PROG_TYPE_XDP;
    attr.insns     = ptr_to_u64(insns);
    attr.insn_cnt  = sizeof(insns)/sizeof(insns[0]);
    attr.license   = ptr_to_u64("GPL");
    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

Y rhan anodd prog_load yw diffiniad ein rhaglen BPF fel amrywiaeth o strwythurau struct bpf_insn insns[]. Ond gan ein bod yn defnyddio rhaglen sydd gennym yn C, gallwn dwyllo ychydig:

$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
       7:       b7 01 00 00 00 00 00 00 r1 = 0
       8:       15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2>
       9:       61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0)
      10:       07 01 00 00 01 00 00 00 r1 += 1
      11:       63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1
      12:       b7 01 00 00 02 00 00 00 r1 = 2

0000000000000068 <LBB0_2>:
      13:       bf 10 00 00 00 00 00 00 r0 = r1
      14:       95 00 00 00 00 00 00 00 exit

Yn gyfan gwbl, mae angen i ni ysgrifennu 14 cyfarwyddiadau ar ffurf strwythurau fel struct bpf_insn (cyngor: cymerwch y dymp oddi uchod, ailddarllenwch yr adran cyfarwyddiadau, agorwch linux/bpf.h и linux/bpf_common.h a cheisio penderfynu struct bpf_insn insns[] ar eich pen eich hun):

struct bpf_insn insns[] = {
    /* 85 00 00 00 08 00 00 00 call 8 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 8,
    },

    /* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */
    {
        .code = BPF_MEM | BPF_STX,
        .off = -4,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_10,
    },

    /* bf a2 00 00 00 00 00 00 r2 = r10 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_10,
        .dst_reg = BPF_REG_2,
    },

    /* 07 02 00 00 fc ff ff ff r2 += -4 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_2,
        .imm = -4,
    },

    /* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */
    {
        .code = BPF_LD | BPF_DW | BPF_IMM,
        .src_reg = BPF_PSEUDO_MAP_FD,
        .dst_reg = BPF_REG_1,
        .imm = map_fd,
    },
    { }, /* placeholder */

    /* 85 00 00 00 01 00 00 00 call 1 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 1,
    },

    /* b7 01 00 00 00 00 00 00 r1 = 0 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 0,
    },

    /* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */
    {
        .code = BPF_JMP | BPF_JEQ | BPF_K,
        .off = 4,
        .src_reg = BPF_REG_0,
        .imm = 0,
    },

    /* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */
    {
        .code = BPF_MEM | BPF_LDX,
        .off = 0,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_1,
    },

    /* 07 01 00 00 01 00 00 00 r1 += 1 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 1,
    },

    /* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */
    {
        .code = BPF_MEM | BPF_STX,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* b7 01 00 00 02 00 00 00 r1 = 2 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 2,
    },

    /* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* 95 00 00 00 00 00 00 00 exit */
    {
        .code = BPF_JMP | BPF_EXIT
    },
};

Ymarferiad ar gyfer y rhai nad oeddent yn ysgrifennu hyn eu hunain - darganfyddwch map_fd.

Mae un rhan arall heb ei datgelu ar ôl yn ein rhaglen - xdp_attach. Yn anffodus, ni ellir cysylltu rhaglenni fel XDP gan ddefnyddio galwad system bpf. Roedd y bobl a greodd BPF a XDP yn dod o'r gymuned Linux ar-lein, sy'n golygu eu bod wedi defnyddio'r un mwyaf cyfarwydd iddynt (ond nid i arferol pobl) ar gyfer rhyngweithio â'r cnewyllyn: socedi netlink, Gweld hefyd RFC3549. Y ffordd symlaf o weithredu xdp_attach yn copïo cod o libbpf, sef, o'r ffeil netlink.c, sef yr hyn a wnaethom, gan ei fyrhau ychydig:

Croeso i fyd socedi netlink

Agorwch fath soced netlink NETLINK_ROUTE:

int netlink_open(__u32 *nl_pid)
{
    struct sockaddr_nl sa;
    socklen_t addrlen;
    int one = 1, ret;
    int sock;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0)
        err(1, "socket");

    if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0)
        warnx("netlink error reporting not supported");

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
        err(1, "bind");

    addrlen = sizeof(sa);
    if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0)
        err(1, "getsockname");

    *nl_pid = sa.nl_pid;
    return sock;
}

Rydyn ni'n darllen o'r soced hon:

static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq)
{
    bool multipart = true;
    struct nlmsgerr *errm;
    struct nlmsghdr *nh;
    char buf[4096];
    int len, ret;

    while (multipart) {
        multipart = false;
        len = recv(sock, buf, sizeof(buf), 0);
        if (len < 0)
            err(1, "recv");

        if (len == 0)
            break;

        for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
                nh = NLMSG_NEXT(nh, len)) {
            if (nh->nlmsg_pid != nl_pid)
                errx(1, "wrong pid");
            if (nh->nlmsg_seq != seq)
                errx(1, "INVSEQ");
            if (nh->nlmsg_flags & NLM_F_MULTI)
                multipart = true;
            switch (nh->nlmsg_type) {
                case NLMSG_ERROR:
                    errm = (struct nlmsgerr *)NLMSG_DATA(nh);
                    if (!errm->error)
                        continue;
                    ret = errm->error;
                    // libbpf_nla_dump_errormsg(nh); too many code to copy...
                    goto done;
                case NLMSG_DONE:
                    return 0;
                default:
                    break;
            }
        }
    }
    ret = 0;
done:
    return ret;
}

Yn olaf, dyma ein swyddogaeth sy'n agor soced ac yn anfon neges arbennig iddo sy'n cynnwys disgrifydd ffeil:

static int xdp_attach(int ifindex, int prog_fd)
{
    int sock, seq = 0, ret;
    struct nlattr *nla, *nla_xdp;
    struct {
        struct nlmsghdr  nh;
        struct ifinfomsg ifinfo;
        char             attrbuf[64];
    } req;
    __u32 nl_pid = 0;

    sock = netlink_open(&nl_pid);
    if (sock < 0)
        return sock;

    memset(&req, 0, sizeof(req));
    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    req.nh.nlmsg_type = RTM_SETLINK;
    req.nh.nlmsg_pid = 0;
    req.nh.nlmsg_seq = ++seq;
    req.ifinfo.ifi_family = AF_UNSPEC;
    req.ifinfo.ifi_index = ifindex;

    /* started nested attribute for XDP */
    nla = (struct nlattr *)(((char *)&req)
            + NLMSG_ALIGN(req.nh.nlmsg_len));
    nla->nla_type = NLA_F_NESTED | IFLA_XDP;
    nla->nla_len = NLA_HDRLEN;

    /* add XDP fd */
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FD;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(int);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd));
    nla->nla_len += nla_xdp->nla_len;

    /* if user passed in any flags, add those too */
    __u32 flags = XDP_FLAGS_SKB_MODE;
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FLAGS;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags));
    nla->nla_len += nla_xdp->nla_len;

    req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len);

    if (send(sock, &req, req.nh.nlmsg_len, 0) < 0)
        err(1, "send");
    ret = bpf_netlink_recv(sock, nl_pid, seq);

cleanup:
    close(sock);
    return ret;
}

Felly, mae popeth yn barod i'w brofi:

$ cc nolibbpf.c -o nolibbpf
$ sudo strace -e bpf ./nolibbpf
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, map_name="woo", ...}, 72) = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=15, prog_name="woo", ...}, 72) = 4
+++ exited with 0 +++

Gadewch i ni weld a yw ein rhaglen wedi cysylltu â lo:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 160

Gadewch i ni anfon pings ac edrych ar y map:

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
$ sudo bpftool m dump name woo
key: 00 00 00 00  value: 90 01 00 00 00 00 00 00
key: 01 00 00 00  value: 00 00 00 00 00 00 00 00
key: 02 00 00 00  value: 00 00 00 00 00 00 00 00
key: 03 00 00 00  value: 00 00 00 00 00 00 00 00
key: 04 00 00 00  value: 00 00 00 00 00 00 00 00
key: 05 00 00 00  value: 00 00 00 00 00 00 00 00
key: 06 00 00 00  value: 40 b5 00 00 00 00 00 00
key: 07 00 00 00  value: 00 00 00 00 00 00 00 00
Found 8 elements

Hurray, mae popeth yn gweithio. Sylwch, gyda llaw, bod ein map yn cael ei arddangos eto ar ffurf beit. Mae hyn oherwydd y ffaith bod, yn wahanol libbpf ni wnaethom lwytho gwybodaeth math (BTF). Ond byddwn yn siarad mwy am hyn y tro nesaf.

Offer datblygu

Yn yr adran hon, byddwn yn edrych ar becyn cymorth lleiaf datblygwr BPF.

Yn gyffredinol, nid oes angen unrhyw beth arbennig arnoch i ddatblygu rhaglenni BPF - mae BPF yn rhedeg ar unrhyw gnewyllyn dosbarthu gweddus, ac mae rhaglenni'n cael eu hadeiladu gan ddefnyddio clang, y gellir ei gyflenwi o'r pecyn. Fodd bynnag, oherwydd y ffaith bod BPF yn cael ei ddatblygu, mae'r cnewyllyn a'r offer yn newid yn gyson, os nad ydych chi am ysgrifennu rhaglenni BPF gan ddefnyddio dulliau hen ffasiwn o 2019, yna bydd yn rhaid i chi lunio

  • llvm/clang
  • pahole
  • ei graidd
  • bpftool

(Er mwyn cyfeirio ato, rhedwyd yr adran hon a phob enghraifft yn yr erthygl ar Debian 10.)

llvm/clag

Mae BPF yn gyfeillgar â LLVM ac, er y gellir llunio rhaglenni ar gyfer BPF yn ddiweddar gan ddefnyddio gcc, mae'r holl waith datblygu cyfredol yn cael ei wneud ar gyfer LLVM. Felly, yn gyntaf oll, byddwn yn adeiladu'r fersiwn gyfredol clang o git:

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... много времени спустя
$

Nawr gallwn wirio a ddaeth popeth at ei gilydd yn gywir:

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 11.0.0git
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver1

  Registered Targets:
    bpf    - BPF (host endian)
    bpfeb  - BPF (big endian)
    bpfel  - BPF (little endian)
    x86    - 32-bit X86: Pentium-Pro and above
    x86-64 - 64-bit X86: EM64T and AMD64

(Cyfarwyddiadau cynulliad clang cymryd gan mi o bpf_datblygu_QA.)

Ni fyddwn yn gosod y rhaglenni yr ydym newydd eu hadeiladu, ond yn hytrach dim ond ychwanegu atynt PATH, er enghraifft:

export PATH="`pwd`/bin:$PATH"

(Gellir ychwanegu at hwn .bashrc neu i ffeil ar wahân. Yn bersonol, dwi'n ychwanegu pethau fel hyn at ~/bin/activate-llvm.sh a phan fo angen dwi'n ei wneud . activate-llvm.sh.)

Pahole a BTF

Cyfleustodau pahole a ddefnyddir wrth adeiladu'r cnewyllyn i greu gwybodaeth dadfygio mewn fformat BTF. Ni fyddwn yn manylu yn yr erthygl hon am fanylion technoleg BTF, heblaw am y ffaith ei bod yn gyfleus ac rydym am ei defnyddio. Felly os ydych chi'n mynd i adeiladu'ch cnewyllyn, adeiladwch yn gyntaf pahole (heb pahole ni fyddwch yn gallu adeiladu'r cnewyllyn gyda'r opsiwn CONFIG_DEBUG_INFO_BTF:

$ git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git
$ cd pahole/
$ sudo apt install cmake
$ mkdir build
$ cd build/
$ cmake -D__LIB=lib ..
$ make
$ sudo make install
$ which pahole
/usr/local/bin/pahole

Cnewyllyn ar gyfer arbrofi gyda BPF

Wrth archwilio posibiliadau BPF, rwyf am gydosod fy nghraidd fy hun. Nid yw hyn, yn gyffredinol, yn angenrheidiol, gan y byddwch yn gallu llunio a llwytho rhaglenni BPF ar y cnewyllyn dosbarthu, fodd bynnag, mae cael eich cnewyllyn eich hun yn caniatáu ichi ddefnyddio'r nodweddion BPF diweddaraf, a fydd yn ymddangos yn eich dosbarthiad ymhen misoedd ar y gorau. , neu, fel yn achos rhai offer dadfygio ni fydd yn cael ei becynnu o gwbl yn y dyfodol agos. Hefyd, mae ei graidd ei hun yn ei gwneud hi'n bwysig arbrofi gyda'r cod.

Er mwyn adeiladu cnewyllyn mae angen, yn gyntaf, y cnewyllyn ei hun, ac yn ail, ffeil cyfluniad cnewyllyn. I arbrofi gyda BPF gallwn ddefnyddio'r arferol fanila cnewyllyn neu un o'r cnewyllyn datblygu. Yn hanesyddol, mae datblygiad BPF yn digwydd o fewn cymuned rwydweithio Linux ac felly mae pob newid yn hwyr neu'n hwyrach yn mynd trwy David Miller, cynhaliwr rhwydweithio Linux. Yn dibynnu ar eu natur - golygiadau neu nodweddion newydd - mae newidiadau rhwydwaith yn perthyn i un o ddau graidd - net neu net-next. Mae newidiadau ar gyfer BPF yn cael eu dosbarthu yn yr un ffordd rhwng bpf и bpf-next, sydd wedyn yn cael eu cronni i mewn i net a net-nesaf, yn y drefn honno. Am ragor o fanylion, gw bpf_datblygu_QA и netdev-FAQ. Felly dewiswch gnewyllyn yn seiliedig ar eich chwaeth ac anghenion sefydlogrwydd y system rydych chi'n ei phrofi (*-next cnewyllyn yw'r mwyaf ansefydlog o'r rhai a restrir).

Mae y tu hwnt i gwmpas yr erthygl hon i siarad am sut i reoli ffeiliau cyfluniad cnewyllyn - rhagdybir eich bod naill ai eisoes yn gwybod sut i wneud hyn, neu barod i ddysgu ar eich pen eich hun. Fodd bynnag, dylai'r cyfarwyddiadau canlynol fod yn ddigon mwy neu lai i roi system BPF sy'n gweithio i chi.

Lawrlwythwch un o'r cnewyllyn uchod:

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next

Adeiladu cyfluniad cnewyllyn gweithio lleiaf posibl:

$ cp /boot/config-`uname -r` .config
$ make localmodconfig

Galluogi opsiynau BPF yn y ffeil .config o'ch dewis chi (yn fwyaf tebygol CONFIG_BPF Bydd eisoes wedi'i alluogi gan fod systemd yn ei ddefnyddio). Dyma restr o opsiynau o'r cnewyllyn a ddefnyddir ar gyfer yr erthygl hon:

CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_IPV6_SEG6_BPF=y
# CONFIG_NETFILTER_XT_MATCH_BPF is not set
# CONFIG_BPFILTER is not set
CONFIG_NET_CLS_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y

Yna gallwn ni gydosod a gosod y modiwlau a'r cnewyllyn yn hawdd (gyda llaw, gallwch chi ymgynnull y cnewyllyn gan ddefnyddio'r sydd newydd ei ymgynnull clangtrwy ychwanegu CC=clang):

$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install

ac ailgychwyn gyda'r cnewyllyn newydd (dwi'n ei ddefnyddio ar gyfer hyn kexec o'r pecyn kexec-tools):

v=5.8.0-rc6+ # если вы пересобираете текущее ядро, то можно делать v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -e

bpftool

Y cyfleustodau a ddefnyddir amlaf yn yr erthygl fydd y cyfleustodau bpftool, a gyflenwir fel rhan o'r cnewyllyn Linux. Mae'n cael ei ysgrifennu a'i gynnal gan ddatblygwyr BPF ar gyfer datblygwyr BPF a gellir ei ddefnyddio i reoli pob math o wrthrychau BPF - llwytho rhaglenni, creu a golygu mapiau, archwilio bywyd ecosystem BPF, ac ati. Gellir dod o hyd i ddogfennaeth ar ffurf codau ffynhonnell ar gyfer tudalennau dyn yn y craidd neu, wedi'i lunio'n barod, ar y rhwyd.

Ar adeg ysgrifennu'r ysgrifen hon bpftool yn dod yn barod ar gyfer RHEL, Fedora a Ubuntu yn unig (gweler, er enghraifft, yr edefyn hwn, sy'n adrodd stori anorffenedig pecynnu bpftool yn Debian). Ond os ydych chi eisoes wedi adeiladu eich cnewyllyn, yna adeiladu bpftool mor hawdd â phastai:

$ cd ${linux}/tools/bpf/bpftool
# ... пропишите пути к последнему clang, как рассказано выше
$ make -s

Auto-detecting system features:
...                        libbfd: [ on  ]
...        disassembler-four-args: [ on  ]
...                          zlib: [ on  ]
...                        libcap: [ on  ]
...               clang-bpf-co-re: [ on  ]

Auto-detecting system features:
...                        libelf: [ on  ]
...                          zlib: [ on  ]
...                           bpf: [ on  ]

$

(Yma ${linux} - dyma'ch cyfeiriadur cnewyllyn.) Ar ôl gweithredu'r gorchmynion hyn bpftool yn cael ei gasglu mewn cyfeiriadur ${linux}/tools/bpf/bpftool a gellir ei ychwanegu at y llwybr (yn gyntaf oll i'r defnyddiwr root) neu dim ond copi i /usr/local/sbin.

Casglu bpftool mae'n well defnyddio'r olaf clang, wedi'i ymgynnull fel y disgrifir uchod, a gwiriwch a yw wedi'i gydosod yn gywir - gan ddefnyddio, er enghraifft, y gorchymyn

$ sudo bpftool feature probe kernel
Scanning system configuration...
bpf() syscall for unprivileged users is enabled
JIT compiler is enabled
JIT compiler hardening is disabled
JIT compiler kallsyms exports are enabled for root
...

a fydd yn dangos pa nodweddion BPF sydd wedi'u galluogi yn eich cnewyllyn.

Gyda llaw, gellir rhedeg y gorchymyn blaenorol fel

# bpftool f p k

Gwneir hyn trwy gyfatebiaeth â'r cyfleustodau o'r pecyn iproute2, lle gallwn, er enghraifft, ddweud ip a s eth0 yn hytrach na ip addr show dev eth0.

Casgliad

Mae BPF yn caniatáu ichi pedoli chwain er mwyn mesur yn effeithiol a newid ymarferoldeb y craidd wrth hedfan. Trodd y system yn llwyddiannus iawn, yn nhraddodiadau gorau UNIX: roedd mecanwaith syml sy'n eich galluogi i (ail)raglennu'r cnewyllyn yn caniatáu i nifer fawr o bobl a sefydliadau arbrofi. Ac, er bod yr arbrofion, yn ogystal â datblygiad y seilwaith BPF ei hun, ymhell o fod wedi'u gorffen, mae gan y system ABI sefydlog eisoes sy'n eich galluogi i adeiladu rhesymeg busnes dibynadwy, ac yn bwysicaf oll, effeithiol.

Hoffwn nodi bod y dechnoleg, yn fy marn i, wedi dod mor boblogaidd oherwydd, ar y naill law, gall chwarae (gellir deall pensaernïaeth peiriant fwy neu lai mewn un noson), ac ar y llaw arall, i ddatrys problemau na ellid eu datrys (yn hardd) cyn ei ymddangosiad. Mae'r ddwy gydran hyn gyda'i gilydd yn gorfodi pobl i arbrofi a breuddwydio, sy'n arwain at ymddangosiad atebion mwy a mwy arloesol.

Mae'r erthygl hon, er nad yw'n arbennig o fyr, yn gyflwyniad i fyd BPF yn unig ac nid yw'n disgrifio nodweddion “uwch” a rhannau pwysig o'r bensaernïaeth. Mae'r cynllun wrth symud ymlaen yn rhywbeth fel hyn: yr erthygl nesaf fydd trosolwg o fathau o raglenni BPF (mae yna 5.8 math o raglen yn cael eu cefnogi yn y cnewyllyn 30), yna byddwn yn olaf yn edrych ar sut i ysgrifennu cymwysiadau BPF go iawn gan ddefnyddio rhaglenni olrhain cnewyllyn fel enghraifft, yna mae'n bryd cael cwrs mwy manwl ar bensaernïaeth BPF, ac yna enghreifftiau o rwydweithio BPF a chymwysiadau diogelwch.

Erthyglau blaenorol yn y gyfres hon

  1. BPF ar gyfer y rhai bach, rhan sero: BPF clasurol

Cysylltiadau

  1. Canllaw Cyfeirio BPF a XDP - dogfennaeth ar BPF o cilium, neu'n fwy manwl gywir gan Daniel Borkman, un o grewyr a chynhalwyr BPF. Dyma un o'r disgrifiadau difrifol cyntaf, sy'n wahanol i'r lleill gan fod Daniel yn gwybod yn union am beth mae'n ysgrifennu ac nid oes unrhyw gamgymeriadau yno. Yn benodol, mae'r ddogfen hon yn disgrifio sut i weithio gyda rhaglenni BPF o'r mathau XDP a TC gan ddefnyddio'r cyfleustodau adnabyddus ip o'r pecyn iproute2.

  2. Dogfennaeth/rhwydweithio/filter.txt — ffeil wreiddiol gyda dogfennaeth ar gyfer BPF clasurol ac yna estynedig. Darlleniad da os ydych chi am ymchwilio i iaith y cynulliad a manylion pensaernïol technegol.

  3. Blog am BPF oddi ar facebook. Anaml y caiff ei ddiweddaru, ond yn briodol, gan fod Alexei Starovoitov (awdur eBPF) ac Andrii Nakryiko - (cynhaliwr) yn ysgrifennu yno libbpf).

  4. Cyfrinachau bpftool. Trydar difyr gan Quentin Monnet gydag enghreifftiau a chyfrinachau o ddefnyddio bpftool.

  5. Deifiwch i BPF: rhestr o ddeunydd darllen. Rhestr enfawr (sy'n dal i gael ei chynnal) o ddolenni i ddogfennaeth BPF gan Quentin Monnet.

Ffynhonnell: hab.com

Ychwanegu sylw