Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

Эхэндээ технологи байсан бөгөөд үүнийг BPF гэж нэрлэдэг байсан. Бид түүн рүү харлаа өмнөх, Энэ цувралын Хуучин Гэрээний нийтлэл. 2013 онд Алексей Старовойтов, Даниел Боркман нарын хүчин чармайлтаар орчин үеийн 64 битийн машинуудад оновчтой болгосон сайжруулсан хувилбарыг боловсруулж Линуксийн цөмд оруулсан. Энэхүү шинэ технологийг товчхондоо Internal BPF гэж нэрлээд дараа нь Extended BPF гэж нэрлэсэн бөгөөд одоо хэдэн жилийн дараа хүн бүр үүнийг зүгээр л BPF гэж нэрлэдэг.

Товчхондоо, BPF нь Линуксийн цөмийн орон зайд хэрэглэгчийн оруулсан кодыг дур мэдэн ажиллуулах боломжийг олгодог бөгөөд шинэ архитектур нь маш амжилттай болсон тул түүний бүх програмыг тайлбарлахын тулд бидэнд хэдэн арван нийтлэл хэрэгтэй болно. (Доорх гүйцэтгэлийн кодоос харж байгаачлан хөгжүүлэгчид сайн хийгээгүй цорын ганц зүйл бол зохистой лого бүтээх явдал байв.)

Энэ нийтлэлд BPF виртуал машины бүтэц, BPF-тэй ажиллах цөмийн интерфэйсүүд, хөгжүүлэлтийн хэрэгслүүд, түүнчлэн одоо байгаа боломжуудын талаар товч, маш товч тоймыг тайлбарласан болно. BPF-ийн практик хэрэглээг илүү гүнзгий судлахын тулд ирээдүйд бидэнд хэрэгтэй бүх зүйл.
Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

Өгүүллийн хураангуй

BPF архитектурын танилцуулга. Эхлээд бид BPF-ийн архитектурыг шувууны нүдээр харж, үндсэн бүрэлдэхүүн хэсгүүдийг тоймлох болно.

BPF виртуал машины бүртгэл ба командын систем. Архитектурын талаархи ойлголттой болсон тул бид BPF виртуал машины бүтцийг тайлбарлах болно.

BPF объектуудын амьдралын мөчлөг, bpffs файлын систем. Энэ хэсэгт бид BPF объектуудын амьдралын мөчлөгийг нарийвчлан авч үзэх болно - програмууд болон газрын зураг.

bpf системийн дуудлагыг ашиглан объектуудыг удирдах. Системийн талаар тодорхой ойлголттой болсны дараа бид эцэст нь системийн тусгай дуудлагыг ашиглан хэрэглэгчийн орон зайнаас объектыг хэрхэн үүсгэх, удирдах талаар авч үзэх болно. bpf(2).

Пишем программы BPF с помощью libbpf. Мэдээжийн хэрэг та системийн дуудлага ашиглан програм бичиж болно. Гэхдээ хэцүү байна. Илүү бодитой хувилбарын хувьд цөмийн программистууд номын сан байгуулжээ libbpf. Бид дараагийн жишээнүүдэд ашиглах үндсэн BPF програмын араг ясыг үүсгэх болно.

Цөмийн туслахууд. Энд бид BPF програмууд цөмийн туслах функцүүдэд хэрхэн хандаж болохыг олж мэдэх болно - газрын зургийн хамт шинэ BPF-ийн чадавхийг сонгодог хувилбартай харьцуулахад үндсээр нь өргөжүүлдэг хэрэгсэл юм.

BPF програмуудаас газрын зураг руу нэвтрэх. Энэ үед бид газрын зураг ашигладаг программуудыг хэрхэн бүтээх талаар ойлгох хангалттай мэдлэгтэй болно. Мөн агуу, хүчирхэг шалгагчийг хурдан харцгаая.

Хөгжлийн хэрэгслүүд. Туршилт хийхэд шаардлагатай хэрэгслүүд болон цөмийг хэрхэн угсрах тухай тусламжийн хэсэг.

Дүгнэлт. Өгүүллийн төгсгөлд өдий хүртэл уншсан хүмүүс урам зориг өгөх үгс, юу болох талаар товч тайлбарыг дараагийн нийтлэлүүдээс олох болно. Үргэлжлэлийг хүлээх хүсэлгүй, чадваргүй хүмүүст зориулж бие даан суралцах хэд хэдэн холбоосыг бид жагсаах болно.

BPF Архитектурын танилцуулга

