Эхэндээ технологи байсан бөгөөд үүнийг BPF гэж нэрлэдэг байсан. Бид түүн рүү харлаа
Товчхондоо, BPF нь Линуксийн цөмийн орон зайд хэрэглэгчийн оруулсан кодыг дур мэдэн ажиллуулах боломжийг олгодог бөгөөд шинэ архитектур нь маш амжилттай болсон тул түүний бүх програмыг тайлбарлахын тулд бидэнд хэдэн арван нийтлэл хэрэгтэй болно. (Доорх гүйцэтгэлийн кодоос харж байгаачлан хөгжүүлэгчид сайн хийгээгүй цорын ганц зүйл бол зохистой лого бүтээх явдал байв.)
Энэ нийтлэлд BPF виртуал машины бүтэц, BPF-тэй ажиллах цөмийн интерфэйсүүд, хөгжүүлэлтийн хэрэгслүүд, түүнчлэн одоо байгаа боломжуудын талаар товч, маш товч тоймыг тайлбарласан болно. BPF-ийн практик хэрэглээг илүү гүнзгий судлахын тулд ирээдүйд бидэнд хэрэгтэй бүх зүйл.
Өгүүллийн хураангуй
bpf(2)
.
Пишем программы BPF с помощью libbpf
.libbpf
. Бид дараагийн жишээнүүдэд ашиглах үндсэн BPF програмын араг ясыг үүсгэх болно.
BPF Архитектурын танилцуулга
BPF архитектурыг авч үзэхээсээ өмнө бид сүүлчийн удаа (өө) рүү хандана
Шинэ BPF нь 64 битийн машинууд, үүлэн үйлчилгээ, SDN үүсгэх хэрэгслүүдийн хэрэгцээ нэмэгдэж байгаатай холбогдуулан боловсруулсан болно.Sпрограм хангамжdтодорхойлсон nажиллах). Цөмийн сүлжээний инженерүүдийн сонгодог BPF-ийг орлуулах зорилгоор бүтээсэн шинэ BPF нь зургаан сарын дараа Линукс системийг мөрдөхөд хэцүү программуудыг олсон бөгөөд одоо гарч ирснээс хойш зургаан жилийн дараа бидэнд дараагийн нийтлэл хэрэгтэй болно. янз бүрийн төрлийн програмуудыг жагсаана.
Хөгжилтэй зургууд
Үндсэндээ BPF нь хамгаалагдсан хязгаарлагдмал орчинд ажилладаг виртуал машин бөгөөд цөмийн орон зайд аюулгүй байдлыг алдагдуулахгүйгээр "дурын" кодыг ажиллуулах боломжийг олгодог. BPF программууд нь хэрэглэгчийн орон зайд бүтээгдэж, цөмд ачаалагдаж, зарим үйл явдлын эх сурвалжтай холбогддог. Үйл явдал нь жишээлбэл, багцыг сүлжээний интерфейс рүү хүргэх, цөмийн зарим функцийг эхлүүлэх гэх мэт байж болно. Багцын хувьд BPF програм нь багцын өгөгдөл, мета өгөгдөлд хандах боломжтой (хөтөлбөрийн төрлөөс хамааран унших, бичих боломжтой), цөмийн функцийг ажиллуулах тохиолдолд аргументууд. функц, үүнд цөмийн санах ойн заагч гэх мэт.
Энэ үйл явцыг нарийвчлан авч үзье. Эхлэхийн тулд ассемблер дээр бичигдсэн программуудыг сонгодог BPF-ээс эхний ялгааны талаар ярилцъя. Шинэ хувилбарт программыг өндөр түвшний хэлээр, юуны түрүүнд мэдээж C хэл дээр бичих боломжтой байхаар архитектурыг өргөжүүлсэн. Үүний тулд llvm-д зориулсан backend боловсруулсан бөгөөд энэ нь BPF архитектурт байт код үүсгэх боломжийг олгодог.
BPF архитектур нь зарим талаараа орчин үеийн машинууд дээр үр дүнтэй ажиллахаар зохион бүтээгдсэн. Үүнийг практикт хэрэгжүүлэхийн тулд цөмд ачаалагдсаны дараа BPF байт кодыг JIT хөрвүүлэгч хэмээх бүрэлдэхүүн хэсгийг ашиглан эх код руу хөрвүүлдэг.Just In Time). Дараа нь, хэрэв та санаж байгаа бол сонгодог BPF-д програмыг цөмд ачаалж, үйл явдлын эх үүсвэрт атомаар хавсаргасан - нэг системийн дуудлагын хүрээнд. Шинэ архитектурт энэ нь хоёр үе шаттайгаар явагддаг - эхлээд кодыг системийн дуудлага ашиглан цөмд ачаалдаг. bpf(2)
дараа нь програмын төрлөөс хамааран өөр өөр механизмаар дамжуулан програм нь үйл явдлын эх сурвалжтай холбогддог.
Энд уншигчид асуулт гарч ирж магадгүй юм: боломжтой юу? Ийм кодын гүйцэтгэлийн аюулгүй байдал хэрхэн баталгаажсан бэ? Гүйцэтгэлийн аюулгүй байдлыг баталгаажуулагч гэж нэрлэгддэг BPF програмуудыг ачаалах үе шатаар баталгаажуулдаг (англи хэл дээр энэ үе шатыг шалгагч гэж нэрлэдэг бөгөөд би англи үгийг үргэлжлүүлэн ашиглах болно):
Verifier нь програм нь цөмийн хэвийн ажиллагааг алдагдуулахгүй байхыг баталгаажуулдаг статик анализатор юм. Дашрамд хэлэхэд, энэ програм нь системийн үйл ажиллагаанд саад болохгүй гэсэн үг биш юм - BPF програмууд нь төрлөөс хамааран цөмийн санах ойн хэсгүүдийг уншиж, дахин бичих, функцүүдийн утгыг буцаах, тайрах, нэмэх, дахин бичих боломжтой. тэр ч байтугай сүлжээний пакетуудыг дамжуулах. Verifier нь BPF програмыг ажиллуулснаар цөмд гэмтэл учруулахгүй бөгөөд дүрмийн дагуу бичих хандалттай програм, тухайлбал, гарч буй пакетийн өгөгдөл нь пакетаас гадуур цөмийн санах ойг дарж бичих боломжгүй гэдгийг баталгаажуулдаг. BPF-ийн бусад бүх бүрэлдэхүүн хэсгүүдтэй танилцсаны дараа бид баталгаажуулагчийг холбогдох хэсэгт бага зэрэг нарийвчлан авч үзэх болно.
Тэгэхээр бид өнөөг хүртэл юу сурсан бэ? Хэрэглэгч C хэл дээр програм бичиж, системийн дуудлагыг ашиглан цөмд ачаална bpf(2)
, үүнийг баталгаажуулагчаар шалгаж, эх байт код руу хөрвүүлдэг. Дараа нь ижил эсвэл өөр хэрэглэгч програмыг үйл явдлын эх сурвалжтай холбож, ажиллаж эхэлнэ. Ачаалах болон холболтыг салгах нь хэд хэдэн шалтгааны улмаас шаардлагатай байдаг. Нэгдүгээрт, баталгаажуулагч ажиллуулах нь харьцангуй үнэтэй бөгөөд нэг програмыг хэд хэдэн удаа татаж авснаар бид компьютерийн цагийг дэмий үрдэг. Хоёрдугаарт, програмыг яг яаж холбох нь түүний төрлөөс хамаардаг бөгөөд жилийн өмнө боловсруулсан нэг "бүх нийтийн" интерфейс нь шинэ төрлийн програмуудад тохиромжгүй байж магадгүй юм. (Хэдийгээр одоо архитектур нь боловсронгуй болж байгаа ч энэ интерфейсийг түвшинд нэгтгэх санаа байна. libbpf
.)
Анхааралтай уншигч бид зургийг хараахан дуусаагүй байгааг анзаарч магадгүй юм. Үнэн хэрэгтээ, дээр дурдсан бүх зүйл нь BPF яагаад сонгодог BPF-тэй харьцуулахад дүр төрхийг үндсээр нь өөрчилдөгийг тайлбарлахгүй. Хэрэглэх хүрээг ихээхэн өргөжүүлсэн хоёр шинэлэг зүйл бол хуваалцсан санах ой болон цөмийн туслах функцуудыг ашиглах чадвар юм. BPF-д хуваалцсан санах ойг газрын зураг гэж нэрлэдэг - тодорхой API бүхий хуваалцсан өгөгдлийн бүтцийг ашиглан хэрэгжүүлдэг. Эхний төрлийн газрын зураг нь хэш хүснэгт байсан тул тэд ийм нэрийг авсан байх. Дараа нь массивууд гарч ирэв, орон нутгийн (CPU тутамд) хэш хүснэгтүүд болон локал массивууд, хайлтын мод, BPF програмын заагч агуулсан газрын зураг болон бусад олон зүйлс. Бидний хувьд хамгийн сонирхолтой зүйл бол BPF программууд дуудлага хоорондын төлөвийг хадгалах, бусад программ болон хэрэглэгчийн орон зайд хуваалцах чадвартай болсон явдал юм.
Газрын зурагт системийн дуудлага ашиглан хэрэглэгчийн процессоос ханддаг bpf(2)
, мөн туслах функцуудыг ашиглан цөмд ажиллаж байгаа BPF програмуудаас. Түүгээр ч зогсохгүй туслагч нар газрын зурагтай ажиллахаас гадна цөмийн бусад чадамжид хандах боломжтой. Жишээлбэл, BPF програмууд нь пакетуудыг бусад интерфейс рүү дамжуулах, perf үйл явдлуудыг үүсгэх, цөмийн бүтцэд хандах гэх мэт туслах функцуудыг ашиглаж болно.
Дүгнэж хэлэхэд, BPF нь дурын, өөрөөр хэлбэл баталгаажуулагчаар шалгасан хэрэглэгчийн кодыг цөмийн орон зайд ачаалах боломжийг олгодог. Энэ код нь дуудлагын хоорондох төлөвийг хадгалах, хэрэглэгчийн зайгаар өгөгдөл солилцох боломжтой бөгөөд мөн энэ төрлийн програмын зөвшөөрөгдсөн цөмийн дэд системд хандах боломжтой.
Энэ нь цөмийн модулиудын өгсөн боломжуудтай аль хэдийн ижил төстэй бөгөөд BPF нь зарим давуу талтай байдаг (мэдээжийн хэрэг та зөвхөн ижил төстэй програмуудыг харьцуулж болно, жишээлбэл, системийн мөрдөх - та BPF-ээр дурын драйвер бичиж чадахгүй). Та элсэлтийн босго бага (BPF ашигладаг зарим хэрэгслүүд нь хэрэглэгчээс цөмийн програмчлалын ур чадвар, ерөнхийдөө програмчлалын ур чадвартай байхыг шаарддаггүй), ажиллах үеийн аюулгүй байдал (бичиж байхдаа системийг зөрчөөгүй хүмүүст тайлбар дээр гараа өргөж) тэмдэглэж болно. эсвэл туршилтын модулиуд), атомын шинж чанар - модулиудыг дахин ачаалах үед сул зогсолт гардаг бөгөөд BPF дэд систем нь ямар ч үйл явдлыг орхигдуулахгүй байхыг баталгаажуулдаг (шударга байхын тулд энэ нь бүх төрлийн BPF програмуудад үнэн биш юм).
Ийм чадвар байгаа нь BPF-ийг цөмийг өргөжүүлэх бүх нийтийн хэрэгсэл болгодог бөгөөд энэ нь практик дээр батлагдсан: BPF-д улам олон шинэ төрлийн програмууд нэмэгдэж, улам олон томоохон компаниуд BPF-ийг 24 × 7 байлдааны сервер дээр ашигладаг. стартапууд бизнесээ BPF-д суурилсан шийдлүүд дээр үндэслэн байгуулдаг. BPF-ийг хаа сайгүй ашигладаг: DDoS халдлагаас хамгаалах, SDN үүсгэх (жишээлбэл, кубернетийн сүлжээг хэрэгжүүлэх), системийн мөрдөх гол хэрэгсэл, статистик цуглуулагч, халдлагыг илрүүлэх систем, хамгаалагдсан хязгаарлагдмал орчинд ашиглах гэх мэт.
Өгүүллийн тойм хэсгийг эндээс дуусгаад виртуал машин болон BPF экосистемийг илүү дэлгэрэнгүй авч үзье.
Зайлшгүй байдал: хэрэгслүүд
Дараах хэсгүүдийн жишээнүүдийг ажиллуулахын тулд танд дор хаяж хэд хэдэн хэрэгсэл хэрэгтэй байж магадгүй юм. llvm
/clang
bpf дэмжлэгтэйгээр ба bpftool
Байна. Хэсэг дээр
BPF виртуал машины бүртгэл ба зааврын систем
BPF-ийн архитектур, командын системийг программуудыг Си хэл дээр бичиж, цөмд ачаалсны дараа эх код руу хөрвүүлэхийг харгалзан боловсруулсан. Тиймээс регистрийн тоо, командын багцыг математикийн утгаараа орчин үеийн машинуудын боломжийн огтлолцлыг харгалзан сонгосон. Нэмж дурдахад, программуудад янз бүрийн хязгаарлалт тавьсан, тухайлбал, саяхныг хүртэл гогцоо, дэд программ бичих боломжгүй байсан бөгөөд зааврын тоо 4096-аар хязгаарлагдаж байсан (одоо давуу эрхтэй програмууд сая хүртэлх зааврыг ачаалах боломжтой).
BPF нь хэрэглэгчид хандах боломжтой арван нэгэн 64 битийн бүртгэлтэй r0
-r10
болон програмын тоолуур. Бүртгүүлэх r10
хүрээ заагч агуулсан бөгөөд зөвхөн унших боломжтой. Хөтөлбөрүүд ажиллах үед 512 байт стек болон газрын зураг хэлбэрээр хязгааргүй хэмжээний хуваалцсан санах ойд хандах боломжтой.
BPF програмууд нь програмын төрлийн цөмийн туслахуудын тодорхой багц болон сүүлийн үед ердийн функцуудыг ажиллуулахыг зөвшөөрдөг. Дуудсан функц бүр нь регистрт дамжуулагдсан тав хүртэлх аргументыг авч болно r1
-r5
, буцах утгыг дамжуулна r0
. Функцээс буцаж ирсний дараа бүртгэлийн контентууд баталгаатай болно r6
-r9
Өөрчлөхгүй.
Хөтөлбөрийг үр дүнтэй орчуулахын тулд бүртгүүлдэг r0
-r11
Бүх дэмжигдсэн архитектурын хувьд одоогийн архитектурын ABI онцлогийг харгалзан бодит регистрүүдтэй өвөрмөц байдлаар дүрслэгдсэн болно. Жишээ нь, төлөө x86_64
бүртгэлүүд r1
-r5
, функцийн параметрүүдийг дамжуулахад ашигладаг, дээр харагдана rdi
, rsi
, rdx
, rcx
, r8
, эдгээр нь функцүүдэд параметр дамжуулахад хэрэглэгддэг x86_64
. Жишээлбэл, зүүн талд байгаа код нь баруун талд байгаа код руу дараах байдлаар хөрвүүлнэ.
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
Бүртгүүлэх r0
Мөн програмын гүйцэтгэлийн үр дүнг буцаах, бүртгэлд ашигладаг r1
програмыг контекст руу заагч дамжуулдаг - програмын төрлөөс хамааран энэ нь жишээ нь бүтэц байж болно. struct xdp_md
struct __sk_buff
struct pt_regs
Тиймээс бид регистр, цөмийн туслах, стек, контекст заагч, газрын зураг хэлбэрээр хуваалцсан санах ойтой болсон. Энэ бүхэн аялалд зайлшгүй шаардлагатай биш, гэхдээ ...
Тодорхойлолтыг үргэлжлүүлж, эдгээр объектуудтай ажиллах командын системийн талаар ярилцъя. Бүгд (
энд Code
- энэ бол зааврын кодчилол, Dst
/Src
хүлээн авагч ба эх сурвалжийн кодчилолууд нь тус тус Off
- 16 битийн гарын үсэгтэй догол, ба Imm
нь зарим зааварт хэрэглэгддэг 32 битийн тэмдэгт бүхэл тоо юм (cBPF тогтмол K-тэй төстэй). Кодлох Code
хоёр төрлийн нэг байна:
0, 1, 2, 3 зааврын ангиуд нь санах ойтой ажиллах командуудыг тодорхойлдог. Тэд BPF_LD
, BPF_LDX
, BPF_ST
, BPF_STX
, тус тус. 4, 7-р анги (BPF_ALU
, BPF_ALU64
) ALU зааврын багцыг бүрдүүлнэ. 5, 6-р анги (BPF_JMP
, BPF_JMP32
) үсрэх заавар агуулсан.
BPF зааврын системийг судлах цаашдын төлөвлөгөө нь дараах байдалтай байна: бүх заавар, тэдгээрийн параметрүүдийг нарийвчлан жагсаахын оронд бид энэ хэсэгт хэд хэдэн жишээг авч үзэх бөгөөд тэдгээрээс заавар нь хэрхэн ажилладаг, хэрхэн ажиллах нь тодорхой болно. BPF-д зориулсан хоёртын файлыг гараар задлах. Өгүүллийн дараа материалыг нэгтгэхийн тулд бид Verifier, JIT хөрвүүлэгч, сонгодог BPF-ийн орчуулга, газрын зураг судлах, функцийг дуудах гэх мэт хэсгүүдийн бие даасан зааварчилгаатай танилцах болно.
Бид бие даасан зааврын талаар ярихдаа үндсэн файлуудад хандах болно bpf.h
bpf_common.h
Жишээ нь: толгойдоо BPF-ийг задлах
Бид програмыг хөрвүүлдэг жишээг харцгаая readelf-example.c
мөн үүссэн хоёртын файлыг хар. Бид анхны агуулгыг илчлэх болно readelf-example.c
Бид хоёртын кодоос түүний логикийг сэргээсний дараа доор:
$ 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 ................
Гаралтын эхний багана readelf
догол мөр бөгөөд манай программ нь дараах дөрвөн тушаалаас бүрдэнэ.
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
Тушаалын кодууд тэнцүү байна b7
, 15
, b7
и 95
. Хамгийн бага ач холбогдолтой гурван бит нь зааврын анги гэдгийг санаарай. Манай тохиолдолд бүх зааврын дөрөв дэх бит хоосон тул зааврын ангиуд нь тус тус 7, 5, 7, 5, 7-р анги нь BPF_ALU64
, мөн 5 байна BPF_JMP
. Хоёр ангийн хувьд зааврын формат ижил (дээрхийг харна уу) бөгөөд бид програмаа ингэж дахин бичиж болно (үүнтэй зэрэгцэн бид үлдсэн багануудыг хүн хэлбэрээр дахин бичих болно):
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
Үйл ажиллагаа b
анги ALU64
Байна уу s
(эх сурвалж), дараа нь утгыг эх регистрээс авах бөгөөд хэрэв бидний тохиолдлынх шиг үүнийг тохируулаагүй бол утгыг талбараас авна. Imm
. Тиймээс эхний болон гурав дахь зааварт бид үйл ажиллагааг гүйцэтгэдэг r0 = Imm
. Цаашилбал, JMP анги 1 үйл ажиллагаа юм S
тэг бол эх регистрийн утгыг талбартай харьцуулна Imm
. Хэрэв утгууд давхцаж байвал шилжилт явагдана PC + Off
хаана PC
, ердийнх шиг, дараагийн зааврын хаягийг агуулна. Эцэст нь хэлэхэд, JMP 9-р ангийн үйл ажиллагаа BPF_EXIT
r0
. Хүснэгтэндээ шинэ багана нэмье:
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
Бид үүнийг илүү тохиромжтой хэлбэрээр дахин бичиж болно:
r0 = 1
if (r1 == 0) goto END
r0 = 2
END:
exit
Хэрэв бид бүртгэлд юу байгааг санаж байвал r1
Програм нь цөм болон регистрээс контекст руу заагчийг дамжуулдаг r0
утгыг цөм рүү буцаасан тохиолдолд контекстийн заагч тэг байвал бид 1, эсрэгээр - 2 гэж буцаана. Эх сурвалжийг хараад бидний зөв эсэхийг шалгая:
$ cat readelf-example.c
int foo(void *ctx)
{
return ctx ? 2 : 1;
}
Тиймээ, энэ нь утгагүй програм боловч ердөө дөрвөн энгийн зааварчилгаагаар орчуулагддаг.
Үл хамаарах жишээ: 16 байт заавар
Зарим заавар нь 64 битээс илүү зай эзэлдэг талаар бид өмнө нь дурдсан. Энэ нь жишээлбэл зааварт хамаарна lddw
(Код = 0x18
= BPF_LD
BPF_DW
BPF_IMM
Imm
. Бодит байдал ийм л байна Imm
хэмжээ нь 32, давхар үг нь 64 бит тул 64 битийн шууд утгыг нэг 64 битийн зааварт регистр рүү ачаалах нь ажиллахгүй. Үүнийг хийхийн тулд 64 битийн утгын хоёр дахь хэсгийг талбарт хадгалахын тулд хоёр зэргэлдээ зааварчилгааг ашиглана Imm
Байна. Жишээ нь:
$ 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 ........
Хоёртын программд зөвхөн хоёр заавар байдаг:
Binary Disassm
18000000 ddccbbaa 00000000 44332211 r0 = Imm[0]|Imm[1]
95000000 00000000 exit
Заавартай дахин уулзана lddw
, бид нүүлгэн шилжүүлэх, газрын зурагтай ажиллах талаар ярих үед.
Жишээ нь: стандарт хэрэгслийг ашиглан BPF-ийг задлах
Тиймээс бид BPF хоёртын кодыг уншиж сурсан бөгөөд шаардлагатай бол аливаа зааврыг задлан шинжлэхэд бэлэн байна. Гэсэн хэдий ч практик дээр стандарт хэрэгслийг ашиглан програмыг задлах нь илүү тохиромжтой бөгөөд хурдан байдаг гэдгийг хэлэх нь зүйтэй.
$ 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
BPF объектуудын амьдралын мөчлөг, bpffs файлын систем
(Би энэ дэд хэсэгт тайлбарласан зарим нарийн ширийн зүйлийг эхлээд олж мэдсэн
BPF объектууд - программууд болон газрын зураг нь командуудыг ашиглан хэрэглэгчийн орон зайнаас бүтээгддэг BPF_PROG_LOAD
и BPF_MAP_CREATE
системийн дуудлага bpf(2)
, энэ нь яг яаж болдог талаар бид дараагийн хэсэгт ярих болно. Энэ нь цөмийн өгөгдлийн бүтцийг бий болгодог бөгөөд тэдгээр нь тус бүрийн хувьд refcount
(лавлагааны тоо) нэгээр тохируулагдсан бөгөөд тухайн объект руу чиглэсэн файлын тодорхойлогчийг хэрэглэгч рүү буцаана. Бариулыг хаасны дараа refcount
объект нэгээр багасч, тэг болоход объект устгагдана.
Хэрэв програм нь газрын зураг ашигладаг бол refcount
Програмыг ачаалсны дараа эдгээр газрын зургууд нэгээр нэмэгддэг, i.e. Тэдний файлын тодорхойлогч нь хэрэглэгчийн процессоос хаагдах боломжтой бөгөөд хэвээр байна refcount
тэг болохгүй:
Програмыг амжилттай ачаалсны дараа бид үүнийг ихэвчлэн ямар нэгэн үйл явдал үүсгэгчтэй холбодог. Жишээлбэл, бид үүнийг ирж буй пакетуудыг боловсруулах эсвэл заримтай нь холбохын тулд сүлжээний интерфейс дээр байрлуулж болно tracepoint
цөмд. Энэ үед лавлагааны тоолуур мөн нэгээр нэмэгдэх ба бид дуудагч програм дахь файлын тодорхойлогчийг хаах боломжтой болно.
Хэрэв бид одоо ачаалагчийг унтраавал яах вэ? Энэ нь үйл явдлын үүсгэгчийн (дэгээ) төрлөөс хамаарна. Ачигч дууссаны дараа бүх сүлжээний дэгээнүүд байх болно, эдгээр нь дэлхийн дэгээ гэж нэрлэгддэг. Жишээлбэл, мөрдөх програмууд нь тэдгээрийг үүсгэсэн процесс дууссаны дараа гарах болно (тиймээс "орон нутгийн процесс руу" орон нутгийн гэж нэрлэдэг). Техникийн хувьд локал дэгээ нь хэрэглэгчийн орон зайд үргэлж харгалзах файлын тодорхойлогчтой байдаг тул процесс хаагдах үед хаагддаг, харин глобал дэгээ нь хаагддаггүй. Дараах зураг дээр улаан загалмай ашиглан дуудагч програмыг дуусгах нь орон нутгийн болон дэлхийн дэгээний хувьд объектуудын ашиглалтын хугацаанд хэрхэн нөлөөлж байгааг харуулахыг хичээсэн.
Яагаад орон нутгийн болон дэлхийн дэгээ хооронд ялгаа байдаг вэ? Зарим төрлийн сүлжээний программуудыг хэрэглэгчийн орон зайгүйгээр ажиллуулах нь утга учиртай, жишээлбэл, DDoS хамгаалалтыг төсөөлөөд үз дээ - ачаалагч нь дүрмийг бичиж, BPF програмыг сүлжээний интерфэйстэй холбосны дараа ачаалагч өөрөө явж, өөрийгөө устгаж чадна. Нөгөө талаас, арван минутын дотор өвдөг дээрээ бичсэн алдаа засах програмыг төсөөлөөд үз дээ - энэ нь дууссаны дараа та системд хог үлдэхгүй байхыг хүсч байгаа бөгөөд орон нутгийн дэгээнүүд үүнийг баталгаажуулах болно.
Нөгөөтэйгүүр, та цөм дэх хяналтын цэгт холбогдож, олон жилийн статистик мэдээллийг цуглуулахыг хүсч байна гэж төсөөлөөд үз дээ. Энэ тохиолдолд та хэрэглэгчийн хэсгийг дуусгаж, үе үе статистик руу буцахыг хүсч байна. bpf файлын систем нь ийм боломжийг олгодог. Энэ нь зөвхөн санах ойд байдаг псевдо файлын систем бөгөөд BPF объектуудыг лавлах файлуудыг үүсгэх, улмаар нэмэгдүүлэх боломжийг олгодог. refcount
объектууд. Үүний дараа дуудагч гарч болох бөгөөд түүний үүсгэсэн объектууд амьд үлдэх болно.
BPF объектуудыг лавласан bpffs файлуудыг үүсгэхийг "pinning" гэж нэрлэдэг ("процесс нь BPF програм эсвэл газрын зургийг хавчих боломжтой" гэсэн өгүүлбэрт байгаачлан). BPF объектуудад файлын объект үүсгэх нь зөвхөн локал объектуудын ашиглалтын хугацааг уртасгах төдийгүй дэлхийн объектуудыг ашиглахад тохиромжтой - дэлхийн DDoS хамгаалах програмын жишээ рүү буцаж очиход бид ирж, статистикийг харах боломжтой байхыг хүсч байна. цагаас цагт.
BPF файлын системийг ихэвчлэн суулгасан байдаг /sys/fs/bpf
, гэхдээ үүнийг дотооддоо суулгаж болно, жишээ нь:
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
Файлын системийн нэрийг тушаалыг ашиглан үүсгэнэ BPF_OBJ_PIN
BPF системийн дуудлага. Үүнийг харуулахын тулд программыг аваад, эмхэтгэж, байршуулж, хавчуулъя bpffs
. Манай програм ямар ч ашигтай зүйл хийхгүй, бид зөвхөн кодыг танилцуулж байгаа тул та жишээг хуулбарлах боломжтой.
$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
return 0;
}
char _license[] __attribute__((section("license"), used)) = "GPL";
Энэ программыг эмхэтгэж файлын системийн локал хуулбарыг үүсгэцгээе bpffs
:
$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
Одоо хэрэглүүрийг ашиглан програмаа татаж авцгаая bpftool
болон дагалдах системийн дуудлагуудыг харна уу bpf(2)
(зарим нэг хамааралгүй мөрийг стрейн гаралтаас хассан):
$ 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
Энд бид програмыг ашиглан ачааллаа BPF_PROG_LOAD
, цөмөөс файлын тодорхойлогч хүлээн авсан 3
болон тушаалыг ашиглан BPF_OBJ_PIN
энэ файлын тодорхойлогчийг файл болгон бэхэлсэн "bpf-mountpoint/test"
. Үүний дараа ачаалагч програм bpftool
ажиллаж дууссан, гэхдээ бид үүнийг ямар ч сүлжээний интерфэйсэд хавсаргаагүй ч манай програм цөмд үлдсэн:
$ 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
Бид файлын объектыг ердийн байдлаар устгаж болно unlink(2)
Үүний дараа холбогдох програмыг устгах болно:
$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory
Объектуудыг устгаж байна
Объектуудыг устгах тухай ярихад бид програмыг дэгээнээс (үйл явдлын үүсгэгч) салгасны дараа нэг ч шинэ үйл явдал түүнийг эхлүүлэхэд түлхэц өгөхгүй гэдгийг тодруулах шаардлагатай, гэхдээ програмын одоогийн бүх тохиолдлууд ердийн дарааллаар хийгдэх болно. .
Зарим төрлийн BPF програмууд нь програмыг шууд солих боломжийг олгодог, i.e. дарааллын атомыг хангах replace = detach old program, attach new program
. Энэ тохиолдолд програмын хуучин хувилбарын бүх идэвхтэй тохиолдлууд ажлаа дуусгаж, шинэ программаас шинэ үйл явдал зохицуулагчийг үүсгэх бөгөөд энд "атоми" гэдэг нь нэг ч үйл явдлыг орхигдуулахгүй гэсэн үг юм.
Үйл явдлын эх сурвалжид хөтөлбөрүүдийг хавсаргах
Энэ нийтлэлд бид хөтөлбөрүүдийг үйл явдлын эх сурвалжтай холбох талаар тусад нь тайлбарлахгүй, учир нь үүнийг тодорхой төрлийн хөтөлбөрийн хүрээнд судлах нь зүйтэй юм. см.
bpf системийн дуудлагыг ашиглан объектуудыг удирдах
BPF програмууд
Бүх BPF объектуудыг системийн дуудлагыг ашиглан хэрэглэгчийн орон зайнаас үүсгэж, удирддаг bpf
, дараах прототиптэй байна:
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
Энд баг байна cmd
төрлийн утгуудын нэг юм enum bpf_cmd
attr
- тодорхой програмын параметрүүдийн заагч ба size
— заагчийн дагуу объектын хэмжээ, i.e. ихэвчлэн энэ sizeof(*attr)
. Цөм 5.8-д системийн дуудлага bpf
34 өөр командыг дэмждэг union bpf_attr
200 мөрийг эзэлдэг. Гэхдээ бид үүнээс айх ёсгүй, учир нь бид хэд хэдэн нийтлэлийн туршид тушаалууд болон параметрүүдтэй танилцах болно.
Багаас эхэлцгээе BPF_PROG_LOAD
, BPF програмуудыг үүсгэдэг - BPF зааврын багцыг авч цөмд ачаална. Ачаалах үед баталгаажуулагчийг ажиллуулж, дараа нь JIT хөрвүүлэгчийг ажиллуулж, амжилттай гүйцэтгэсний дараа програмын файлын тодорхойлогчийг хэрэглэгч рүү буцаана. Дараа нь түүнд юу тохиолдохыг бид өмнөх хэсгээс харсан
Одоо бид энгийн BPF програмыг ачаалах тусгай програм бичих болно, гэхдээ эхлээд бид ямар төрлийн програмыг ачаалахаа шийдэх хэрэгтэй - бид сонгох хэрэгтэй. BPF_PROG_TYPE_XDP
, энэ нь утгыг буцаана XDP_PASS
(бүх багцыг алгасах). BPF ассемблер дээр энэ нь маш энгийн харагдаж байна:
r0 = 2
exit
Бид шийдсэний дараа гэж Бид байршуулах болно, бид үүнийг хэрхэн хийхийг хэлж чадна:
#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();
}
Програмын сонирхолтой үйл явдлууд массивын тодорхойлолтоос эхэлдэг insns
- машины код дахь манай BPF програм. Энэ тохиолдолд BPF хөтөлбөрийн заавар бүрийг бүтцэд багтаасан болно bpf_insn
insns
зааврыг дагаж мөрддөг r0 = 2
, Хоёрдугаарт - exit
.
Ухрах. Цөм нь машины код бичих, цөмийн толгой файлыг ашиглахад илүү тохиромжтой макронуудыг тодорхойлдог tools/include/linux/filter.h
бид бичиж болно
struct bpf_insn insns[] = {
BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
BPF_EXIT_INSN()
};
Гэхдээ BPF програмуудыг эх кодоор бичих нь зөвхөн цөмд тест, BPF-ийн тухай нийтлэл бичихэд л шаардлагатай байдаг тул эдгээр макро байхгүй байгаа нь хөгжүүлэгчийн амьдралыг үнэхээр хүндрүүлдэггүй.
BPF программыг тодорхойлсны дараа бид цөм рүү ачаалах ажил руу шилждэг. Манай минималист параметрийн багц attr
програмын төрөл, зааврын багц, тоо, шаардлагатай лиценз, нэрийг багтаасан болно "woo"
, үүнийг бид татаж авсны дараа системээс програмаа олоход ашигладаг. Амласан ёсоор уг програмыг системийн дуудлага ашиглан системд ачаалдаг bpf
.
Хөтөлбөрийн төгсгөлд бид ачааллыг дуурайдаг хязгааргүй гогцоонд ордог. Үүнгүйгээр, системийн дуудлагын бидэнд буцаж ирсэн файлын тодорхойлогч хаагдах үед програм цөмд устах болно. bpf
, мөн бид үүнийг системд харахгүй.
За, бид туршилтанд бэлэн байна. Доорх программыг угсарч ажиллуулъя strace
Бүх зүйл зохих ёсоор ажиллаж байгаа эсэхийг шалгахын тулд:
$ 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(
Бүх зүйл сайхан байна, bpf(2)
3 бариулыг бидэнд буцааж өгөөд бид хязгааргүй давталт руу орлоо pause()
. Системээс програмаа олохыг хичээцгээе. Үүнийг хийхийн тулд бид өөр терминал руу очиж, хэрэгслийг ашиглана 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)
Систем дээр ачаалагдсан програм байгааг бид харж байна woo
Түүний дэлхийн ID нь 390 бөгөөд одоогоор хийгдэж байна simple-prog
програмыг зааж буй нээлттэй файлын тодорхойлогч байдаг (мөн хэрэв simple-prog
тэгвэл ажлаа дуусгана woo
алга болно). Хүлээгдэж буйгаар хөтөлбөр woo
BPF архитектурт хоёртын кодын 16 байт - хоёр заавар авдаг боловч эх хэлбэрээрээ (x86_64) аль хэдийн 40 байт байна. Манай хөтөлбөрийг анхны хэлбэрээр нь харцгаая.
# bpftool prog dump xlated id 390
0: (b7) r0 = 2
1: (95) exit
гэнэтийн зүйл байхгүй. Одоо 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
хувьд тийм ч үр дүнтэй биш exit(2)
, гэхдээ шударгаар хэлэхэд манай програм хэтэрхий энгийн бөгөөд энгийн бус програмуудын хувьд JIT хөрвүүлэгчийн нэмсэн оршил болон төгсгөлийн үг мэдээж хэрэг болно.
Газрын зураг
BPF програмууд нь бусад BPF програмууд болон хэрэглэгчийн орон зай дахь програмуудад хандах боломжтой бүтэцлэгдсэн санах ойн хэсгүүдийг ашиглаж болно. Эдгээр объектуудыг газрын зураг гэж нэрлэдэг бөгөөд энэ хэсэгт бид системийн дуудлагыг ашиглан тэдгээрийг хэрхэн удирдахыг харуулах болно bpf
.
Газрын зургийн боломжууд нь зөвхөн хуваалцсан санах ойд хандахад хязгаарлагдахгүй гэдгийг шууд хэлье. Жишээлбэл, BPF програмын заагч эсвэл сүлжээний интерфейсийн заагч, perf үйл явдлуудтай ажиллах газрын зураг гэх мэт тусгай зориулалтын газрын зураг байдаг. Уншигчдыг төөрөгдүүлэхгүйн тулд бид тэдний талаар энд ярихгүй. Үүнээс гадна бид синхрончлолын асуудлыг үл тоомсорлодог, учир нь энэ нь бидний жишээнүүдэд тийм ч чухал биш юм. Боломжтой газрын зургийн төрлүүдийн бүрэн жагсаалтыг эндээс олж болно <linux/bpf.h>
BPF_MAP_TYPE_HASH
.
Хэрэв та C++ хэл дээр хэш хүснэгт үүсгэвэл хэлэх болно unordered_map<int,long> woo
, энэ нь оросоор “Надад ширээ хэрэгтэй байна woo
хязгааргүй хэмжээтэй, түлхүүрүүд нь төрөлтэй int
, мөн утгууд нь төрөл юм long
" BPF хэш хүснэгтийг үүсгэхийн тулд бид хүснэгтийн хамгийн их хэмжээг зааж өгөхөөс бусад тохиолдолд ижил зүйлийг хийх хэрэгтэй бөгөөд түлхүүр, утгын төрлийг зааж өгөхийн оронд тэдгээрийн хэмжээг байтаар зааж өгөх хэрэгтэй. . Газрын зураг үүсгэхийн тулд командыг ашиглана уу BPF_MAP_CREATE
системийн дуудлага bpf
. Газрын зураг үүсгэдэг бага эсвэл бага хэмжээний програмыг авч үзье. BPF програмуудыг ачаалдаг өмнөх програмын дараа энэ нь танд энгийн мэт санагдах болно:
$ 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();
}
Энд бид параметрийн багцыг тодорхойлно attr
, үүнд бид "Надад түлхүүрүүд болон хэмжээсийн утгууд бүхий хэш хүснэгт хэрэгтэй байна sizeof(int)
, үүнд би хамгийн ихдээ дөрвөн элемент оруулах боломжтой." BPF газрын зургийг үүсгэхдээ та бусад параметрүүдийг зааж өгч болно, жишээлбэл, програмын жишээн дээрхтэй ижил аргаар бид объектын нэрийг зааж өгсөн болно. "woo"
.
Програмаа эмхэтгэж ажиллуулъя:
$ 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(
Системийн дуудлага энд байна bpf(2)
бидэнд тодорхойлогч газрын зургийн дугаарыг буцааж өгсөн 3
дараа нь програм нь хүлээгдэж байгаачлан системийн дуудлагад нэмэлт зааварчилгааг хүлээж байна pause(2)
.
Одоо програмаа арын дэвсгэр рүү илгээж эсвэл өөр терминалыг нээж, хэрэглүүрийг ашиглан объектоо харцгаая bpftool
(бид газрын зургийг нэрээр нь бусдаас ялгаж чадна):
$ sudo bpftool map
...
114: hash name woo flags 0x0
key 4B value 4B max_entries 4 memlock 4096B
...
114 тоо нь манай объектын дэлхийн ID юм. Системийн аль ч програм нь энэ ID-г ашиглан одоо байгаа газрын зургийг командыг ашиглан нээх боломжтой BPF_MAP_GET_FD_BY_ID
системийн дуудлага bpf
.
Одоо бид хэш хүснэгтээрээ тоглож болно. Түүний агуулгыг харцгаая:
$ sudo bpftool map dump id 114
Found 0 elements
Хоосон. Үүнд үнэ цэнээ оруулъя hash[1] = 1
:
$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0
Хүснэгтийг дахин харцгаая:
$ sudo bpftool map dump id 114
key: 01 00 00 00 value: 01 00 00 00
Found 1 element
Өө! Бид нэг элемент нэмж чадсан. Үүнийг хийхийн тулд бид байт түвшинд ажиллах ёстой гэдгийг анхаарна уу bptftool
хэш хүснэгтийн утгууд ямар төрлийн болохыг мэдэхгүй байна. (Энэ мэдлэгийг түүнд BTF ашиглан шилжүүлж болно, гэхдээ одоо энэ талаар илүү ихийг хэлэх болно.)
bpftool яг хэрхэн элементүүдийг уншиж, нэмдэг вэ? Бүрээсний доор харцгаая:
$ 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
Эхлээд бид командыг ашиглан газрын зургийг дэлхийн ID-аар нь нээв BPF_MAP_GET_FD_BY_ID
и bpf(2)
3-р тодорхойлогчийг бидэнд буцааж өгсөн. Цаашид командыг ашиглан BPF_MAP_GET_NEXT_KEY
Бид дамжуулснаар хүснэгтийн эхний түлхүүрийг олсон NULL
"өмнөх" түлхүүрийн заагч болгон. Хэрэв бидэнд түлхүүр байгаа бол бид хийж чадна BPF_MAP_LOOKUP_ELEM
заагч руу утгыг буцаана value
. Дараагийн алхам бол бид одоогийн түлхүүр рүү заагч дамжуулж дараагийн элементийг олохыг оролдох боловч бидний хүснэгтэд зөвхөн нэг элемент болон тушаал агуулагдана. BPF_MAP_GET_NEXT_KEY
буцаж ирдэг ENOENT
.
За, 1-р түлхүүрээр утгыг өөрчилье, бидний бизнесийн логик бүртгүүлэх шаардлагатай гэж үзье 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
Хүлээгдэж буйгаар энэ нь маш энгийн: тушаал BPF_MAP_GET_FD_BY_ID
ID болон тушаалаар манай газрын зургийг нээнэ BPF_MAP_UPDATE_ELEM
элементийг дарж бичнэ.
Тиймээс бид нэг програмаас хэш хүснэгт үүсгэсний дараа түүний агуулгыг нөгөө програмаас уншиж, бичиж болно. Хэрэв бид үүнийг командын мөрөөс хийж чадсан бол систем дээрх бусад програмууд үүнийг хийж чадна гэдгийг анхаарна уу. Хэрэглэгчийн зайнаас газрын зурагтай ажиллахын тулд дээр дурдсан тушаалуудаас гадна,
BPF_MAP_LOOKUP_ELEM
: түлхүүрээр утгыг олохBPF_MAP_UPDATE_ELEM
: шинэчлэх/утга үүсгэхBPF_MAP_DELETE_ELEM
: түлхүүрийг арилгахBPF_MAP_GET_NEXT_KEY
: дараагийн (эсвэл эхний) түлхүүрийг олBPF_MAP_GET_NEXT_ID
: одоо байгаа бүх газрын зургийг үзэх боломжийг танд олгоно, энэ нь ингэж ажилладагbpftool map
BPF_MAP_GET_FD_BY_ID
: одоо байгаа газрын зургийг дэлхийн ID-аар нь нээхBPF_MAP_LOOKUP_AND_DELETE_ELEM
: объектын утгыг атомаар шинэчилж, хуучныг нь буцаанаBPF_MAP_FREEZE
: газрын зургийг хэрэглэгчийн орон зайгаас өөрчлөх боломжгүй болгох (энэ үйлдлийг буцаах боломжгүй)BPF_MAP_LOOKUP_BATCH
,BPF_MAP_LOOKUP_AND_DELETE_BATCH
,BPF_MAP_UPDATE_BATCH
,BPF_MAP_DELETE_BATCH
: олон нийтийн үйл ажиллагаа. Жишээлбэл,BPF_MAP_LOOKUP_AND_DELETE_BATCH
- энэ бол газрын зураг дээрх бүх утгыг уншиж, дахин тохируулах цорын ганц найдвартай арга юм
Эдгээр бүх тушаалууд нь газрын зургийн бүх төрлүүдэд ажиллахгүй ч ерөнхийдөө хэрэглэгчийн орон зайгаас бусад төрлийн газрын зурагтай ажиллах нь хэш хүснэгттэй ажиллахтай яг адилхан харагддаг.
Захиалгын үүднээс хэш хүснэгтийн туршилтуудаа дуусгая. Бид дөрөв хүртэлх түлхүүрийг агуулж болох хүснэгт үүсгэснийг санаж байна уу? Өөр хэдэн элемент нэмье:
$ 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
Одоогоор маш сайн:
$ 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
Дахиад нэгийг нэмж оруулъя:
$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long
Хүлээгдэж байсанчлан бид амжилтанд хүрсэнгүй. Алдааг илүү нарийвчлан авч үзье:
$ 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 +++
Бүх зүйл сайхан байна: хүлээж байсанчлан баг BPF_MAP_UPDATE_ELEM
шинэ, тав дахь, түлхүүр үүсгэхийг оролдсон боловч гацсан E2BIG
.
Тиймээс бид BPF програмуудыг үүсгэж ачаалахаас гадна хэрэглэгчийн орон зайгаас газрын зураг үүсгэж, удирдах боломжтой. Одоо бид BPF програмын газрын зургийг хэрхэн ашиглаж болохыг харах нь логик юм. Бид энэ тухай уншихад хэцүү программуудын хэлээр машины макро кодоор ярьж болох ч үнэн хэрэгтээ BPF программуудыг хэрхэн бичиж, хадгалж байгааг харуулах цаг иржээ. libbpf
.
(Доод түвшний жишээ байхгүйд сэтгэл хангалуун бус байгаа уншигчдын хувьд: бид газрын зураг ашигладаг программуудыг нарийвчлан шинжлэх болно. libbpf
мөн зааврын түвшинд юу болдгийг танд хэлье. Сэтгэл дундуур байгаа уншигчдад зориулав маш их, бид нэмсэн
libbpf ашиглан BPF програм бичих
Машины кодыг ашиглан BPF програм бичих нь зөвхөн анх удаа л сонирхолтой байж болох бөгөөд дараа нь цатгал эхэлдэг. Энэ мөчид та анхаарлаа хандуулах хэрэгтэй llvm
, энэ нь BPF архитектурт код үүсгэх backend болон номын сантай libbpf
, энэ нь танд BPF програмын хэрэглэгчийн талыг бичих, ашиглан үүсгэсэн BPF програмын кодыг ачаалах боломжийг олгодог. llvm
/clang
.
Үнэндээ бид энэ болон дараагийн өгүүллүүдээс үзэх болно. libbpf
үүнгүйгээр маш их ажил хийдэг (эсвэл үүнтэй төстэй хэрэгсэл - iproute2
, libbcc
, libbpf-go
гэх мэт) амьдрах боломжгүй. Төслийн хамгийн чухал шинж чанаруудын нэг libbpf
нь BPF CO-RE (Нэг удаа эмхэтгэ, хаа сайгүй ажиллуул) - өөр өөр API дээр ажиллах чадвартай (жишээлбэл, цөмийн бүтэц хувилбараас өөрчлөгдөх үед) нэг цөмөөс нөгөөд зөөврийн BPF програм бичих боломжийг олгодог төсөл юм. хувилбар руу). CO-RE-тэй ажиллахын тулд таны цөмийг BTF дэмжлэгтэйгээр эмхэтгэсэн байх ёстой (бид үүнийг хэрхэн хийх талаар энэ хэсэгт тайлбарласан болно.
$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux
Энэ файл нь цөмд ашигласан бүх өгөгдлийн төрлүүдийн талаарх мэдээллийг хадгалдаг бөгөөд бидний ашиглаж буй бүх жишээнүүдэд ашиглагддаг libbpf
. Бид дараагийн нийтлэлд CO-RE-ийн талаар дэлгэрэнгүй ярих болно, гэхдээ энэ нийтлэлд - зөвхөн өөртөө цөм бүтээхэд хангалттай. CONFIG_DEBUG_INFO_BTF
.
номын сан libbpf
шууд лавлахад амьдардаг tools/lib/bpf
цөм болон түүний хөгжлийг захидлын жагсаалтаар явуулдаг [email protected]
. Гэсэн хэдий ч цөмөөс гадуур амьдардаг програмуудын хэрэгцээнд зориулж тусдаа репозиторыг хадгалдаг
Энэ хэсэгт бид хэрхэн ашиглах төслийг бий болгох талаар авч үзэх болно libbpf
, хэд хэдэн (их бага утгагүй) тестийн программ бичиж, энэ бүхэн хэрхэн ажилладаг талаар дэлгэрэнгүй дүн шинжилгээ хийцгээе. Энэ нь бидэнд BPF программууд газрын зураг, цөмийн туслахууд, BTF гэх мэттэй хэрхэн харьцаж байгааг дараах хэсгүүдэд илүү хялбар тайлбарлах боломжийг олгоно.
Ихэвчлэн төслүүдийг ашигладаг libbpf
GitHub репозиторыг git дэд модуль болгон нэмбэл бид мөн адил хийх болно:
$ 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.
Руу явж байна libbpf
маш энгийн:
$ 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
Энэ хэсэгт бидний дараагийн төлөвлөгөө дараах байдалтай байна: бид BPF програмыг бичих болно BPF_PROG_TYPE_XDP
, өмнөх жишээнтэй адил боловч C хэл дээр бид үүнийг ашиглан хөрвүүлдэг clang
, мөн цөмд ачаалах туслах програм бичнэ. Дараах хэсгүүдэд бид BPF хөтөлбөр болон туслах хөтөлбөрийн чадавхийг өргөжүүлэх болно.
Жишээ нь: libbpf ашиглан бүрэн хэмжээний програм үүсгэх
Эхлэхийн тулд бид файлыг ашигладаг /sys/kernel/btf/vmlinux
, дээр дурдсан бөгөөд толгой файл хэлбэрээр үүнтэй тэнцэх файлыг үүсгэнэ үү:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
Энэ файл нь манай цөмд байгаа бүх өгөгдлийн бүтцийг хадгалах болно, жишээ нь цөмд IPv4 толгойг ингэж тодорхойлдог:
$ 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;
};
Одоо бид BPF програмаа 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";
Хэдийгээр бидний хөтөлбөр маш энгийн байсан ч бид олон нарийн ширийн зүйлийг анхаарч үзэх хэрэгтэй. Нэгдүгээрт, бидний оруулсан эхний толгой файл бол vmlinux.h
, бид үүнийг ашиглан дөнгөж үүсгэсэн bpftool btf dump
- одоо бид цөмийн бүтэц ямар харагдахыг мэдэхийн тулд kernel-headers багцыг суулгах шаардлагагүй болсон. Дараах толгой файл нь номын сангаас бидэнд ирдэг libbpf
. Одоо бидэнд макро тодорхойлоход л хэрэгтэй SEC
, энэ нь тэмдэгтийг ELF объект файлын тохирох хэсэг рүү илгээдэг. Манай хөтөлбөр энэ хэсэгт багтсан болно xdp/simple
, налуу зураасны өмнө бид BPF програмын төрлийг тодорхойлдог - энэ нь ашигласан конвенц юм libbpf
, хэсгийн нэр дээр үндэслэн эхлүүлэх үед зөв төрлийг орлуулах болно bpf(2)
. BPF хөтөлбөр нь өөрөө юм C
- маш энгийн бөгөөд нэг мөрөөс бүрдэнэ return XDP_PASS
. Эцэст нь тусдаа хэсэг "license"
лицензийн нэрийг агуулна.
Бид програмаа llvm/clang, >= 10.0.0 хувилбар эсвэл түүнээс дээш хувилбарыг ашиглан эмхэтгэх боломжтой (хэсгийг үзнэ үү.
$ 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
Сонирхолтой шинж чанаруудын дунд бид зорилтот архитектурыг зааж өгдөг -target bpf
болон гарчиг руу хүрэх зам libbpf
, бидний саяхан суулгасан. Мөн энэ тухай мартаж болохгүй -O2
, энэ сонголтгүйгээр та ирээдүйд гэнэтийн бэлэг хүлээж магадгүй. Кодоо харцгаая, бид хүссэн програмаа бичиж чадсан уу?
$ 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
Тийм ээ, энэ ажилласан! Одоо бид програмтай хоёртын файлтай байгаа бөгөөд бид үүнийг цөмд ачаалах програм үүсгэхийг хүсч байна. Үүний тулд номын сан libbpf
бидэнд хоёр сонголтыг санал болгож байна - доод түвшний API эсвэл дээд түвшний API ашиглах. Бид BPF хөтөлбөрийг хэрхэн бичих, ачаалах, холбох талаар сурахыг хүсч байгаа тул дараагийн судалгаанд бага хүчин чармайлт гаргахыг хүсч байгаа тул бид хоёр дахь замаар явах болно.
Нэгдүгээрт, бид програмынхаа "араг яс" -ыг хоёртын хувилбараас ижил хэрэглүүрийг ашиглан үүсгэх хэрэгтэй bpftool
- BPF ертөнцийн Швейцарийн хутга (BPF-ийг бүтээгч, хөтлөгчдийн нэг Даниел Боркман нь Швейцарь хүн учраас шууд утгаар нь ойлгож болно):
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
Файлд xdp-simple.skel.h
Манай програмын хоёртын код, объектыг ачаалах, хавсаргах, устгах зэрэг удирдах функцуудыг агуулдаг. Манай энгийн тохиолдолд энэ нь хэтэрхий их юм шиг харагддаг, гэхдээ энэ нь тухайн объектын файл нь олон BPF программууд болон газрын зураг агуулсан тохиолдолд ажилладаг бөгөөд энэ аварга ELF-ийг ачаалахын тулд бид араг ясыг үүсгэж, захиалгат програмаас нэг эсвэл хоёр функцийг дуудах хэрэгтэй. бичиж байна. Одоо үргэлжлүүлье.
Хатуухан хэлэхэд манай дуудагч програм нь өчүүхэн юм:
#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);
}
энд struct xdp_simple_bpf
файлд тодорхойлсон xdp-simple.skel.h
мөн бидний объект файлыг тайлбарладаг:
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;
};
Бид доод түвшний API-ийн ул мөрийг эндээс харж болно: бүтэц struct bpf_program *simple
и struct bpf_link *simple
. Эхний бүтэц нь хэсэгт бичигдсэн манай програмыг тусгайлан тайлбарласан болно xdp/simple
, хоёр дахь нь програм нь үйл явдлын эх сурвалжтай хэрхэн холбогдож байгааг тайлбарладаг.
үйл ажиллагаа xdp_simple_bpf__open_and_load
, ELF объектыг нээж, задлан шинжилж, бүх бүтэц, дэд бүтцийг үүсгэж (програмаас гадна ELF нь өгөгдөл, зөвхөн унших өгөгдөл, дибаг хийх мэдээлэл, лиценз гэх мэт бусад хэсгүүдийг агуулдаг), дараа нь систем ашиглан цөмд ачаална. залгах bpf
, бид програмыг эмхэтгэн ажиллуулснаар шалгаж болно:
$ 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
Одоо ашиглаж байгаа програмаа харцгаая bpftool
. Түүний 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)
болон dump (бид командын товчилсон хэлбэрийг ашигладаг bpftool prog dump xlated
):
# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
0: (b7) r0 = 2
1: (95) exit
Шинэ ямар нэг зүйл! Програм нь манай C эх файлын хэсгүүдийг хэвлэсэн. Үүнийг номын сан хийсэн libbpf
, хоёртын файлаас дибаг хийх хэсгийг олж, үүнийг BTF объект болгон хөрвүүлж, үүнийг ашиглан цөмд ачааллаа. BPF_BTF_LOAD
, дараа нь програмыг тушаалаар ачаалах үед үүссэн файлын тодорхойлогчийг зааж өгнө BPG_PROG_LOAD
.
Цөмийн туслахууд
BPF програмууд нь "гадаад" функцуудыг ажиллуулж болно - цөмийн туслахууд. Эдгээр туслах функцууд нь BPF програмуудад цөмийн бүтцэд хандах, газрын зургийг удирдах, мөн "бодит ертөнц" -тэй харилцах боломжийг олгодог - perf үйл явдлуудыг үүсгэх, техник хангамжийг удирдах (жишээлбэл, пакетуудыг дахин чиглүүлэх) гэх мэт.
Жишээ нь: bpf_get_smp_processor_id
"Жишээгээр суралцах" парадигмын хүрээнд туслах функцүүдийн нэгийг авч үзье. bpf_get_smp_processor_id()
, kernel/bpf/helpers.c
. Энэ нь түүнийг дуудсан BPF програм ажиллаж байгаа процессорын дугаарыг буцаана. Гэхдээ бид түүний семантикийг тийм ч их сонирхдоггүй, учир нь түүний хэрэгжилт нь нэг мөр болдог.
BPF_CALL_0(bpf_get_smp_processor_id)
{
return smp_processor_id();
}
BPF туслах функцийн тодорхойлолт нь Линукс системийн дуудлагын тодорхойлолттой төстэй. Энд жишээ нь ямар ч аргументгүй функц тодорхойлогдсон байна. (Гурван аргумент авдаг функцийг макро ашиглан тодорхойлсон BPF_CALL_3
. Аргументуудын хамгийн их тоо нь тав байна.) Гэсэн хэдий ч энэ нь зөвхөн тодорхойлолтын эхний хэсэг юм. Хоёр дахь хэсэг нь төрлийн бүтцийг тодорхойлох явдал юм struct bpf_func_proto
, энэ нь баталгаажуулагчийн ойлгодог туслах функцийн тайлбарыг агуулсан:
const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
.func = bpf_get_smp_processor_id,
.gpl_only = false,
.ret_type = RET_INTEGER,
};
Туслах функцуудыг бүртгэж байна
Тодорхой төрлийн BPF программууд энэ функцийг ашиглахын тулд, жишээлбэл, төрлөөр нь бүртгүүлэх ёстой BPF_PROG_TYPE_XDP
цөмд функц тодорхойлогддог xdp_func_proto
, энэ нь туслах функцийн ID-аас XDP энэ функцийг дэмждэг эсэхийг тодорхойлдог. Бидний үүрэг
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;
...
}
}
Шинэ BPF програмын төрлүүд файлд "тодорхойлогдсон" include/linux/bpf_types.h
BPF_PROG_TYPE
. Энэ нь логик тодорхойлолт учраас хашилтанд тодорхойлогдсон бөгөөд Си хэлний нэр томъёонд бүхэл бүтэн бетон бүтээцийг тодорхойлох нь бусад газарт тохиолддог. Ялангуяа файлд kernel/bpf/verifier.c
файлын бүх тодорхойлолт bpf_types.h
массив бүтцийг бий болгоход ашигладаг 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
};
Өөрөөр хэлбэл, BPF програмын төрөл бүрийн хувьд тухайн төрлийн өгөгдлийн бүтэц рүү заагчийг тодорхойлсон болно struct bpf_verifier_ops
, утгыг нь эхлүүлсэн _name ## _verifier_ops
, өөрөөр хэлбэл, xdp_verifier_ops
нь xdp
. Бүтэц xdp_verifier_ops
net/core/filter.c
дараах байдлаар:
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,
};
Эндээс бид өөрсдийн мэддэг функцийг харж байна xdp_func_proto
, энэ нь сорилттой тулгарах бүрт баталгаажуулагчийг ажиллуулах болно ямар нэг төрлийн BPF програм доторх функцуудыг үзнэ үү verifier.c
Таамагласан BPF програм нь функцийг хэрхэн ашигладаг болохыг харцгаая bpf_get_smp_processor_id
. Үүнийг хийхийн тулд бид өмнөх хэсгийнхээ програмыг дараах байдлаар дахин бичнэ.
#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";
Тэмдэглэгээ bpf_get_smp_processor_id
<bpf/bpf_helper_defs.h>
номын сангууд libbpf
хэрхэн
static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;
тэр бол, bpf_get_smp_processor_id
нь функцийн заагч бөгөөд утга нь 8, энд 8 нь утга юм BPF_FUNC_get_smp_processor_id
төрөл enum bpf_fun_id
, энэ нь бидэнд файлд тодорхойлогдсон vmlinux.h
(файл bpf_helper_defs.h
цөм нь скриптээр үүсгэгддэг тул "шидэт" тоонууд зүгээр). Энэ функц нь аргумент авахгүй бөгөөд төрлийн утгыг буцаана __u32
. Бид үүнийг програмдаа ажиллуулахад, clang
зааварчилгааг үүсгэдэг BPF_CALL
"зөв төрөл" Програмаа эмхэтгээд хэсгийг харцгаая 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
Эхний мөрөнд бид зааврыг харж байна call
, параметр IMM
энэ нь 8-тай тэнцүү бөгөөд SRC_REG
- тэг. Баталгаажуулагчийн ашигладаг ABI гэрээний дагуу энэ нь туслах функцийн XNUMX дугаартай дуудлага юм. Үүнийг эхлүүлсний дараа логик нь энгийн байдаг. Бүртгэлээс утгыг буцаана r0
руу хуулсан r1
ба 2,3-р мөрөнд төрөл болгон хувиргана u32
— дээд 32 бит арилсан. 4,5,6,7-р мөрөнд бид 2-ыг буцаана (XDP_PASS
) эсвэл 1 (XDP_DROP
) 0-р мөрийн туслах функц нь тэг эсвэл тэгээс өөр утгыг буцаасан эсэхээс хамаарна.
Өөрийгөө туршиж үзье: програмыг ачаалж, гаралтыг харцгаая 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
За, баталгаажуулагч зөв цөмийн туслахыг олсон.
Жишээ нь: аргументуудыг дамжуулж, эцэст нь програмыг ажиллуулж байна!
Ажиллах түвшний бүх туслах функцууд нь анхны загвартай байдаг
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
Туслах функцүүдийн параметрүүдийг регистрүүдэд дамжуулдаг r1
-r5
, мөн утгыг бүртгэлд буцаана r0
. Таваас дээш аргумент авах функц байхгүй бөгөөд цаашид дэмжлэг үзүүлэхгүй байх төлөвтэй байна.
Шинэ цөмийн туслагч болон BPF параметрүүдийг хэрхэн дамжуулдаг талаар харцгаая. Дахин бичье xdp-simple.bpf.c
дараах байдлаар (үлдсэн мөрүүд өөрчлөгдөөгүй):
SEC("xdp/simple")
int simple(void *ctx)
{
bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
return XDP_PASS;
}
Манай программ нь ажиллаж байгаа CPU-ийн дугаарыг хэвлэдэг. Үүнийг эмхэтгээд кодыг харцгаая:
$ 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
0-7-р мөрөнд бид мөрийг бичнэ running on CPU%un
, дараа нь 8-р мөрөнд бид танил мөрийг ажиллуулдаг bpf_get_smp_processor_id
. 9-12-р мөрөнд бид туслах аргументуудыг бэлтгэдэг bpf_printk
- бүртгэл r1
, r2
, r3
. Яагаад хоёр биш гурав байгаа юм бэ? Учир нь bpf_printk
- bpf_trace_printk
, форматын мөрийн хэмжээг дамжуулах шаардлагатай.
Одоо хэд хэдэн мөр нэмж оруулъя xdp-simple.c
Ингэснээр манай програм интерфэйстэй холбогдоно lo
тэгээд үнэхээр эхэлсэн!
$ 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);
}
Энд бид функцийг ашигладаг bpf_set_link_xdp_fd
, XDP төрлийн BPF программуудыг сүлжээний интерфейстэй холбодог. Бид интерфейсийн дугаарыг хатуу кодлосон lo
, энэ нь үргэлж 1. Бид хуучин програмыг хавсаргасан бол эхлээд салгахын тулд функцийг хоёр удаа ажиллуулдаг. Одоо бидэнд сорилт хэрэггүй гэдгийг анхаарна уу pause
эсвэл хязгааргүй давталт: манай дуудагч програм гарах боловч BPF програм нь үйл явдлын эх сурвалжтай холбогдсон тул устгагдахгүй. Амжилттай татаж аваад холболт хийсний дараа програм нь ирсэн сүлжээний пакет бүрт ажиллах болно lo
.
Програмаа татаж аваад интерфейсийг харцгаая 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
Бидний татаж авсан програм нь ID 669-тэй бөгөөд интерфейс дээр бид ижил ID-г харж байна lo
. Бид хэд хэдэн багц илгээх болно 127.0.0.1
(хүсэлт + хариу):
$ ping -c1 localhost
Одоо дибаг хийх виртуал файлын агуулгыг харцгаая /sys/kernel/debug/tracing/trace_pipe
, аль нь bpf_printk
зурвасаа бичдэг:
# 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
Хоёр багц харагдсан lo
болон CPU0 дээр боловсруулсан - бидний анхны бүрэн утгагүй BPF програм ажилласан!
Үүнийг тэмдэглэх нь зүйтэй юм bpf_printk
Энэ нь дибаг хийх файлд бичдэг нь дэмий хоосон зүйл биш юм: энэ нь үйлдвэрлэлд ашиглах хамгийн амжилттай туслагч биш боловч бидний зорилго бол энгийн зүйлийг харуулах явдал байв.
BPF програмуудаас газрын зурагт хандах
Жишээ нь: BPF програмын газрын зургийг ашиглах
Өмнөх хэсгүүдэд бид хэрэглэгчийн орон зайгаас газрын зураг үүсгэх, ашиглах талаар сурсан бөгөөд одоо цөмийн хэсгийг харцгаая. Ердийнх шигээ жишээгээр эхэлцгээе. Програмаа дахин бичье xdp-simple.bpf.c
дараах байдлаар:
#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";
Хөтөлбөрийн эхэнд бид газрын зургийн тодорхойлолтыг нэмсэн woo
: Энэ нь 8 элементтэй массив бөгөөд дараах утгыг хадгалдаг u64
(С хэлэнд бид ийм массивыг тодорхойлох болно u64 woo[8]
). Хөтөлбөрт "xdp/simple"
Бид одоогийн процессорын дугаарыг хувьсагч болгон авдаг key
дараа нь туслах функцийг ашиглана уу bpf_map_lookup_element
Бид массив дахь харгалзах оруулга руу заагчийг авдаг бөгөөд бид үүнийг нэгээр нэмэгдүүлнэ. Орос хэл рүү орчуулсан: Бид ирж буй пакетуудыг ямар CPU боловсруулсан талаарх статистикийг тооцдог. Програмыг ажиллуулахыг оролдъё:
$ 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
Түүнийг холбогдсон эсэхийг шалгацгаая 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 108
$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
Одоо массивын агуулгыг харцгаая:
$ 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 }
]
Бараг бүх процессыг CPU7 дээр боловсруулсан. Энэ нь бидний хувьд чухал биш, гол зүйл бол програм ажиллаж байгаа бөгөөд бид BPF програмаас газрын зураг руу хэрхэн нэвтрэхийг ойлгодог. хелперов bpf_mp_*
Нууцлаг индекс
Тиймээс, бид BPF програмаас газрын зураг руу дуудлага хийх боломжтой
val = bpf_map_lookup_elem(&woo, &key);
туслах функц хаана харагдаж байна
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
гэхдээ бид заагч дамжуулж байна &woo
нэргүй бүтэц рүү struct { ... }
...
Хэрэв бид програмын ассемблерийг харвал үнэ цэнэ нь харагдаж байна &woo
үнэндээ тодорхойлогдоогүй (мөр 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
...
болон нүүлгэн шилжүүлэлтэд агуулагддаг:
$ 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
Гэхдээ хэрэв бид аль хэдийн ачаалагдсан програмыг харвал зөв газрын зураг (мөр 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]
...
Тиймээс бид ачаалагч програмаа эхлүүлэх үед холбоосыг хийж болно гэж дүгнэж болно &woo
номын сантай зүйлээр солигдсон libbpf
. Эхлээд бид гаралтыг харна 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
Бид үүнийг харж байна libbpf
газрын зураг үүсгэсэн woo
Тэгээд манай програмыг татаж авлаа simple
. Програмыг хэрхэн ачаалах талаар дэлгэрэнгүй харцгаая:
- залгах
xdp_simple_bpf__open_and_load
файлаасxdp-simple.skel.h
- ямар шалтгаан болдог
xdp_simple_bpf__load
файлаасxdp-simple.skel.h
- ямар шалтгаан болдог
bpf_object__load_skeleton
файлаасlibbpf/src/libbpf.c
- ямар шалтгаан болдог
bpf_object__load_xattr
ньlibbpf/src/libbpf.c
Сүүлчийн функц бусад зүйлсийн дунд дуудна bpf_object__create_maps
, одоо байгаа газрын зургийг үүсгэж эсвэл нээж, тэдгээрийг файлын тодорхойлогч болгон хувиргадаг. (Энэ бол бидний харж байгаа газар юм BPF_MAP_CREATE
гаралтанд strace
.) Дараа нь функц дуудагдана bpf_object__relocate
Бид харсан зүйлээ санаж байгаа болохоор тэр бидний сонирхлыг татдаг woo
нүүлгэн шилжүүлэх хүснэгтэд. Үүнийг судалснаар бид эцэст нь функц дотор өөрсдийгөө олдог bpf_program__relocate
, аль
case RELO_LD64:
insn[0].src_reg = BPF_PSEUDO_MAP_FD;
insn[0].imm = obj->maps[relo->map_idx].fd;
break;
Тиймээс бид зааварчилгаагаа авдаг
18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
үүн доторх эх регистрийг солих BPF_PSEUDO_MAP_FD
, мөн манай газрын зургийн файлын тодорхойлогчийн эхний IMM болон тэнцүү бол жишээлбэл, 0xdeadbeef
, үүний үр дүнд бид зааврыг хүлээн авах болно
18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll
Газрын зургийн мэдээллийг тодорхой ачаалагдсан BPF програм руу ингэж шилжүүлдэг. Энэ тохиолдолд газрын зургийг ашиглан үүсгэж болно BPF_MAP_CREATE
, мөн ID ашиглан нээнэ BPF_MAP_GET_FD_BY_ID
.
Нийт, ашиглах үед libbpf
алгоритм нь дараах байдалтай байна.
- эмхэтгэлийн явцад газрын зураг руу холбох нүүлгэн шилжүүлэх хүснэгтэд бүртгэлийг үүсгэсэн
libbpf
ELF объектын номыг нээж, ашигласан бүх газрын зургийг олж, тэдгээрийн файлын тодорхойлогчдыг үүсгэдэг- файлын тодорхойлогчдыг зааврын нэг хэсэг болгон цөмд ачаалдаг
LD64
Таны төсөөлж байгаагаар илүү олон зүйл байгаа бөгөөд бид голыг нь харах хэрэгтэй болно. Аз болоход бидэнд нэг сэжүүр байна - бид утгыг нь бичсэн BPF_PSEUDO_MAP_FD
эх сурвалжийн бүртгэлд оруулбал бид үүнийг булшлах боломжтой бөгөөд энэ нь биднийг бүх гэгээнтнүүдийн ариун дагшин руу хөтлөх болно. kernel/bpf/verifier.c
, Энд өвөрмөц нэртэй функц нь файлын тодорхойлогчийг бүтцийн хаягаар орлуулдаг 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;
(бүтэн кодыг олж болно
- Програмыг ачаалах үед шалгагч нь газрын зургийн зөв ашиглалтыг шалгаж, холбогдох бүтцийн хаягийг бичдэг
struct bpf_map
ашиглан ELF хоёртын файлыг татаж авахдаа libbpf
Өөр олон зүйл байгаа ч бид бусад нийтлэлд энэ талаар ярих болно.
Хөтөлбөр болон газрын зургийг libbpfгүйгээр ачаалж байна
Амласан ёсоороо газрын зураг ашигладаг программыг тусламжгүйгээр хэрхэн үүсгэж, ачаалах талаар мэдэхийг хүссэн уншигчдад зориулсан жишээ энд байна. libbpf
. Энэ нь таныг хамаарлыг бий болгох боломжгүй орчинд ажиллаж, бит бүрийг хадгалах, эсвэл програм бичих зэрэгт хэрэг болно. ply
Логикийг дагахад хялбар болгохын тулд бид эдгээр зорилгын үүднээс жишээгээ дахин бичих болно xdp-simple
. Энэ жишээнд авч үзсэн програмын бүрэн, бага зэрэг өргөтгөсөн кодыг эндээс олж болно
Бидний хэрэглээний логик дараах байдалтай байна.
- төрлийн газрын зураг үүсгэх
BPF_MAP_TYPE_ARRAY
командыг ашигланBPF_MAP_CREATE
, - энэ газрын зургийг ашиглах программ үүсгэх,
- програмыг интерфэйстэй холбоно уу
lo
,
хүн гэж орчуулагддаг
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);
}
энд map_create
нь системийн дуудлагын тухай эхний жишээн дээр хийсэн шиг газрын зураг үүсгэдэг bpf
- “цөм, надад 8 элементийн массив хэлбэрээр шинэ газрын зураг хийж өгнө үү __u64
мөн надад файлын тодорхойлогчийг буцааж өгөөч":
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));
}
Програмыг ачаалахад хялбар байдаг:
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));
}
Хэцүү хэсэг prog_load
Энэ нь манай BPF хөтөлбөрийн бүтцийн массив гэсэн тодорхойлолт юм struct bpf_insn insns[]
. Гэхдээ бид C хэл дээр байгаа програмыг ашиглаж байгаа тул бага зэрэг хуурч болно:
$ 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
Нийтдээ бид 14 зааврыг бүтцийн хэлбэрээр бичих хэрэгтэй struct bpf_insn
(зөвлөгөө: дээрээс нь хогийн цэгийг авч, зааврын хэсгийг дахин уншиж, нээ linux/bpf.h
linux/bpf_common.h
struct bpf_insn insns[]
ганцаараа):
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
},
};
Үүнийг өөрөө бичээгүй хүмүүст зориулсан дасгал - олоорой map_fd
.
Манай нэвтрүүлэгт бас нэг үл мэдэгдэх хэсэг үлдсэн байна - xdp_attach
. Харамсалтай нь XDP зэрэг програмуудыг системийн дуудлага ашиглан холбох боломжгүй bpf
. BPF болон XDP-г бүтээсэн хүмүүс нь Линуксийн онлайн нийгэмлэгийнх байсан бөгөөд энэ нь тэд өөрсдөдөө хамгийн танил (гэхдээ тийм биш) ашигладаг гэсэн үг юм. хэвийн хүмүүс) цөмтэй харилцах интерфейс: xdp_attach
-аас код хуулж байна libbpf
, тухайлбал, файлаас netlink.c
Netlink залгууруудын ертөнцөд тавтай морилно уу
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;
}
Бид энэ залгуураас уншдаг:
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;
}
Эцэст нь, залгуурыг нээж, файлын тодорхойлогч агуулсан тусгай мессежийг илгээдэг бидний функц энд байна:
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;
}
Тиймээс бүх зүйл туршилтанд бэлэн байна:
$ 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 +++
Манай програм холбогдсон эсэхийг харцгаая 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
Пинг илгээж, газрын зургийг харцгаая:
$ 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
Уяа, бүх зүйл ажиллаж байна. Дашрамд хэлэхэд манай газрын зураг дахин байт хэлбэрээр харагдаж байгааг анхаарна уу. Энэ нь ялгаатай байгаатай холбоотой юм libbpf
Бид төрлийн мэдээллийг (BTF) ачааллаагүй. Гэхдээ бид дараагийн удаа энэ талаар дэлгэрэнгүй ярих болно.
Хөгжлийн хэрэгслүүд
Энэ хэсэгт бид хамгийн бага BPF хөгжүүлэгчийн хэрэгслийг авч үзэх болно.
Ерөнхийдөө BPF программыг хөгжүүлэхэд танд онцгой зүйл хэрэггүй - BPF нь ямар ч зохистой түгээлтийн цөм дээр ажилладаг бөгөөд программуудыг ашиглан бүтээгдсэн байдаг. clang
, багцаас нийлүүлэх боломжтой. Гэсэн хэдий ч, BPF боловсруулагдаж байгаа тул цөм, хэрэгслүүд нь байнга өөрчлөгдөж байдаг тул 2019 оноос хойш хуучин аргуудыг ашиглан BPF програм бичихийг хүсэхгүй байгаа бол та эмхэтгэх хэрэгтэй болно.
llvm
/clang
pahole
- түүний гол цөм
bpftool
(Лавлах үүднээс энэ хэсэг болон нийтлэл дэх бүх жишээг Debian 10 дээр ажиллуулсан.)
llvm/clang
BPF нь LLVM-тэй ээлтэй бөгөөд сүүлийн үед BPF-д зориулсан программуудыг gcc ашиглан эмхэтгэж болох ч одоогийн бүх хөгжүүлэлтийг LLVM-д зориулж хийсэн. Тиймээс бид юуны түрүүнд одоогийн хувилбарыг бүтээх болно clang
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
... много времени спустя
$
Одоо бид бүх зүйл зөв хийгдсэн эсэхийг шалгаж болно:
$ ./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
(Угсрах заавар clang
надаас авсан
Бид саяхан бүтээсэн програмуудаа суулгахгүй, харин зүгээр л нэмнэ үү PATH
Жишээ нь:
export PATH="`pwd`/bin:$PATH"
(Үүнийг нэмж болно .bashrc
эсвэл тусдаа файл руу. Би хувьдаа иймэрхүү зүйлсийг нэмдэг ~/bin/activate-llvm.sh
шаардлагатай үед би үүнийг хийдэг . activate-llvm.sh
.)
Пахоле ба BTF
Хэрэгсэл pahole
BTF форматаар дибаг хийх мэдээллийг бий болгохын тулд цөмийг бүтээхэд ашигладаг. Бид энэ нийтлэлд BTF технологийн нарийн ширийн зүйлийн талаар дэлгэрэнгүй ярихгүй бөгөөд энэ нь тохиромжтой бөгөөд бид үүнийг ашиглахыг хүсч байна. Тиймээс хэрэв та цөмөө бүтээх гэж байгаа бол эхлээд бүтээгээрэй pahole
(байхгүй pahole
сонголтоор цөмийг бүтээх боломжгүй болно 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
BPF-тэй туршилт хийх цөмүүд
BPF-ийн боломжуудыг судлахдаа би өөрийн цөмийг цуглуулахыг хүсч байна. Ерөнхийдөө энэ нь шаардлагагүй, учир нь та түгээлтийн цөм дээр BPF програмуудыг эмхэтгэж, ачаалах боломжтой боловч өөрийн цөмтэй байх нь хамгийн сүүлийн үеийн BPF функцуудыг ашиглах боломжийг олгодог бөгөөд энэ нь хамгийн сайндаа хэдэн сарын дараа таны түгээлтэд гарч ирэх болно. , эсвэл зарим дибаг хийх хэрэгслүүдийн нэгэн адил ойрын ирээдүйд огт багцлагдахгүй. Түүнчлэн, өөрийн үндсэн хэсэг нь кодыг туршиж үзэх нь чухал юм.
Цөмийг бүтээхийн тулд та нэгдүгээрт, цөм өөрөө, хоёрдугаарт, цөмийн тохиргооны файл хэрэгтэй. BPF-ийг туршихын тулд бид ердийн аргыг ашиглаж болно net
net-next
bpf
bpf-next
*-next
Цөмүүд нь жагсаасан хүмүүсийн хамгийн тогтворгүй нь юм).
Цөмийн тохиргооны файлуудыг хэрхэн удирдах талаар ярих нь энэ нийтлэлийн хамрах хүрээнээс гадуур юм - та үүнийг хэрхэн хийхээ аль хэдийн мэддэг гэж үзэж байна, эсвэл
Дээрх цөмүүдийн аль нэгийг татаж авна уу:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next
Хамгийн бага ажилладаг цөмийн тохиргоог бий болгох:
$ cp /boot/config-`uname -r` .config
$ make localmodconfig
Файл дахь BPF сонголтыг идэвхжүүлнэ үү .config
өөрийн сонголтоор (хамгийн их магадлалтай CONFIG_BPF
systemd үүнийг ашигладаг тул аль хэдийн идэвхжсэн байх болно). Энэ нийтлэлд ашигласан цөмийн сонголтуудын жагсаалт энд байна:
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
Дараа нь бид модулиуд болон цөмийг хялбархан угсарч суулгаж болно (дашрамд хэлэхэд та шинээр угсарсан цөмийг ашиглан цөмийг угсарч болно. clang
нэмэх замаар CC=clang
):
$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install
шинэ цөмөөр дахин ачаална уу (би үүнд ашигладаг kexec
багцаас 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
Нийтлэлд хамгийн түгээмэл хэрэглэгддэг хэрэгсэл бол хэрэгсэл байх болно bpftool
, Линуксийн цөмийн нэг хэсэг болгон нийлүүлсэн. Үүнийг BPF хөгжүүлэгчид BPF хөгжүүлэгчдэд зориулж бичиж, хөтөлдөг бөгөөд бүх төрлийн BPF объектуудыг удирдахад ашиглаж болно - програм ачаалах, газрын зураг үүсгэх, засварлах, BPF экосистемийн амьдралыг судлах гэх мэт. Хүний хуудасны эх код хэлбэрээр баримт бичгийг олж болно
Үүнийг бичиж байх үед bpftool
зөвхөн RHEL, Fedora болон Ubuntu-д бэлэн ирдэг (жишээ нь: bpftool
Debian дээр). Гэхдээ хэрэв та цөмөө аль хэдийн бүтээсэн бол бүтээгээрэй bpftool
бялуу шиг хялбар:
$ 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 ]
$
(энд ${linux}
- энэ бол таны цөмийн лавлах.) Эдгээр командуудыг гүйцэтгэсний дараа bpftool
лавлахад цуглуулна ${linux}/tools/bpf/bpftool
мөн үүнийг замд нэмж болно (юуны өмнө хэрэглэгч root
) эсвэл зүгээр л хуулна уу /usr/local/sbin
.
Цуглуулна bpftool
сүүлийнхийг ашиглах нь дээр clang
, дээр дурдсанчлан угсарч, зөв угсарсан эсэхийг шалгана уу - жишээ нь, тушаалыг ашиглан
$ 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
...
Энэ нь таны цөмд аль BPF функц идэвхжсэнийг харуулах болно.
Дашрамд хэлэхэд өмнөх тушаалыг дараах байдлаар ажиллуулж болно
# bpftool f p k
Үүнийг багцын хэрэгслүүдтэй адилтгаж хийдэг iproute2
, бид хаана, жишээ нь хэлж болно ip a s eth0
оронд нь ip addr show dev eth0
.
дүгнэлт
BPF нь бөөсийг гутлах боломжийг олгодог бөгөөд үндсэн хэсгийн үйл ажиллагааг үр дүнтэй хэмжиж, шууд өөрчлөх боломжтой. Энэхүү систем нь UNIX-ийн шилдэг уламжлалуудын дагуу маш амжилттай болсон: цөмийг дахин програмчлах боломжийг олгодог энгийн механизм нь асар олон тооны хүмүүс, байгууллагуудад туршилт хийх боломжийг олгосон. Туршилтууд, түүнчлэн BPF-ийн дэд бүтцийг хөгжүүлэх ажил дуусаагүй байгаа ч систем нь найдвартай, хамгийн чухал нь үр дүнтэй бизнесийн логикийг бий болгох боломжийг олгодог тогтвортой ABI-тэй болсон.
Миний бодлоор энэ технологи нь нэг талаараа боломжтой учраас маш их алдартай болсон гэдгийг тэмдэглэхийг хүсч байна тоглох (машины архитектурыг нэг оройд их бага ойлгох боломжтой), нөгөө талаас, гарч ирэхээсээ өмнө шийдэж чадаагүй (сайхан) асуудлыг шийдвэрлэх. Эдгээр хоёр бүрэлдэхүүн хэсэг нь хүмүүсийг туршиж, мөрөөддөг бөгөөд энэ нь улам бүр шинэлэг шийдлүүдийг бий болгоход хүргэдэг.
Энэ нийтлэл нь тийм ч богино биш боловч зөвхөн BPF-ийн ертөнцийн танилцуулга бөгөөд архитектурын "дэвшилтэт" шинж чанарууд болон чухал хэсгүүдийг тайлбарлаагүй болно. Цаашид хийх төлөвлөгөө нь дараах байдалтай байна: дараагийн нийтлэл нь BPF програмын төрлүүдийн тойм байх болно (5.8 цөмд дэмжигдсэн 30 төрлийн програм байдаг), дараа нь бид цөмийн хайгч програмуудыг ашиглан бодит BPF програмуудыг хэрхэн бичих талаар авч үзэх болно. Жишээлбэл, BPF-ийн архитектурын талаар илүү гүнзгийрүүлсэн сургалт, дараа нь BPF сүлжээ, аюулгүй байдлын хэрэглээний жишээнүүдийг үзэх цаг болжээ.
Энэ цувралын өмнөх нийтлэлүүд
Холбоосууд
-
BPF болон XDP лавлах гарын авлага - cilium-ийн BPF-ийн талаарх баримт бичиг, эсвэл BPF-ийг бүтээгч, хөтлөгчдийн нэг Даниел Боркманаас авсан. Энэ бол Даниел яг юуны тухай бичиж байгаагаа мэддэг бөгөөд энд ямар ч алдаа байхгүй гэдгээрээ бусдаас ялгаатай нь анхны ноцтой тайлбаруудын нэг юм. Ялангуяа энэ баримт бичиг нь сайн мэддэг хэрэгслийг ашиглан XDP ба TC төрлийн BPF програмуудтай хэрхэн ажиллах талаар тайлбарласан болно.ip
багцаасiproute2
. -
Documentation/networking/filter.txt - сонгодог, дараа нь өргөтгөсөн BPF-ийн баримт бичиг бүхий эх файл. Хэрэв та ассемблер хэл, техникийн архитектурын нарийн ширийн зүйлийг судлахыг хүсч байвал сайн уншаарай. -
Фэйсбүүкээс BPF-ийн тухай блог . Энэ нь ховор шинэчлэгддэг, гэхдээ Алексей Старовойтов (eBPF-ийн зохиогч) болон Андрей Накрыйко нар (засварч) энд бичсэнчлэн тохиромжтой.libbpf
). -
bpftool-ийн нууцууд . bpftool ашиглах жишээ, нууцыг агуулсан Quentin Monnet-ийн хөгжилтэй твиттер хуудас. -
BPF руу шумбах: унших материалын жагсаалт . Quentin Monnet-ийн BPF баримт бичгийн холбоосуудын аварга том (мөн хадгалагдсаар байгаа) жагсаалт.
Эх сурвалж: www.habr.com