ProHoster > Блог > басқарма > Кішкентайларға арналған BPF, бірінші бөлім: ұзартылған BPF
Кішкентайларға арналған BPF, бірінші бөлім: ұзартылған BPF
Басында технология болды және ол BPF деп аталды. Біз оған қарадық алдыңғы, Осы сериядағы Ескі өсиет мақаласы. 2013 жылы Алексей Старовойтов пен Даниэль Боркманның күшімен оның жетілдірілген нұсқасы қазіргі заманғы 64 разрядты машиналар үшін оңтайландырылды және Linux ядросына енгізілді. Бұл жаңа технология қысқаша Ішкі BPF деп аталды, содан кейін кеңейтілген BPF деп өзгертілді, енді бірнеше жылдан кейін барлығы оны жай BPF деп атайды.
Шамамен айтқанда, BPF сізге Linux ядросының кеңістігінде ерікті пайдаланушы беретін кодты іске қосуға мүмкіндік береді және жаңа архитектураның сәтті болғаны сонша, оның барлық қосымшаларын сипаттау үшін бізге тағы ондаған мақала қажет болады. (Төмендегі өнімділік кодында көріп отырғаныңыздай, әзірлеушілер жақсы істемеген жалғыз нәрсе - лайықты логотип жасау болды.)
Бұл мақалада BPF виртуалды машинасының құрылымы, BPF-мен жұмыс істеуге арналған ядро интерфейстері, әзірлеу құралдары, сондай-ақ бар мүмкіндіктерге қысқаша, өте қысқаша шолу, яғни. BPF практикалық қолдануларын тереңірек зерттеу үшін болашақта бізге қажет нәрсенің бәрі.
Мақаланың қысқаша мазмұны
BPF архитектурасына кіріспе. Біріншіден, біз BPF архитектурасына құс көзімен қарап, негізгі құрамдастарды сипаттаймыз.
bpf жүйелік шақыру арқылы нысандарды басқару. Жүйе туралы біраз түсінігі бар, біз ақырында арнайы жүйелік шақыру арқылы пайдаланушы кеңістігінен нысандарды қалай құруға және басқаруға болатынын қарастырамыз - bpf(2).
Пишем программы BPF с помощью libbpf. Әрине, жүйелік шақыру арқылы бағдарламаларды жазуға болады. Бірақ қиын. Нақтырақ сценарий үшін ядролық бағдарламашылар кітапхана әзірледі libbpf. Біз келесі мысалдарда қолданатын негізгі BPF қолданбасының қаңқасын жасаймыз.
Ядро көмекшілері. Мұнда біз BPF бағдарламалары ядроның көмекші функцияларына қалай қол жеткізе алатынын үйренеміз - бұл құрал карталармен бірге классикалықпен салыстырғанда жаңа BPF мүмкіндіктерін түбегейлі кеңейтеді.
BPF бағдарламаларынан карталарға қол жеткізу. Осы кезде біз карталарды пайдаланатын бағдарламаларды қалай жасауға болатынын түсіну үшін жеткілікті білеміз. Тіпті керемет және күшті тексерушіге жылдам шолу жасайық.
Әзірлеу құралдары. Эксперименттерге қажетті утилиталар мен ядроларды қалай жинау керектігі туралы анықтама бөлімі.
Қорытынды. Мақаланың соңында осы уақытқа дейін оқығандар келесі мақалаларда мотивациялық сөздер мен не болатынын қысқаша сипаттайды. Сондай-ақ біз жалғастыруды күтуге ниеті немесе мүмкіндігі жоқ адамдар үшін өздігінен оқуға арналған бірқатар сілтемелерді тізімдейміз.
BPF архитектурасына кіріспе
BPF архитектурасын қарастыруды бастамас бұрын, біз соңғы рет (oh) сілтеме жасаймыз классикалық BPF, ол RISC машиналарының пайда болуына жауап ретінде әзірленді және тиімді пакеттерді сүзу мәселесін шешті. Архитектураның сәтті болғаны соншалық, тоқсаныншы жылдары Беркли UNIX-те дүниеге келген ол бар операциялық жүйелердің көпшілігіне ауыстырылды, жиырмасыншы жылдарға дейін аман қалды және әлі де жаңа қосымшаларды табуда.
Жаңа BPF 64-биттік машиналар, бұлттық қызметтер және SDN құру құралдарының қажеттілігінің артуына жауап ретінде әзірленді (Sжиі-dанықталған nжұмыс). Классикалық BPF үшін жетілдірілген ауыстыру ретінде ядролық желі инженерлері әзірлеген, жаңа BPF алты айдан кейін Linux жүйелерін іздеудің қиын тапсырмасында қолданбаларды тапты және енді, пайда болғаннан кейін алты жыл өткен соң, бізге келесі мақала қажет болады: бағдарламалардың әр түрін көрсетіңіз.
Күлкілі суреттер
Негізінде, BPF қауіпсіздікке нұқсан келтірместен ядро кеңістігінде «еркін» кодты іске қосуға мүмкіндік беретін құмсалғыш виртуалды машинасы болып табылады. BPF бағдарламалары пайдаланушы кеңістігінде жасалады, ядроға жүктеледі және кейбір оқиғалар көзіне қосылады. Оқиға, мысалы, желілік интерфейске пакетті жеткізу, кейбір ядро функциясын іске қосу және т.б. болуы мүмкін. Бума жағдайында BPF бағдарламасы буманың деректері мен метадеректеріне қол жеткізе алады (бағдарлама түріне байланысты оқу және, мүмкін, жазу үшін); ядро функциясын іске қосқан жағдайда, дәлелдер. функция, соның ішінде ядро жадына арналған көрсеткіштер және т.б.
Бұл процесті толығырақ қарастырайық. Алдымен классикалық BPF-тен бірінші айырмашылығы туралы айтайық, олар үшін бағдарламалар ассемблерде жазылған. Жаңа нұсқада архитектура бағдарламаларды жоғары деңгейлі тілдерде, ең алдымен, әрине, С тілінде жазуға болатындай кеңейтілді. Бұл үшін BPF архитектурасы үшін байт-кодты генерациялауға мүмкіндік беретін llvm сервері әзірленді.
BPF архитектурасы ішінара заманауи машиналарда тиімді жұмыс істеу үшін жасалған. Бұл тәжірибеде жұмыс істеу үшін ядроға жүктелгеннен кейін BPF байт коды JIT компиляторы деп аталатын құрамдастың көмегімен жергілікті кодқа аударылады (Jжоғары In Tмен). Әрі қарай, есіңізде болса, классикалық BPF-де бағдарлама ядроға жүктеліп, оқиға көзіне атомдық түрде - бір жүйелік шақыру контексінде тіркелген. Жаңа архитектурада бұл екі кезеңде болады - біріншіден, код жүйелік шақыру арқылы ядроға жүктеледі. bpf(2)содан кейін бағдарлама түріне байланысты өзгеретін басқа механизмдер арқылы бағдарлама оқиға көзіне бекітіледі.
Бұл жерде оқырманда сұрақ туындауы мүмкін: мүмкін бе? Мұндай кодты орындау қауіпсіздігіне қалай кепілдік беріледі? Орындау қауіпсіздігі бізге BPF бағдарламаларын жүктеу кезеңі арқылы кепілдік береді verifier (ағылшын тілінде бұл кезең verifier деп аталады және мен ағылшын сөзін пайдалануды жалғастырамын):
Верификатор – программаның ядроның қалыпты жұмысын бұзбауын қамтамасыз ететін статикалық анализатор. Бұл, айтпақшы, бағдарлама жүйенің жұмысына кедергі келтіре алмайды дегенді білдірмейді - BPF бағдарламалары түріне байланысты ядро жадының бөлімдерін оқи және қайта жаза алады, функциялардың мәндерін қайтара алады, қиюға, қосуға, қайта жазуға болады. және тіпті желілік пакеттерді жіберу. Verifier BPF бағдарламасын іске қосу ядроны бұзбайтынына және ережелерге сәйкес жазуға рұқсаты бар бағдарлама, мысалы, шығыс пакетінің деректері пакеттен тыс ядро жадысын қайта жаза алмайтынына кепілдік береді. BPF барлық басқа компоненттерімен танысқаннан кейін біз сәйкес бөлімде верфикаторды толығырақ қарастырамыз.
Сонымен, біз осы уақытқа дейін не білдік? Пайдаланушы бағдарламаны Си тілінде жазады, жүйелік шақыру арқылы ядроға жүктейді bpf(2), мұнда оны тексеруші тексереді және жергілікті байт кодқа аударады. Содан кейін сол немесе басқа пайдаланушы бағдарламаны оқиға көзіне қосады және ол орындала бастайды. Жүктеу мен қосылымды бөлу бірнеше себептерге байланысты қажет. Біріншіден, верфикаторды іске қосу салыстырмалы түрде қымбат және бір бағдарламаны бірнеше рет жүктеп алу арқылы біз компьютер уақытын босқа кетіреміз. Екіншіден, бағдарламаның дәл қалай қосылуы оның түріне байланысты және бір жыл бұрын жасалған бір «әмбебап» интерфейс бағдарламалардың жаңа түрлеріне сәйкес келмеуі мүмкін. (Қазір архитектура жетілген болса да, бұл интерфейсті деңгейде біріктіру идеясы бар. libbpf.)
Мұқият оқырман суреттермен әлі аяқталмағанымызды байқайды. Шынында да, жоғарыда айтылғандардың бәрі BPF классикалық BPF салыстырғанда суретті түбегейлі өзгертетінін түсіндірмейді. Қолдану аясын айтарлықтай кеңейтетін екі жаңалық - ортақ жад пен ядро көмекшісі функцияларын пайдалану мүмкіндігі. BPF-де ортақ жад карталар деп аталатындар арқылы жүзеге асырылады - белгілі бір API бар ортақ деректер құрылымдары. Олар бұл атауды алған болуы мүмкін, себебі картаның бірінші түрі хэш-кесте пайда болды. Содан кейін массивтер пайда болды, жергілікті (әр CPU) хэш кестелері және жергілікті массивтер, іздеу ағаштары, BPF бағдарламаларына көрсеткіштерді қамтитын карталар және т.б. Біз үшін ең қызықтысы, BPF бағдарламаларының енді қоңыраулар арасындағы күйді сақтау және оны басқа бағдарламалармен және пайдаланушы кеңістігімен бөлісу мүмкіндігі бар.
Карталарға жүйелік қоңырау арқылы пайдаланушы процестерінен қатынасады bpf(2), және көмекші функцияларды қолданатын ядрода жұмыс істейтін 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. Мысалы, сол жақтағы код оң жақтағы кодқа келесідей аударылады:
Тіркелу r0 сонымен қатар бағдарламаның орындалу нәтижесін қайтару үшін және регистрде қолданылады r1 бағдарлама контекстке көрсеткіш беріледі - бағдарлама түріне байланысты бұл, мысалы, құрылым болуы мүмкін struct xdp_md (XDP үшін) немесе құрылым struct __sk_buff (әртүрлі желілік бағдарламалар үшін) немесе құрылым struct pt_regs (қадағалау бағдарламаларының әртүрлі түрлері үшін) және т.б.
Сонымен, бізде регистрлер жинағы, ядро көмекшілері, стек, контекстік көрсеткіш және карталар түріндегі ортақ жады болды. Мұның бәрі сапарда өте қажет емес, бірақ...
Сипаттаманы жалғастырып, осы объектілермен жұмыс істеуге арналған командалық жүйе туралы сөйлесейік. Барлық (барлығы дерлік) BPF нұсқауларында бекітілген 64 бит өлшемі бар. Егер сіз 64 биттік Big Endian құрылғысындағы бір нұсқауды қарасаңыз, көресіз
Бұл 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 аудармасы, сондай-ақ карталарды зерттеу, функцияларды шақыру және т.б. туралы бөлімдерде жеке нұсқаулармен танысамыз.
Командалық кодтар тең 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. Көзге қарап дұрыс екенімізді тексерейік:
Иә, бұл мағынасыз бағдарлама, бірақ ол тек төрт қарапайым нұсқауларға аударылады.
Ерекше мысал: 16 байт нұсқау
Кейбір нұсқаулардың 64 биттен көп орын алатынын жоғарыда айттық. Бұл, мысалы, нұсқауларға қатысты lddw (Код = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — өрістерден қос сөзді регистрге жүктеңіз Imm. Істің мәні мұнда Imm өлшемі 32, ал қос сөз 64 бит, сондықтан бір 64 биттік нұсқауда регистрге 64 биттік жедел мәнді жүктеу жұмыс істемейді. Ол үшін өрісте 64-биттік мәннің екінші бөлігін сақтау үшін екі көршілес нұсқаулар қолданылады Imm. Мысал:
Сонымен, біз BPF екілік кодтарын оқуды үйрендік және қажет болған жағдайда кез келген нұсқауды талдауға дайынбыз. Дегенмен, іс жүзінде стандартты құралдарды пайдаланып бағдарламаларды бөлшектеу ыңғайлы және жылдамырақ екенін айту керек, мысалы:
BPF объектілерінің өмірлік циклі, bpffs файлдық жүйесі
(Мен алдымен осы бөлімшеде сипатталған кейбір мәліметтерді білдім ораза Алексей Старовойтов BPF блогы.)
BPF объектілері – бағдарламалар мен карталар – командалар арқылы пайдаланушы кеңістігінен жасалады BPF_PROG_LOAD и BPF_MAP_CREATE жүйелік қоңырау bpf(2), мұның дәл қалай болатыны туралы келесі бөлімде айтатын боламыз. Бұл ядро деректерінің құрылымдарын және олардың әрқайсысы үшін жасайды refcount (анықтама саны) бір мәнге орнатылады және нысанды көрсететін файл дескрипторы пайдаланушыға қайтарылады. Тұтқаны жабылғаннан кейін refcount объект біреуге азаяды, ал нөлге жеткенде объект жойылады.
Бағдарлама карталарды пайдаланса, онда refcount бұл карталар бағдарламаны жүктегеннен кейін бір есе артады, яғни. олардың файл дескрипторлары пайдаланушы процесінен жабылуы мүмкін және бәрібір refcount нөлге айналмайды:
Бағдарламаны сәтті жүктегеннен кейін біз оны әдетте оқиға генераторының қандай да бір түріне тіркейміз. Мысалы, біз оны кіріс пакеттерін өңдеу немесе кейбіріне қосу үшін желілік интерфейске қоя аламыз tracepoint өзегінде. Осы кезде анықтамалық есептегіш те бір есе артады және біз жүктеуші бағдарламасындағы файл дескрипторын жаба аламыз.
Жүктегішті қазір өшірсек не болады? Бұл оқиға генераторының (ілмек) түріне байланысты. Барлық желілік ілгектер жүктеуші аяқталғаннан кейін болады, бұл жаһандық ілгектер деп аталады. Және, мысалы, бақылау бағдарламалары оларды жасаған процесс аяқталғаннан кейін шығарылады (сондықтан «жергілікті процесске» жергілікті деп аталады). Техникалық тұрғыдан, жергілікті ілгектер әрқашан пайдаланушы кеңістігінде сәйкес файл дескрипторына ие және сондықтан процесс жабылған кезде жабылады, бірақ жаһандық ілгектер жоқ. Төмендегі суретте қызыл кресттерді пайдалана отырып, мен жергілікті және ғаламдық ілгектер жағдайында жүктеуші бағдарламасының тоқтатылуы нысандардың қызмет ету мерзіміне қалай әсер ететінін көрсетуге тырысамын.
Неліктен жергілікті және жаһандық ілгектер арасында айырмашылық бар? Желілік бағдарламалардың кейбір түрлерін іске қосу пайдаланушы кеңістігісіз мағынасы бар, мысалы, DDoS қорғауын елестетіңіз - жүктеуші ережелерді жазады және BPF бағдарламасын желі интерфейсіне қосады, содан кейін жүктеуші барып, өзін өлтіруі мүмкін. Екінші жағынан, сіз он минут ішінде тізеңізге жазған отладтауды бақылау бағдарламасын елестетіп көріңіз - ол аяқталған кезде жүйеде қоқыс қалмағанын қалайсыз және жергілікті ілгектер мұны қамтамасыз етеді.
Екінші жағынан, ядродағы бақылау нүктесіне қосылғыңыз келетінін және көптеген жылдар бойы статистиканы жинағыңыз келетінін елестетіңіз. Бұл жағдайда пайдаланушы бөлігін аяқтап, мезгіл-мезгіл статистикаға оралғыңыз келеді. bpf файлдық жүйесі бұл мүмкіндікті береді. Бұл BPF нысандарына сілтеме жасайтын файлдарды жасауға және осылайша көбейтуге мүмкіндік беретін тек жадтағы псевдофайлдық жүйе. refcount нысандар. Осыдан кейін жүктеуші шыға алады және ол жасаған нысандар тірі қалады.
BPF нысандарына сілтеме жасайтын bpff файлдарында файлдарды жасау «тіреу» деп аталады (келесі сөз тіркесіндегідей: «процесс BPF бағдарламасын немесе картасын бекітеді»). BPF нысандары үшін файлдық нысандарды жасау тек жергілікті нысандардың қызмет ету мерзімін ұзарту үшін ғана емес, сонымен қатар жаһандық нысандардың ыңғайлылығы үшін де мағынасы бар - жаһандық DDoS қорғау бағдарламасымен мысалға оралсақ, біз келіп, статистиканы көргіміз келеді. кейде.
BPF файлдық жүйесі әдетте орнатылған /sys/fs/bpf, бірақ оны жергілікті түрде де орнатуға болады, мысалы, келесідей:
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
Файлдық жүйе атаулары пәрмен арқылы жасалады BPF_OBJ_PIN BPF жүйесін шақыру. Суреттеу үшін бағдарламаны алайық, оны құрастырайық, жүктеп алайық және оны бекітейік bpffs. Біздің бағдарлама пайдалы ештеңе жасамайды, біз мысалды қайта шығару үшін кодты ғана ұсынып отырмыз:
Енді утилитаны пайдаланып бағдарламамызды жүктеп алайық bpftool және ілеспе жүйелік қоңырауларды қараңыз bpf(2) (кейбір сәйкес емес жолдар страце шығысынан жойылды):
Мұнда біз бағдарламаны пайдаланып жүктедік 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 бағдарламаларының кейбір түрлері бағдарламаны жылдам ауыстыруға мүмкіндік береді, яғни. реттілік атомдылығын қамтамасыз етеді 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 — көрсеткішке сәйкес нысан өлшемі, яғни. әдетте бұл 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
Біз шешкеннен кейін сол жүктеп саламыз, мұны қалай жасайтынымызды айта аламыз:
Бағдарламадағы қызықты оқиғалар массивтің анықтамасынан басталады insns - машиналық кодтағы біздің BPF бағдарламамыз. Бұл жағдайда BPF бағдарламасының әрбір нұсқауы құрылымға оралады bpf_insn. Бірінші элемент insns нұсқауларға сәйкес келеді r0 = 2, екінші - exit.
Шегіну. Ядро машина кодтарын жазу және ядро тақырыбы файлын пайдалану үшін ыңғайлырақ макростарды анықтайды tools/include/linux/filter.h жаза алар едік
Бірақ BPF бағдарламаларын жергілікті кодта жазу тек ядродағы сынақтар мен BPF туралы мақалаларды жазу үшін қажет болғандықтан, бұл макростардың болмауы әзірлеушінің өмірін қиындатпайды.
BPF бағдарламасын анықтағаннан кейін оны ядроға жүктеуге көшеміз. Біздің минималистік параметрлер жиынтығы attr бағдарлама түрін, нұсқаулар жиынтығы мен санын, қажетті лицензияны және атауын қамтиды "woo", оны жүктеп алғаннан кейін жүйеде бағдарламамызды табу үшін қолданамыз. Бағдарлама, уәде етілгендей, жүйеге қоңырау шалу арқылы жүктеледі bpf.
Бағдарламаның соңында біз пайдалы жүктемені имитациялайтын шексіз циклмен аяқталамыз. Онсыз, жүйелік шақыру бізге қайтарылған файл дескрипторы жабылған кезде бағдарлама ядромен жойылады. bpf, және біз оны жүйеде көрмейміз.
Ал, біз сынаққа дайынбыз. Төменде бағдарламаны жинап, іске қосайық straceбәрі дұрыс жұмыс істеп тұрғанын тексеру үшін:
Бәрі жақсы, bpf(2) бізге 3 тұтқасын қайтарды және біз шексіз циклге кірдік pause(). Жүйеде бағдарламамызды табуға тырысайық. Мұны істеу үшін біз басқа терминалға өтіп, қызметтік бағдарламаны қолданамыз bpftool:
Жүйеде жүктелген бағдарлама бар екенін көреміз woo жаһандық идентификаторы 390 және қазір орындалуда simple-prog бағдарламаға нұсқайтын ашық файл дескрипторы бар (және егер simple-prog содан кейін жұмысты аяқтайды woo жоғалады). Күткендей, бағдарлама woo BPF архитектурасында екілік кодтардың 16 байт - екі нұсқаулығын алады, бірақ оның түпнұсқа түрінде (x86_64) ол қазірдің өзінде 40 байт. Бағдарламамызды бастапқы түрінде қарастырайық:
тосынсыйлар жоқ. Енді 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 бағдарламаларына көрсеткіштер немесе желілік интерфейстерге көрсеткіштер, перф оқиғаларымен жұмыс істеу карталары және т.б. Оқырманды шатастырмау үшін біз бұл жерде олар туралы айтпаймыз. Бұдан басқа, біз синхрондау мәселелерін елемейміз, өйткені бұл біздің мысалдарымыз үшін маңызды емес. Қолжетімді карта түрлерінің толық тізімін мына жерден табуға болады <linux/bpf.h>, және бұл бөлімде мысал ретінде тарихи бірінші типті хэш кестесін аламыз BPF_MAP_TYPE_HASH.
Егер сіз C++ тілінде хэш кестесін жасасаңыз, айтасыз unordered_map<int,long> woo, бұл орыс тілінде «Маған үстел керек woo шектеусіз өлшем, оның кілттері типті int, ал мәндер түрі болып табылады long" BPF хэш кестесін жасау үшін біз кестенің максималды өлшемін көрсетуіміз керек, ал кілттер мен мәндердің түрлерін көрсетудің орнына олардың өлшемдерін байтпен көрсетуіміз керек болса, дәл солай істеуіміз керек. . Карталарды жасау үшін пәрменді пайдаланыңыз BPF_MAP_CREATE жүйелік қоңырау bpf. Картаны жасайтын азды-көпті минималды бағдарламаны қарастырайық. BPF бағдарламаларын жүктейтін алдыңғы бағдарламадан кейін бұл сізге қарапайым болып көрінуі керек:
Мұнда біз параметрлер жинағын анықтаймыз attr, онда біз «Маған кілттері мен өлшем мәндері бар хэш кестесі қажет sizeof(int), онда мен ең көбі төрт элементті қоя аламын». BPF карталарын жасау кезінде сіз басқа параметрлерді көрсете аласыз, мысалы, бағдарламамен мысалдағыдай, біз нысанның атын келесідей көрсеттік. "woo".
Міне, жүйелік қоңырау 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 саны біздің нысанның ғаламдық идентификаторы болып табылады. Жүйедегі кез келген бағдарлама осы идентификаторды пәрмен арқылы бар картаны ашу үшін пайдалана алады 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 қалай дәл оқиды және элементтерді қосады? Сорғыштың астына қарайық:
Алдымен пәрмен арқылы картаны оның ғаламдық идентификаторы бойынша аштық 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:
Күткендей, бұл өте қарапайым: пәрмен 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: бар картаны оның ғаламдық идентификаторы бойынша ашыңыз
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 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 архитектурасына арналған кодты генерациялауға арналған сервері және кітапханасы бар libbpf, ол BPF қолданбаларының пайдаланушы жағын жазуға және көмегімен жасалған BPF бағдарламаларының кодын жүктеуге мүмкіндік береді. llvm/clang.
Шын мәнінде, біз осы және кейінгі мақалаларда көретініміздей, libbpf онсыз өте көп жұмыс істейді (немесе ұқсас құралдар - iproute2, libbcc, libbpf-goжәне т.б.) өмір сүру мүмкін емес. Жобаның киллер ерекшеліктерінің бірі libbpf бұл BPF CO-RE (Бір рет құрастыру, барлық жерде іске қосу) – әртүрлі API интерфейстерінде жұмыс істеу мүмкіндігі бар (мысалы, ядро құрылымы нұсқадан өзгерген кезде) бір ядродан екіншісіне тасымалданатын BPF бағдарламаларын жазуға мүмкіндік беретін жоба. нұсқасына). CO-RE-мен жұмыс істеу үшін сіздің ядроңыз BTF қолдауымен құрастырылуы керек (біз мұны қалай істеу керектігін бөлімде сипаттаймыз. Даму құралдары. Сіз ядроңыздың BTF көмегімен жасалғанын немесе өте қарапайым емес екенін келесі файлдың болуы арқылы тексере аласыз:
Бұл файл ядрода пайдаланылатын барлық деректер түрлері туралы ақпаратты сақтайды және біздің қолданатын барлық мысалдарымызда қолданылады libbpf. Біз CO-RE туралы келесі мақалада егжей-тегжейлі айтатын боламыз, бірақ бұл мақалада - өзіңізге ядро құрыңыз. CONFIG_DEBUG_INFO_BTF.
Кітапхана libbpf каталогта тұрады tools/lib/bpf ядро және оның дамуы пошталық тізім арқылы жүзеге асырылады [email protected]. Дегенмен, ядродан тыс тұратын қолданбалардың қажеттіліктері үшін бөлек репозиторий сақталады https://github.com/libbpf/libbpf онда ядро кітапханасы азды-көпті оқуға қол жеткізу үшін шағылыстырылған.
Бұл бөлімде біз пайдаланатын жобаны қалай жасауға болатынын қарастырамыз libbpf, бірнеше (көп немесе аз мағынасыз) сынақ бағдарламаларын жазып, оның барлығы қалай жұмыс істейтінін егжей-тегжейлі талдап көрейік. Бұл келесі бөлімдерде BPF бағдарламаларының карталармен, ядро көмекшілерімен, BTF және т.
Әдетте жобаларды пайдаланады libbpf GitHub репозиторийін git ішкі модулі ретінде қосыңыз, біз де солай істейміз:
Бұл бөлімдегі келесі жоспарымыз келесідей: біз 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 тақырыбы осылай анықталады:
Бағдарламамыз өте қарапайым болып шыққанымен, біз әлі де көптеген бөлшектерге назар аударуымыз керек. Біріншіден, біз енгізетін бірінші тақырып файлы vmlinux.h, біз оны жаңа ғана пайдаланып шығардық bpftool btf dump - енді ядро құрылымдарының қандай екенін білу үшін ядро-тақырыптар бумасын орнатудың қажеті жоқ. Келесі тақырып файлы бізге кітапханадан келеді libbpf. Енді ол макросты анықтау үшін ғана қажет SEC, ол таңбаны ELF нысан файлының сәйкес бөліміне жібереді. Біздің бағдарлама бөлімде қамтылған xdp/simple, мұнда қиғаш сызықтың алдында BPF программа түрін анықтаймыз - бұл қолданылатын шарт libbpf, бөлім атауына негізделген ол іске қосу кезінде дұрыс түрді ауыстырады bpf(2). BPF бағдарламасының өзі C - өте қарапайым және бір жолдан тұрады return XDP_PASS. Соңында, бөлек бөлім "license" лицензияның атауын қамтиды.
Біз бағдарламамызды llvm/clang, >= 10.0.0 нұсқасы немесе одан да жақсырақ (бөлімді қараңыз) арқылы құрастыра аламыз. Даму құралдары):
Қызықты ерекшеліктер арасында: біз мақсатты архитектураны көрсетеміз -target bpf және тақырыптарға жол libbpf, біз жақында орнатқан. Сондай-ақ, бұл туралы ұмытпаңыз -O2, бұл опциясыз сіз болашақта тосын сыйларға тап болуыңыз мүмкін. Біздің кодты қарастырайық, біз қалаған бағдарламаны жаза алдық па?
Иә, жұмыс істеді! Енді бізде бағдарлама бар екілік файл бар және біз оны ядроға жүктейтін қолданба жасағымыз келеді. Осы мақсатта кітапхана 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 және біздің нысан файлымызды сипаттайды:
Төмен деңгейлі API іздерін мына жерден көре аламыз: құрылым struct bpf_program *simple и struct bpf_link *simple. Бірінші құрылым бөлімде жазылған бағдарламамызды арнайы сипаттайды xdp/simple, ал екіншісі бағдарламаның оқиға көзіне қосылу жолын сипаттайды.
функция xdp_simple_bpf__open_and_load, ELF объектісін ашады, оны талдайды, барлық құрылымдар мен ішкі құрылымдарды жасайды (бағдарламадан басқа ELF-де басқа бөлімдер де бар - деректер, тек оқуға арналған деректер, жөндеу ақпараты, лицензия және т.б.), содан кейін жүйені пайдаланып ядроға жүктейді. қоңырау шалу bpf, біз оны бағдарламаны құрастыру және іске қосу арқылы тексере аламыз:
Енді қолданатын бағдарламамызды қарастырайық bpftool. Оның жеке куәлігін табайық:
# 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)
және демп (біз команданың қысқартылған түрін қолданамыз 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 бағдарламаларына ядро құрылымдарына қол жеткізуге, карталарды басқаруға, сондай-ақ «нақты әлеммен» байланысуға мүмкіндік береді - тамаша оқиғаларды жасауға, аппараттық құралдарды басқаруға (мысалы, пакеттерді қайта бағыттау) және т.б.
Мысал: bpf_get_smp_processor_id
«Үлгі бойынша оқыту» парадигмасы аясында көмекші функциялардың бірін қарастырайық, bpf_get_smp_processor_id(), белгілі файлда kernel/bpf/helpers.c. Ол оны шақырған BPF бағдарламасы жұмыс істеп тұрған процессордың нөмірін қайтарады. Бірақ оның семантикасы бізді қызықтырмайды, өйткені оның орындалуы бір жолды алады:
BPF көмекшісі функциясының анықтамалары Linux жүйесінің қоңырау анықтамаларына ұқсас. Мұнда, мысалы, аргументтері жоқ функция анықталған. (Айталық, үш аргумент алатын функция макрос арқылы анықталады BPF_CALL_3. Аргументтердің максималды саны – бес.) Дегенмен, бұл анықтаманың бірінші бөлігі ғана. Екінші бөлім тип құрылымын анықтау болып табылады struct bpf_func_proto, онда тексеруші түсінетін көмекші функцияның сипаттамасы бар:
Белгілі бір түрдегі BPF бағдарламалары бұл функцияны пайдалануы үшін оны, мысалы, түрі үшін тіркеуі керек BPF_PROG_TYPE_XDP функция ядрода анықталған xdp_func_proto, ол көмекші функция идентификаторынан XDP бұл функцияны қолдайтынын немесе қолдамайтынын анықтайды. Біздің функциямыз қолдайды:
Жаңа BPF бағдарламасының түрлері файлда "анықталған" include/linux/bpf_types.h макросты пайдалану BPF_PROG_TYPE. Тырнақшаларда анықталған, себебі бұл логикалық анықтама, ал Си тілінің терминдерінде нақты құрылымдардың тұтас жиынтығының анықтамасы басқа жерлерде кездеседі. Атап айтқанда, файлда kernel/bpf/verifier.c файлдағы барлық анықтамалар bpf_types.h құрылымдар массивін жасау үшін қолданылады bpf_verifier_ops[]:
Яғни, BPF бағдарламасының әрбір түрі үшін типтің деректер құрылымына көрсеткіш анықталады struct bpf_verifier_ops, ол мәнмен инициализацияланған _name ## _verifier_ops, яғни, xdp_verifier_ops үшін xdp. Құрылым xdp_verifier_opsанықтаған файлда net/core/filter.c келесідей:
Мұнда біз таныс функциямызды көреміз xdp_func_proto, ол тексерушіні қиындыққа тап болған сайын іске қосады кейбіреулері BPF бағдарламасындағы функцияларды қараңыз verifier.c.
Гипотетикалық BPF бағдарламасы функцияны қалай пайдаланатынын қарастырайық bpf_get_smp_processor_id. Ол үшін біз алдыңғы бөлімдегі бағдарламаны келесідей қайта жазамыз:
яғни, 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:
Бірінші жолда біз нұсқауларды көреміз call, параметр IMM ол 8-ге тең және SRC_REG - нөл. Тексеруші пайдаланатын ABI келісіміне сәйкес, бұл сегізінші көмекші функциясына шақыру. Ол іске қосылғаннан кейін логика қарапайым. Регистрден мәнді қайтару r0 көшірілді r1 ал 2,3-жолдарда типке түрлендіріледі u32 — жоғарғы 32 бит тазаланады. 4,5,6,7-жолдарда 2 қайтарамыз (XDP_PASS) немесе 1 (XDP_DROP) 0 жолындағы көмекші функция нөлдік немесе нөлдік емес мәнді қайтарғанына байланысты.
$ 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;
}
Біздің бағдарлама жұмыс істеп тұрған процессордың нөмірін басып шығарады. Оны құрастырып, кодты қарастырайық:
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 және шынымен басталды!
Мұнда біз функцияны қолданамыз 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 бар және біз интерфейсте бірдей идентификаторды көреміз 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 келесідей:
Бағдарламаның басында біз карта анықтамасын қостық woo: Бұл сияқты мәндерді сақтайтын 8 элементті массив u64 (С тілінде біз осындай массивті анықтайтын едік u64 woo[8]). Бағдарламада "xdp/simple" ағымдағы процессор нөмірін айнымалыға аламыз key содан кейін көмекші функциясын пайдаланыңыз bpf_map_lookup_element біз массивтегі сәйкес жазбаға көрсеткіш аламыз, оны бір көбейтеміз. Орыс тіліне аударылған: біз кіріс пакеттерін өңдеген процессордың статистикасын есептейміз. Бағдарламаны іске қосып көрейік:
Оның қосылғанын тексерейік 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
Барлық дерлік процестер CPU7-де өңделді. Бұл біз үшін маңызды емес, бастысы бағдарлама жұмыс істейді және біз BPF бағдарламаларынан карталарға қалай қол жеткізуге болатындығын түсінеміз - пайдалану хелперов bpf_mp_*.
Мистикалық көрсеткіш
Осылайша, біз картаға BPF бағдарламасынан қоңыраулар арқылы қол жеткізе аламыз
$ 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-жол):
Осылайша, жүктеуші бағдарламаны іске қосу кезінде сілтеме бар деп қорытынды жасауға болады &woo кітапханасы бар нәрсемен ауыстырылды libbpf. Алдымен біз шығуды қарастырамыз strace:
Біз мұны көреміз libbpf картасын жасады woo содан кейін біздің бағдарламаны жүктеп алдық simple. Бағдарламаны қалай жүктейтінімізді толығырақ қарастырайық:
бұл тудырады 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;
және ондағы бастапқы регистрді ауыстырыңыз 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:
(толық кодты табуға болады байланыс). Сонымен, біз алгоритмімізді кеңейте аламыз:
Бағдарламаны жүктеу кезінде верфикатор картаның дұрыс пайдаланылуын тексереді және сәйкес құрылымның мекенжайын жазады 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 және маған файл дескрипторын қайтарыңыз»:
Күрделі бөлік prog_load құрылымдар массиві ретінде біздің BPF бағдарламамыздың анықтамасы болып табылады struct bpf_insn insns[]. Бірақ бізде C тілінде бар бағдарламаны пайдаланып жатқандықтан, біз аздап алдап аламыз:
Барлығы сияқты құрылымдар түрінде 14 нұсқаулық жазуымыз керек struct bpf_insn (кеңес: үйіндіні жоғарыдан алыңыз, нұсқаулар бөлімін қайта оқып шығыңыз, ашыңыз linux/bpf.h и linux/bpf_common.h және анықтауға тырысыңыз struct bpf_insn insns[] өз бетінше):
Мұны өздері жазбағандарға арналған жаттығу - табыңыз map_fd.
Біздің бағдарламада тағы бір ашылмаған бөлік қалды - xdp_attach. Өкінішке орай, XDP сияқты бағдарламаларды жүйелік қоңырау арқылы қосу мүмкін емес bpf. BPF және XDP жасаған адамдар желідегі Linux қауымдастығының өкілдері болды, яғни олар өздеріне ең танысын пайдаланды (бірақ қалыпты адамдар) ядромен әрекеттесу интерфейсі: 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;
}
Соңында, розетканы ашатын және оған файл дескрипторы бар арнайы хабарлама жіберетін функциямыз:
$ 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
Ура, бәрі жұмыс істейді. Айтпақшы, біздің карта қайтадан байт түрінде көрсетілетінін ескеріңіз. Бұл айырмашылығы, бұл фактімен байланысты 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
... много времени спустя
$
Енді біз бәрі дұрыс біріктірілгенін тексере аламыз:
(Құрастыру нұсқаулары 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 дамуы Linux желілік қауымдастығында орын алады, сондықтан барлық өзгерістер ерте ме, кеш пе, Linux желісін қолдаушы Дэвид Миллер арқылы өтеді. Олардың сипатына қарай - өңдеулер немесе жаңа мүмкіндіктер - желілік өзгерістер екі ядроның біріне жатады - net немесе net-next. BPF үшін өзгерістер арасында бірдей жолмен бөлінеді bpf и bpf-next, олар кейін сәйкесінше net және net-next ішіне біріктіріледі. Қосымша мәліметтер алу үшін қараңыз bpf_devel_QA и netdev-ЖҚС. Сондықтан өз талғамыңызға және сынап жатқан жүйенің тұрақтылық қажеттіліктеріне қарай ядроны таңдаңыз (*-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, Linux ядросының бөлігі ретінде жеткізіледі. Оны 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 анықтамалық нұсқаулығы — цилиумнан алынған BPF туралы құжаттама, дәлірек айтқанда, BPF жасаушылар мен қолдаушылардың бірі Дэниел Боркманнан. Бұл бірінші маңызды сипаттамалардың бірі, оның басқалардан ерекшелігі Даниел не туралы жазып жатқанын жақсы біледі және онда ешқандай қате жоқ. Атап айтқанда, бұл құжат белгілі утилитаны пайдаланып XDP және TC типті BPF бағдарламаларымен қалай жұмыс істеу керектігін сипаттайды. ip пакеттен iproute2.
Documentation/networking/filter.txt — классикалық, содан кейін кеңейтілген BPF құжаттамасы бар түпнұсқа файл. Ассемблер тілі мен техникалық архитектуралық бөлшектерді зерттегіңіз келсе, жақсы оқу.
Facebook-тен BPF туралы блог. Ол сирек жаңартылады, бірақ Алексей Старовойтов (eBPF авторы) және Андрей Накрыйко - (жұмысшы) жазғандай. libbpf).
bpftool құпиялары. Квентин Моннеден bpftool қолдану мысалдары мен құпиялары бар қызықты твиттер желісі.