BPF архитектурыг авч үзэхээсээ өмнө бид сүүлчийн удаа (өө) рүү хандана сонгодог BPF, энэ нь RISC машинууд бий болсонтой холбогдуулан боловсруулсан бөгөөд үр ашигтай пакет шүүлтүүрийн асуудлыг шийдсэн. Архитектур нь маш амжилттай болсон тул XNUMX-ээд онд Беркли UNIX-д төрж, одоо байгаа ихэнх үйлдлийн системд шилжүүлж, галзуу хорин хэдэн онд амьд үлдсэн бөгөөд одоо ч шинэ програмуудыг хайж байна.

Шинэ BPF нь 64 битийн машинууд, үүлэн үйлчилгээ, SDN үүсгэх хэрэгслүүдийн хэрэгцээ нэмэгдэж байгаатай холбогдуулан боловсруулсан болно.Sпрограм хангамжdтодорхойлсон nажиллах). Цөмийн сүлжээний инженерүүдийн сонгодог BPF-ийг орлуулах зорилгоор бүтээсэн шинэ BPF нь зургаан сарын дараа Линукс системийг мөрдөхөд хэцүү программуудыг олсон бөгөөд одоо гарч ирснээс хойш зургаан жилийн дараа бидэнд дараагийн нийтлэл хэрэгтэй болно. янз бүрийн төрлийн програмуудыг жагсаана.

Хөгжилтэй зургууд

Үндсэндээ BPF нь хамгаалагдсан хязгаарлагдмал орчинд ажилладаг виртуал машин бөгөөд цөмийн орон зайд аюулгүй байдлыг алдагдуулахгүйгээр "дурын" кодыг ажиллуулах боломжийг олгодог. BPF программууд нь хэрэглэгчийн орон зайд бүтээгдэж, цөмд ачаалагдаж, зарим үйл явдлын эх сурвалжтай холбогддог. Үйл явдал нь жишээлбэл, багцыг сүлжээний интерфейс рүү хүргэх, цөмийн зарим функцийг эхлүүлэх гэх мэт байж болно. Багцын хувьд BPF програм нь багцын өгөгдөл, мета өгөгдөлд хандах боломжтой (хөтөлбөрийн төрлөөс хамааран унших, бичих боломжтой), цөмийн функцийг ажиллуулах тохиолдолд аргументууд. функц, үүнд цөмийн санах ойн заагч гэх мэт.

Энэ үйл явцыг нарийвчлан авч үзье. Эхлэхийн тулд ассемблер дээр бичигдсэн программуудыг сонгодог BPF-ээс эхний ялгааны талаар ярилцъя. Шинэ хувилбарт программыг өндөр түвшний хэлээр, юуны түрүүнд мэдээж C хэл дээр бичих боломжтой байхаар архитектурыг өргөжүүлсэн. Үүний тулд llvm-д зориулсан backend боловсруулсан бөгөөд энэ нь BPF архитектурт байт код үүсгэх боломжийг олгодог.

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

BPF архитектур нь зарим талаараа орчин үеийн машинууд дээр үр дүнтэй ажиллахаар зохион бүтээгдсэн. Үүнийг практикт хэрэгжүүлэхийн тулд цөмд ачаалагдсаны дараа BPF байт кодыг JIT хөрвүүлэгч хэмээх бүрэлдэхүүн хэсгийг ашиглан эх код руу хөрвүүлдэг.Just In Time). Дараа нь, хэрэв та санаж байгаа бол сонгодог BPF-д програмыг цөмд ачаалж, үйл явдлын эх үүсвэрт атомаар хавсаргасан - нэг системийн дуудлагын хүрээнд. Шинэ архитектурт энэ нь хоёр үе шаттайгаар явагддаг - эхлээд кодыг системийн дуудлага ашиглан цөмд ачаалдаг. bpf(2)дараа нь програмын төрлөөс хамааран өөр өөр механизмаар дамжуулан програм нь үйл явдлын эх сурвалжтай холбогддог.

Энд уншигчид асуулт гарч ирж магадгүй юм: боломжтой юу? Ийм кодын гүйцэтгэлийн аюулгүй байдал хэрхэн баталгаажсан бэ? Гүйцэтгэлийн аюулгүй байдлыг баталгаажуулагч гэж нэрлэгддэг BPF програмуудыг ачаалах үе шатаар баталгаажуулдаг (англи хэл дээр энэ үе шатыг шалгагч гэж нэрлэдэг бөгөөд би англи үгийг үргэлжлүүлэн ашиглах болно):

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн 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-ийг цөмийг өргөжүүлэх бүх нийтийн хэрэгсэл болгодог бөгөөд энэ нь практик дээр батлагдсан: 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 (XDP-ийн хувьд) эсвэл бүтэц struct __sk_buff (өөр өөр сүлжээний програмуудын хувьд) эсвэл бүтэц struct pt_regs (янз бүрийн төрлийн мөрдөх програмын хувьд) гэх мэт.

Тиймээс бид регистр, цөмийн туслах, стек, контекст заагч, газрын зураг хэлбэрээр хуваалцсан санах ойтой болсон. Энэ бүхэн аялалд зайлшгүй шаардлагатай биш, гэхдээ ...

Тодорхойлолтыг үргэлжлүүлж, эдгээр объектуудтай ажиллах командын системийн талаар ярилцъя. Бүгд (Бараг бүх) BPF заавар нь тогтмол 64 бит хэмжээтэй байна. Хэрэв та 64 битийн Big Endian машин дээрх нэг зааврыг харвал харах болно

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

энд Code - энэ бол зааврын кодчилол, Dst/Src хүлээн авагч ба эх сурвалжийн кодчилолууд нь тус тус Off - 16 битийн гарын үсэгтэй догол, ба Imm нь зарим зааварт хэрэглэгддэг 32 битийн тэмдэгт бүхэл тоо юм (cBPF тогтмол K-тэй төстэй). Кодлох Code хоёр төрлийн нэг байна:

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

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 зааврын тоон кодыг тодорхойлдог. Архитектурыг бие даан судлах ба/эсвэл хоёртын файлуудыг задлан шинжлэхдээ нарийн төвөгтэй байдлын дарааллаар эрэмбэлсэн дараах эх сурвалжаас семантикийг олж болно. Албан бус eBPF үзүүлэлт, BPF болон XDP лавлах гарын авлага, Зааварчилгааны багц, Documentation/networking/filter.txt Мэдээжийн хэрэг, Линуксийн эх кодонд - verifier, JIT, BPF орчуулагч.

Жишээ нь: толгойдоо 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 Байна уу BPF_MOV. Энэ нь очих газрын бүртгэлд утга оноодог. Хэрэв бит тохируулагдсан бол s (эх сурвалж), дараа нь утгыг эх регистрээс авах бөгөөд хэрэв бидний тохиолдлынх шиг үүнийг тохируулаагүй бол утгыг талбараас авна. Imm. Тиймээс эхний болон гурав дахь зааварт бид үйл ажиллагааг гүйцэтгэдэг r0 = Imm. Цаашилбал, JMP анги 1 үйл ажиллагаа юм BPF_JEQ (тэнцүү бол үсрэх). Манай тохиолдолд, бит оноос хойш 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 объектууд - программууд болон газрын зураг нь командуудыг ашиглан хэрэглэгчийн орон зайнаас бүтээгддэг BPF_PROG_LOAD и BPF_MAP_CREATE системийн дуудлага bpf(2), энэ нь яг яаж болдог талаар бид дараагийн хэсэгт ярих болно. Энэ нь цөмийн өгөгдлийн бүтцийг бий болгодог бөгөөд тэдгээр нь тус бүрийн хувьд refcount (лавлагааны тоо) нэгээр тохируулагдсан бөгөөд тухайн объект руу чиглэсэн файлын тодорхойлогчийг хэрэглэгч рүү буцаана. Бариулыг хаасны дараа refcount объект нэгээр багасч, тэг болоход объект устгагдана.

Хэрэв програм нь газрын зураг ашигладаг бол refcount Програмыг ачаалсны дараа эдгээр газрын зургууд нэгээр нэмэгддэг, i.e. Тэдний файлын тодорхойлогч нь хэрэглэгчийн процессоос хаагдах боломжтой бөгөөд хэвээр байна refcount тэг болохгүй:

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

Програмыг амжилттай ачаалсны дараа бид үүнийг ихэвчлэн ямар нэгэн үйл явдал үүсгэгчтэй холбодог. Жишээлбэл, бид үүнийг ирж буй пакетуудыг боловсруулах эсвэл заримтай нь холбохын тулд сүлжээний интерфейс дээр байрлуулж болно tracepoint цөмд. Энэ үед лавлагааны тоолуур мөн нэгээр нэмэгдэх ба бид дуудагч програм дахь файлын тодорхойлогчийг хаах боломжтой болно.

Хэрэв бид одоо ачаалагчийг унтраавал яах вэ? Энэ нь үйл явдлын үүсгэгчийн (дэгээ) төрлөөс хамаарна. Ачигч дууссаны дараа бүх сүлжээний дэгээнүүд байх болно, эдгээр нь дэлхийн дэгээ гэж нэрлэгддэг. Жишээлбэл, мөрдөх програмууд нь тэдгээрийг үүсгэсэн процесс дууссаны дараа гарах болно (тиймээс "орон нутгийн процесс руу" орон нутгийн гэж нэрлэдэг). Техникийн хувьд локал дэгээ нь хэрэглэгчийн орон зайд үргэлж харгалзах файлын тодорхойлогчтой байдаг тул процесс хаагдах үед хаагддаг, харин глобал дэгээ нь хаагддаггүй. Дараах зураг дээр улаан загалмай ашиглан дуудагч програмыг дуусгах нь орон нутгийн болон дэлхийн дэгээний хувьд объектуудын ашиглалтын хугацаанд хэрхэн нөлөөлж байгааг харуулахыг хичээсэн.

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

Яагаад орон нутгийн болон дэлхийн дэгээ хооронд ялгаа байдаг вэ? Зарим төрлийн сүлжээний программуудыг хэрэглэгчийн орон зайгүйгээр ажиллуулах нь утга учиртай, жишээлбэл, DDoS хамгаалалтыг төсөөлөөд үз дээ - ачаалагч нь дүрмийг бичиж, BPF програмыг сүлжээний интерфэйстэй холбосны дараа ачаалагч өөрөө явж, өөрийгөө устгаж чадна. Нөгөө талаас, арван минутын дотор өвдөг дээрээ бичсэн алдаа засах програмыг төсөөлөөд үз дээ - энэ нь дууссаны дараа та системд хог үлдэхгүй байхыг хүсч байгаа бөгөөд орон нутгийн дэгээнүүд үүнийг баталгаажуулах болно.

Нөгөөтэйгүүр, та цөм дэх хяналтын цэгт холбогдож, олон жилийн статистик мэдээллийг цуглуулахыг хүсч байна гэж төсөөлөөд үз дээ. Энэ тохиолдолд та хэрэглэгчийн хэсгийг дуусгаж, үе үе статистик руу буцахыг хүсч байна. bpf файлын систем нь ийм боломжийг олгодог. Энэ нь зөвхөн санах ойд байдаг псевдо файлын систем бөгөөд BPF объектуудыг лавлах файлуудыг үүсгэх, улмаар нэмэгдүүлэх боломжийг олгодог. refcount объектууд. Үүний дараа дуудагч гарч болох бөгөөд түүний үүсгэсэн объектууд амьд үлдэх болно.

Бяцхан хүүхдүүдэд зориулсан BPF, нэгдүгээр хэсэг: өргөтгөсөн BPF

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. Энэ тохиолдолд програмын хуучин хувилбарын бүх идэвхтэй тохиолдлууд ажлаа дуусгаж, шинэ программаас шинэ үйл явдал зохицуулагчийг үүсгэх бөгөөд энд "атоми" гэдэг нь нэг ч үйл явдлыг орхигдуулахгүй гэсэн үг юм.

Үйл явдлын эх сурвалжид хөтөлбөрүүдийг хавсаргах

Энэ нийтлэлд бид хөтөлбөрүүдийг үйл явдлын эх сурвалжтай холбох талаар тусад нь тайлбарлахгүй, учир нь үүнийг тодорхой төрлийн хөтөлбөрийн хүрээнд судлах нь зүйтэй юм. см. жишээ нь доор, бид XDP зэрэг програмууд хэрхэн холбогдож байгааг харуулав.

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 програмыг ачаалах тусгай програм бичих болно, гэхдээ эхлээд бид ямар төрлийн програмыг ачаалахаа шийдэх хэрэгтэй - бид сонгох хэрэгтэй. төрөл мөн энэ төрлийн хүрээнд шалгах шалгалтыг давах програм бичнэ. Гэсэн хэдий ч, үйл явцыг хүндрүүлэхгүйн тулд энд бэлэн шийдэл байна: бид ийм програмыг авах болно 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 дэмжлэгтэйгээр эмхэтгэсэн байх ёстой (бид үүнийг хэрхэн хийх талаар энэ хэсэгт тайлбарласан болно. Хөгжлийн хэрэгслүүд. Та өөрийн цөмийг 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]. Гэсэн хэдий ч цөмөөс гадуур амьдардаг програмуудын хэрэгцээнд зориулж тусдаа репозиторыг хадгалдаг https://github.com/libbpf/libbpf Цөмийн номын сан нь байгаагаар нь унших хандалтад зориулж толин тусгалтай байдаг.

Энэ хэсэгт бид хэрхэн ашиглах төслийг бий болгох талаар авч үзэх болно 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, энэ нь BPF хоёртын кодыг шууд үүсгэдэг.

Логикийг дагахад хялбар болгохын тулд бид эдгээр зорилгын үүднээс жишээгээ дахин бичих болно 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-г бүтээсэн хүмүүс нь Линуксийн онлайн нийгэмлэгийнх байсан бөгөөд энэ нь тэд өөрсдөдөө хамгийн танил (гэхдээ тийм биш) ашигладаг гэсэн үг юм. хэвийн хүмүүс) цөмтэй харилцах интерфейс: netlink залгуурууд, мөн үзнэ үү RFC3549. Хэрэгжүүлэх хамгийн энгийн арга 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 надаас авсан bpf_devel_QA.)

Бид саяхан бүтээсэн програмуудаа суулгахгүй, харин зүгээр л нэмнэ үү 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-ийг туршихын тулд бид ердийн аргыг ашиглаж болно ваниль цөм эсвэл хөгжүүлэлтийн цөмүүдийн нэг. Түүхийн хувьд BPF нь Линукс сүлжээний нийгэмлэгийн хүрээнд явагддаг тул бүх өөрчлөлтүүд эрт орой хэзээ нэгэн цагт Линуксийн сүлжээний засварлагч Дэвид Миллерээр дамждаг. Тэдгээрийн шинж чанараас хамааран засварлах эсвэл шинэ боломжууд - сүлжээний өөрчлөлтүүд нь хоёр цөмийн аль нэгэнд багтдаг. net буюу net-next. BPF-ийн өөрчлөлтийг хооронд нь ижил аргаар хуваарилдаг bpf и bpf-next, дараа нь net болон net-next-д нэгтгэгдэнэ. Дэлгэрэнгүй мэдээллийг үзнэ үү bpf_devel_QA и netdev-FAQ. Тиймээс өөрийн амт болон туршиж буй системийн тогтвортой байдлын хэрэгцээнд үндэслэн цөмийг сонгоорой (*-next Цөмүүд нь жагсаасан хүмүүсийн хамгийн тогтворгүй нь юм).

Цөмийн тохиргооны файлуудыг хэрхэн удирдах талаар ярих нь энэ нийтлэлийн хамрах хүрээнээс гадуур юм - та үүнийг хэрхэн хийхээ аль хэдийн мэддэг гэж үзэж байна, эсвэл сурахад бэлэн өөрийнхөөрөө. Гэсэн хэдий ч дараах зааварчилгаа нь танд BPF-ийг идэвхжүүлсэн системийг ажиллуулахад хангалттай эсвэл бага байх ёстой.

Дээрх цөмүүдийн аль нэгийг татаж авна уу:

$ 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 сүлжээ, аюулгүй байдлын хэрэглээний жишээнүүдийг үзэх цаг болжээ.

Энэ цувралын өмнөх нийтлэлүүд

  1. Бяцхан хүүхдүүдэд зориулсан BPF, тэг хэсэг: сонгодог BPF

Холбоосууд

  1. BPF болон XDP лавлах гарын авлага - cilium-ийн BPF-ийн талаарх баримт бичиг, эсвэл BPF-ийг бүтээгч, хөтлөгчдийн нэг Даниел Боркманаас авсан. Энэ бол Даниел яг юуны тухай бичиж байгаагаа мэддэг бөгөөд энд ямар ч алдаа байхгүй гэдгээрээ бусдаас ялгаатай нь анхны ноцтой тайлбаруудын нэг юм. Ялангуяа энэ баримт бичиг нь сайн мэддэг хэрэгслийг ашиглан XDP ба TC төрлийн BPF програмуудтай хэрхэн ажиллах талаар тайлбарласан болно. ip багцаас iproute2.

  2. Documentation/networking/filter.txt - сонгодог, дараа нь өргөтгөсөн BPF-ийн баримт бичиг бүхий эх файл. Хэрэв та ассемблер хэл, техникийн архитектурын нарийн ширийн зүйлийг судлахыг хүсч байвал сайн уншаарай.

  3. Фэйсбүүкээс BPF-ийн тухай блог. Энэ нь ховор шинэчлэгддэг, гэхдээ Алексей Старовойтов (eBPF-ийн зохиогч) болон Андрей Накрыйко нар (засварч) энд бичсэнчлэн тохиромжтой. libbpf).

  4. bpftool-ийн нууцууд. bpftool ашиглах жишээ, нууцыг агуулсан Quentin Monnet-ийн хөгжилтэй твиттер хуудас.

  5. BPF руу шумбах: унших материалын жагсаалт. Quentin Monnet-ийн BPF баримт бичгийн холбоосуудын аварга том (мөн хадгалагдсаар байгаа) жагсаалт.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх