အစပိုင်းတွင် နည်းပညာတစ်ခုရှိခဲ့ပြီး ၎င်းကို BPF ဟုခေါ်သည်။ ငါတို့သူမကိုကြည့်တယ်။
အကြမ်းဖျင်းပြောရလျှင် BPF သည် သင့်အား Linux kernel space တွင် မထင်သလိုအသုံးပြုသူမှပေးသောကုဒ်ကို run နိုင်စေကာ ဗိသုကာအသစ်သည် အလွန်အောင်မြင်လာသောကြောင့် ၎င်း၏အပလီကေးရှင်းအားလုံးကိုဖော်ပြရန် နောက်ထပ်ဆောင်းပါးတစ်ဒါဇင် လိုအပ်မည်ဖြစ်သည်။ (အောက်ဖော်ပြပါ စွမ်းဆောင်ရည်ကုဒ်တွင် သင်တွေ့မြင်ရသည့်အတိုင်း developer များ ကောင်းစွာမလုပ်ဆောင်နိုင်သောအရာမှာ သင့်လျော်သောလိုဂိုကို ဖန်တီးခြင်းဖြစ်သည်။)
ဤဆောင်းပါးတွင် BPF virtual machine ၏ဖွဲ့စည်းပုံ၊ BPF နှင့်အလုပ်လုပ်ရန်အတွက် kernel interfaces၊ development tools များအပြင် ရှိပြီးသားစွမ်းဆောင်ရည်များ၏ အတိုချုပ်၊ အလွန်အတိုချုပ်သော ခြုံငုံသုံးသပ်ချက်ကို ဖော်ပြထားပါသည်။ BPF ၏လက်တွေ့အသုံးချမှုများကို ပိုမိုနက်ရှိုင်းစွာလေ့လာရန်အတွက် အနာဂတ်တွင် ကျွန်ုပ်တို့လိုအပ်မည့်အရာအားလုံးကို။
ဆောင်းပါးအကျဉ်းချုပ်
bpf(2)
.
Пишем программы BPF с помощью libbpf
.libbpf
. ကျွန်ုပ်တို့သည် နောက်ဆက်တွဲနမူနာများတွင် အသုံးပြုမည့် အခြေခံ BPF အပလီကေးရှင်းအရိုးစုတစ်ခုကို ဖန်တီးပါမည်။
BPF ဗိသုကာ မိတ်ဆက်
BPF ဗိသုကာကို မစဉ်းစားမီ၊ နောက်ဆုံးအကြိမ် (အိုး) ကို ရည်ညွှန်းပါမည်။
BPF အသစ်ကို 64-bit စက်များ၊ cloud ဝန်ဆောင်မှုများနှင့် SDN ဖန်တီးရန်အတွက် ကိရိယာများ လိုအပ်မှု တိုးပွားလာမှုကို တုံ့ပြန်သည့်အနေဖြင့် တီထွင်ခဲ့ခြင်းဖြစ်သည်။Sဆော့ဖ်ဝဲ-dဒဏ်ငွေ nအလုပ်လုပ်ခြင်း)။ ဂန္ထဝင် BPF အတွက် ပိုမိုကောင်းမွန်သော အစားထိုးမှုအဖြစ် kernel ကွန်ရက်အင်ဂျင်နီယာများမှ တီထွင်ထားသည့် BPF အသစ်သည် ခြောက်လအကြာတွင် Linux စနစ်များကို ခြေရာခံရန် ခက်ခဲသောအလုပ်တွင် အပလီကေးရှင်းများကို တွေ့ရှိခဲ့ပြီး ယခု ခြောက်နှစ်အကြာတွင်၊ ကျွန်ုပ်တို့သည် နောက်ဆောင်းပါးတစ်ခုလုံးအတွက် လိုအပ်မည်ဖြစ်သည်။ မတူညီသော ပရိုဂရမ်များကို စာရင်းပြုစုပါ။
ရယ်စရာပုံများ
၎င်း၏ အူတိုင်တွင်၊ BPF သည် လုံခြုံရေးကို မထိခိုက်စေဘဲ kernel space တွင် "တရားမျှတသော" ကုဒ်ကို လုပ်ဆောင်နိုင်စေမည့် sandbox virtual machine တစ်ခုဖြစ်သည်။ BPF ပရိုဂရမ်များကို အသုံးပြုသူနေရာလွတ်တွင် ဖန်တီးထားပြီး kernel ထဲသို့ တင်ကာ အချို့သော event source နှင့် ချိတ်ဆက်ထားသည်။ ဖြစ်ရပ်တစ်ခုသည် ဥပမာအားဖြင့်၊ ကွန်ရက်ချိတ်ဆက်မှုတစ်ခုသို့ ပက်ကတ်တစ်ခုပေးပို့ခြင်း၊ အချို့သော kernel လုပ်ဆောင်ချက်ကို စတင်ခြင်းစသည်ဖြင့် ဖြစ်နိုင်သည်။ ပက်ကေ့ဂျ်တစ်ခုတွင်၊ BPF ပရိုဂရမ်သည် ပက်ကေ့ဂျ်၏ဒေတာနှင့် မက်တာဒေတာကို ဝင်ရောက်ခွင့်ရှိလိမ့်မည် (ပရိုဂရမ်အမျိုးအစားပေါ် မူတည်၍ စာရေးခြင်း)၊ kernel လုပ်ဆောင်မှုတွင်၊ ငြင်းခုံမှုများ၊ pointers to kernel memory စသည်တို့ အပါအဝင် လုပ်ဆောင်ချက်၊
ဒီဖြစ်စဉ်ကို အနီးကပ်လေ့လာကြည့်ရအောင်။ စတင်ရန်အတွက်၊ assembler တွင်ရေးသားခဲ့သောဂန္ထဝင် BPF နှင့်ပထမဆုံးကွာခြားချက်အကြောင်းပြောဆိုကြပါစို့။ ဗားရှင်းအသစ်တွင်၊ ပရိုဂရမ်များကို အဆင့်မြင့်ဘာသာစကားများဖြင့် ရေးသားနိုင်စေရန်အတွက် ဗိသုကာလက်ရာကို ချဲ့ထွင်ထားပါသည်။ အဓိကအားဖြင့်၊ ဟုတ်ပါတယ်၊ C တွင်၊ ဤအတွက်၊ BPF ဗိသုကာအတွက် bytecode ကိုထုတ်ပေးနိုင်စေမည့် llvm အတွက် backend တစ်ခုကို ဖန်တီးထားပါသည်။
BPF ဗိသုကာလက်ရာသည် ခေတ်မီစက်များပေါ်တွင် ထိရောက်စွာလည်ပတ်နိုင်ရန် တစ်စိတ်တစ်ပိုင်းအားဖြင့် ဒီဇိုင်းထုတ်ထားသည်။ ဤအလုပ်အား လက်တွေ့လုပ်ဆောင်ရန်၊ kernel ထဲသို့ တစ်ကြိမ်တင်ပြီးသည်နှင့် BPF bytecode ကို JIT compiler ဟုခေါ်သော အစိတ်အပိုင်းကို အသုံးပြု၍ မူရင်းကုဒ်သို့ ဘာသာပြန်ဆိုပါသည်။Just In Time) နောက်တစ်ခု၊ သင်မှတ်မိပါက၊ ဂန္ထဝင် BPF တွင် ပရိုဂရမ်ကို kernel ထဲသို့ တင်ပြီး စနစ်ခေါ်ဆိုမှုတစ်ခုတည်း၏ ဆက်စပ်မှုတွင် ဖြစ်ရပ်အရင်းအမြစ်နှင့် အက်တမ်ပုံစံဖြင့် ချိတ်ဆက်ထားသည်။ ဗိသုကာအသစ်တွင်၊ ၎င်းသည် အဆင့်နှစ်ဆင့်ဖြင့် ဖြစ်ပျက်သည် - ပထမ၊ စနစ်ခေါ်ဆိုမှုကို အသုံးပြု၍ ကုဒ်ကို kernel ထဲသို့ တင်ပေးသည်။ bpf(2)
ထို့နောက်၊ နောက်ပိုင်းတွင်၊ ပရိုဂရမ်အမျိုးအစားပေါ် မူတည်၍ ကွဲပြားသော အခြားသော ယန္တရားများမှတဆင့်၊ ပရိုဂရမ်သည် ဖြစ်ရပ်အရင်းအမြစ်သို့ ချိတ်ဆက်သည်။
ဤနေရာတွင် စာဖတ်သူ မေးစရာရှိလာနိုင်သည်- ဖြစ်နိုင်ပါသလား။ ထိုကုဒ်၏ အကောင်အထည်ဖော်မှုဘေးကင်းမှုကို မည်သို့အာမခံသနည်း။ Verifier ဟုခေါ်သော BPF ပရိုဂရမ်များကို ဖွင့်သည့်အဆင့်တွင် ကျွန်ုပ်တို့အား အာမခံချက်ပေးသည် (ဤအဆင့်ကို အင်္ဂလိပ်လို Verifier ဟုခေါ်ပြီး အင်္ဂလိပ်စကားလုံးကို ကျွန်ုပ်ဆက်လက်အသုံးပြုပါမည်)
Verifier သည် ပရိုဂရမ်တစ်ခုသည် kernel ၏ ပုံမှန်လုပ်ဆောင်မှုကို မနှောင့်ယှက်ကြောင်း သေချာစေသည့် တည်ငြိမ်မှုခွဲခြမ်းစိတ်ဖြာမှုတစ်ခုဖြစ်သည်။ ၎င်းသည် နည်းလမ်းအားဖြင့်၊ ပရိုဂရမ်သည် စနစ်၏လည်ပတ်မှုကို အနှောင့်အယှက်မပြုနိုင်ဟု မဆိုလိုပါ - BPF ပရိုဂရမ်များသည် အမျိုးအစားပေါ် မူတည်၍ kernel memory ၏ အပိုင်းများကို ဖတ်ပြီး ပြန်ရေးနိုင်သည်၊ လုပ်ဆောင်ချက်များ၏ တန်ဖိုးများကို ပြန်ပေးသည်၊ ချုံ့ရန်၊ နောက်ဆက်တွဲ၊ ပြန်ရေးနိုင်သည်။ ပြီးတော့ network packets တွေကိုတောင် forward လုပ်ပါ။ BPF ပရိုဂရမ်တစ်ခုအား လုပ်ဆောင်ခြင်းသည် kernel ကို ပျက်စီးစေမည်မဟုတ်ကြောင်းနှင့် စည်းမျဉ်းများအတိုင်း၊ ဥပမာ၊ အထွက်ပက်ကတ်တစ်ခု၏ ဒေတာကို ရေးသားခွင့်ရှိသည့် ပရိုဂရမ်သည် ပက်ကတ်အပြင်ဘက်ရှိ kernel မှတ်ဉာဏ်ကို overwrite လုပ်မည်မဟုတ်ကြောင်း Verifier မှ အာမခံပါသည်။ BPF ၏ အခြားအစိတ်အပိုင်းများအားလုံးကို သိရှိနားလည်ပြီးနောက် သက်ဆိုင်ရာကဏ္ဍတွင် အသေးစိတ်အချက်အနည်းငယ်ဖြင့် အတည်ပြုစစ်ဆေးခြင်းကို ကြည့်ရှုပါမည်။
ဒါဆို အခုထိ ဘာသင်ယူခဲ့လဲ။ အသုံးပြုသူသည် C တွင် ပရိုဂရမ်တစ်ခုကို ရေးသားပြီး စနစ်ခေါ်ဆိုမှုကို အသုံးပြု၍ kernel ထဲသို့ ထည့်သွင်းသည်။ bpf(2)
၎င်းကို အတည်ပြုသူမှ စစ်ဆေးပြီး မူရင်း bytecode သို့ ပြန်ဆိုထားသည့်နေရာတွင်။ ထို့နောက် တူညီသော သို့မဟုတ် အခြားအသုံးပြုသူသည် ပရိုဂရမ်ကို ဖြစ်ရပ်အရင်းအမြစ်နှင့် ချိတ်ဆက်ပြီး ၎င်းကို စတင်လုပ်ဆောင်သည်။ အကြောင်းရင်းများစွာအတွက် boot နှင့် connection ကို ပိုင်းခြားရန် လိုအပ်ပါသည်။ ပထမဦးစွာ၊ verifier ကိုအသုံးပြုခြင်းသည်အတော်လေးစျေးကြီးပြီး တူညီသောပရိုဂရမ်ကိုအကြိမ်ပေါင်းများစွာဒေါင်းလုဒ်လုပ်ခြင်းဖြင့်ကျွန်ုပ်တို့သည်ကွန်ပြူတာအချိန်ကိုဖြုန်းတီးကြသည်။ ဒုတိယအနေနှင့်၊ ပရိုဂရမ်တစ်ခုအား မည်ကဲ့သို့ချိတ်ဆက်ပုံအတိအကျသည် ၎င်း၏အမျိုးအစားပေါ်တွင်မူတည်ပြီး လွန်ခဲ့သည့်တစ်နှစ်က တီထွင်ခဲ့သော “universal” interface တစ်ခုသည် ပရိုဂရမ်အသစ်အမျိုးအစားများအတွက် သင့်လျော်မည်မဟုတ်ပေ။ (ယခုအခါတွင် ဗိသုကာပညာသည် ပိုမိုရင့်ကျက်လာသော်လည်း၊ ဤ interface အဆင့်တွင် ပေါင်းစည်းရန် စိတ်ကူးတစ်ခုရှိသည်။ libbpf
.)
ကျွန်ုပ်တို့သည် ပုံများကို မပြီးသေးကြောင်း အာရုံစိုက်သော စာဖတ်သူ သတိပြုမိပေမည်။ အမှန်စင်စစ်၊ အထက်ဖော်ပြပါအားလုံးသည် BPF သည် ဂန္ထဝင် BPF နှင့် နှိုင်းယှဉ်ပါက ရုပ်ပုံအား အခြေခံကျကျ ပြောင်းလဲသွားသည့် အကြောင်းရင်းကို ရှင်းပြမထားပေ။ အသုံးချနိုင်မှုနယ်ပယ်ကို သိသာထင်ရှားစွာ ချဲ့ထွင်နိုင်သော တီထွင်ဆန်းသစ်မှုနှစ်ခုမှာ မျှဝေထားသော မှတ်ဉာဏ်နှင့် kernel အကူလုပ်ဆောင်ချက်များကို အသုံးပြုခြင်းဖြစ်ပါသည်။ BPF တွင်၊ မျှဝေထားသော မမ်မိုရီကို သီးခြား API တစ်ခုဖြင့် မျှဝေထားသော ဒေတာဖွဲ့စည်းပုံများဟုခေါ်သော မြေပုံများကို အသုံးပြု၍ လုပ်ဆောင်သည်။ ပေါ်လာမည့် ပထမဆုံးမြေပုံအမျိုးအစားမှာ hash table ဖြစ်သောကြောင့် ၎င်းတို့သည် ဤအမည်ကို ရနိုင်ဖွယ်ရှိသည်။ ထို့နောက် arrays များပေါ်လာသည်၊ local (per-CPU) hash tables နှင့် local arrays၊ search tree၊ maps တွင် pointers များ BPF ပရိုဂရမ်များနှင့် အခြားများစွာပါဝင်ပါသည်။ ယခု ကျွန်ုပ်တို့အတွက် စိတ်ဝင်စားစရာမှာ BPF ပရိုဂရမ်များသည် ဖုန်းခေါ်ဆိုမှုများကြားတွင် ဆက်လက်တည်ရှိနေနိုင်ပြီး ၎င်းကို အခြားပရိုဂရမ်များနှင့် အသုံးပြုသူနေရာဖြင့် မျှဝေနိုင်စွမ်းရှိနေပြီဖြစ်သည်။
စနစ်ခေါ်ဆိုမှုကို အသုံးပြု၍ အသုံးပြုသူလုပ်ငန်းစဉ်များမှ Maps ကို ဝင်ရောက်ကြည့်ရှုသည်။ bpf(2)
နှင့် helper လုပ်ဆောင်ချက်များကို အသုံးပြု၍ kernel တွင်လည်ပတ်နေသော BPF ပရိုဂရမ်များမှ။ ထို့အပြင်၊ အကူအညီပေးသူများသည် မြေပုံများနှင့် အလုပ်လုပ်ရန်သာမက အခြားသော kernel စွမ်းရည်များကိုပါ ရယူရန်လည်း ရှိပါသည်။ ဥပမာအားဖြင့်၊ BPF ပရိုဂရမ်များသည် ပက်ကေ့ခ်ျများကို အခြားအင်တာဖေ့စ်များသို့ ပေးပို့ရန်၊ perf ဖြစ်ရပ်များကို ဖန်တီးရန်၊ kernel တည်ဆောက်ပုံများကို ဝင်ရောက်ကြည့်ရှုရန် အစရှိသည်တို့ကို အသုံးပြုနိုင်သည်။
အနှစ်ချုပ်အားဖြင့်၊ BPF သည် kernel space ထဲသို့ မမှန်မကန်ပြုလုပ်ထားသော၊ ဆိုလိုသည်မှာ၊ verifier-tested၊ အသုံးပြုသူကုဒ်ကို kernel space ထဲသို့ ပေးဆောင်နိုင်သည်။ ဤကုဒ်သည် ခေါ်ဆိုမှုများနှင့် အသုံးပြုသူနေရာနှင့် ဒေတာဖလှယ်မှုအကြား အခြေအနေကို သိမ်းဆည်းနိုင်ပြီး ဤပရိုဂရမ်အမျိုးအစားက ခွင့်ပြုထားသော kernel ခွဲစနစ်များကိုလည်း အသုံးပြုခွင့်ရှိသည်။
၎င်းသည် BPF တွင် အားသာချက်အချို့ရှိသည်နှင့် နှိုင်းယှဉ်ပါက kernel modules မှပေးသောစွမ်းရည်များနှင့်ဆင်တူသည် (ဟုတ်ပါတယ်၊ သင်အလားတူအပလီကေးရှင်းများ၊ ဥပမာ၊ စနစ်ခြေရာခံခြင်းတို့ကိုသာ နှိုင်းယှဉ်နိုင်သည် - BPF ဖြင့် မထင်မှတ်ထားသောဒရိုက်ဗာကို သင်မရေးနိုင်ပါ)။ အနိမ့်ဆုံးဝင်ရောက်မှုအဆင့်ကို သင်မှတ်သားနိုင်သည် (BPF ကိုအသုံးပြုသည့် အချို့သော utilities များသည် kernel ပရိုဂရမ်းမင်းစွမ်းရည်ရှိရန်၊ သို့မဟုတ် ယေဘုယျအားဖြင့် ပရိုဂရမ်ရေးခြင်းစွမ်းရည်ရှိရန်မလိုအပ်ပါ)၊ runtime ဘေးကင်းရေး (စာရေးသောအခါတွင် စနစ်မဖောက်မပြန်သူများအတွက် မှတ်ချက်တွင် လက်မြှောက်လိုက်ပါ။ သို့မဟုတ် စမ်းသပ်ခြင်း modules) atomicity - modules များကို ပြန်လည်စတင်ချိန်တွင် စက်ရပ်သွားမည်ဖြစ်ပြီး BPF စနစ်ခွဲသည် မည်သည့်ဖြစ်ရပ်မှ လွတ်သွားခြင်းမရှိကြောင်း သေချာစေသည် (တရားမျှတစေရန်၊ ၎င်းသည် BPF ပရိုဂရမ်အမျိုးအစားအားလုံးအတွက် မမှန်ပါ)။
ထိုသို့သောစွမ်းရည်များရှိနေခြင်းကြောင့် BPF သည် kernel ကိုချဲ့ထွင်ရန်အတွက် universal tool တစ်ခုဖြစ်လာသည်၊ ၎င်းသည် လက်တွေ့တွင်အတည်ပြုထားသည့်အတိုင်းဖြစ်သည်- ပရိုဂရမ်အမျိုးအစားသစ်များကို BPF တွင် ပိုများလာလေလေ၊ ပရိုဂရမ်အသစ်များကို ပိုများလာလေလေ၊ ကုမ္ပဏီကြီးများက တိုက်ခိုက်ရေးဆာဗာများ 24×7 တွင် BPF ကို ပိုမိုအသုံးပြုလာလေလေ၊ ပိုများလာလေဖြစ်သည်။ startup များသည် BPF ကိုအခြေခံသည့်ဖြေရှင်းချက်များအပေါ်အခြေခံ၍ ၎င်းတို့၏လုပ်ငန်းကိုတည်ဆောက်သည်။ BPF ကို နေရာတိုင်းတွင် အသုံးပြုသည်- DDoS တိုက်ခိုက်မှုများကို ကာကွယ်ရာတွင်၊ SDN ကို ဖန်တီးခြင်း (ဥပမာ၊ Kubernetes အတွက် ကွန်ရက်များကို အကောင်အထည်ဖော်ခြင်း)၊ ပင်မစနစ် ခြေရာခံကိရိယာနှင့် စာရင်းဇယားစုဆောင်းသူ၊ ကျူးကျော်ဝင်ရောက်မှု ထောက်လှမ်းခြင်းစနစ်များနှင့် သဲပုံးစနစ်များ စသည်တို့ဖြစ်သည်။
ဆောင်းပါး၏ ခြုံငုံသုံးသပ်ချက် အပိုင်းကို ဤနေရာတွင် အပြီးသတ်၍ စင်စစ် စက်နှင့် BPF ဂေဟစနစ်ကို ပိုမိုအသေးစိတ်ကြည့်ကြပါစို့။
Digression: အသုံးအဆောင်များ
အောက်ပါကဏ္ဍများတွင် နမူနာများကို လုပ်ဆောင်နိုင်စေရန်အတွက် အနည်းဆုံး အသုံးအဆောင်များစွာ လိုအပ်နိုင်သည်၊ llvm
/clang
bpf အထောက်အပံ့နှင့် bpftool
... အပိုင်း၌
BPF Virtual Machine မှတ်ပုံတင်ခြင်းနှင့် ညွှန်ကြားချက်စနစ်
BPF ၏ ဗိသုကာပညာနှင့် အမိန့်ပေးစနစ်သည် ပရိုဂရမ်များကို C ဘာသာစကားဖြင့် ရေးသားမည်ဖြစ်ပြီး kernel အတွင်းသို့ ထည့်သွင်းပြီးနောက် မူရင်းကုဒ်သို့ ဘာသာပြန်ဆိုမည့်အချက်ကို ထည့်သွင်းစဉ်းစား၍ တီထွင်ခဲ့ခြင်းဖြစ်သည်။ ထို့ကြောင့်၊ မှတ်ပုံတင်အရေအတွက်နှင့် command အစုအဝေးကို ခေတ်မီစက်များ၏ သင်္ချာသဘောအရ လမ်းဆုံကို မျက်လုံးဖြင့် ရွေးချယ်ခဲ့သည်။ ထို့အပြင်၊ ဥပမာအားဖြင့်၊ ပရိုဂရမ်များပေါ်တွင် ကန့်သတ်ချက်အမျိုးမျိုးကို မကြာသေးမီအချိန်အထိ ကန့်သတ်မှုများပြုလုပ်ထားပြီး ကွင်းဆက်များနှင့် လုပ်ရိုးလုပ်စဉ်များကို ရေးရန်မဖြစ်နိုင်သေးဘဲ ညွှန်ကြားချက်အရေအတွက်မှာ 4096 အထိ ကန့်သတ်ထားပါသည် (ယခုအခါတွင် အခွင့်ထူးခံပရိုဂရမ်များသည် ညွှန်ကြားချက်ပေါင်း တစ်သန်းအထိ တင်နိုင်သည်)။
BPF တွင် အသုံးပြုသူဝင်ရောက်နိုင်သော 64-bit မှတ်ပုံတင်မှု ဆယ့်တစ်ခုရှိသည်။ r0
-r10
အစီအစဉ်ကောင်တာတစ်ခု။ မှတ်ပုံတင်ပါ။ r10
frame pointer ပါ၀င်ပြီး ဖတ်ရန်သာဖြစ်သည်။ ပရိုဂရမ်များသည် runtime တွင် 512-byte stack နှင့် maps ပုံစံဖြင့် မျှဝေထားသော memory ပမာဏ အကန့်အသတ်မရှိ ဝင်ရောက်နိုင်သည်။
BPF ပရိုဂရမ်များသည် ပရိုဂရမ်အမျိုးအစား kernel အထောက်အကူများ နှင့် မကြာသေးမီက ပုံမှန်လုပ်ဆောင်ချက်များကို လုပ်ဆောင်ရန် ခွင့်ပြုထားသည်။ ဟုခေါ်သော လုပ်ဆောင်ချက်တစ်ခုစီသည် မှတ်ပုံတင်များတွင် ဖြတ်သွားသော အကြောင်းပြချက်ငါးခုအထိ ယူနိုင်သည်။ r1
-r5
၊ နှင့် return value ကို ပေးပို့သည်။ r0
. လုပ်ဆောင်ချက်မှပြန်လာပြီးနောက်၊ စာရင်းသွင်းမှုများ၏အကြောင်းအရာများကိုအာမခံသည်။ r6
-r9
ပြောင်းလဲမည်မဟုတ်ပါ။
ထိရောက်သော ပရိုဂရမ်ဘာသာပြန်ဆိုမှုအတွက် စာရင်းသွင်းပါ။ r0
-r11
ပံ့ပိုးပေးထားသော ဗိသုကာအားလုံးအတွက် လက်ရှိဗိသုကာ၏ ABI အင်္ဂါရပ်များကို ထည့်သွင်းစဉ်းစားပြီး အစစ်အမှန်စာရင်းအင်းများထံ ထူးခြားစွာပုံဖော်ထားသည်။ ဥပမာအားဖြင့်၊ x86_64
စာရင်းများ r1
-r5
လုပ်ဆောင်ချက် ဘောင်များကို ဖြတ်သန်းရန် အသုံးပြုသော၊ ပေါ်တွင် ပြသထားသည်။ rdi
, rsi
, rdx
, rcx
, r8
လုပ်ဆောင်ချက်များကိုဖွင့်ရန် parameters များကိုဖြတ်သန်းရန်အသုံးပြုသည်။ 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
program execution ၏ရလဒ်နှင့် register တွင် return ပြန်ရန်လည်းသုံးသည်။ r1
ပရိုဂရမ်သည် အကြောင်းအရာဆီသို့ ညွှန်ပြချက်ကို ဖြတ်သွားသည် - ပရိုဂရမ်အမျိုးအစားပေါ် မူတည်၍ ၎င်းသည် ဥပမာ၊ ဖွဲ့စည်းပုံတစ်ခု ဖြစ်နိုင်သည်၊ struct xdp_md
struct __sk_buff
struct pt_regs
ထို့ကြောင့်၊ ကျွန်ုပ်တို့တွင် မှတ်ပုံတင်ခြင်း၊ kernel helpers၊ stack တစ်ခု၊ ဆက်စပ်ညွှန်ပြချက်တစ်ခုနှင့် မြေပုံများပုံစံဖြင့် မျှဝေထားသော memory အစုံရှိခဲ့ပါသည်။ ဒါတွေအားလုံးက ခရီးမှာ မလိုအပ်ပေမယ့်...
ဖော်ပြချက်ကို ဆက်ပြီး ဤအရာဝတ္ထုများနှင့် လုပ်ဆောင်ရန်အတွက် အမိန့်ပေးစနစ်အကြောင်း ဆွေးနွေးကြပါစို့။ အားလုံး (
ဒါဟာဖြစ်ပါတယ် Code
- ဤသည်မှာ ညွှန်ကြားချက်၏ ကုဒ်နံပါတ်၊ Dst
/Src
လက်ခံသူနှင့် အရင်းအမြစ်တို့၏ ကုဒ်နံပါတ်များ အသီးသီး၊ Off
- 16-bit signed indentation နှင့် Imm
အချို့သော ညွှန်ကြားချက်များတွင် အသုံးပြုသည့် 32-bit ကိန်းပြည့် (cBPF ကိန်းသေ K နှင့် ဆင်တူသည်)။ ြဖစ်သည်။ Code
အမျိုးအစား နှစ်မျိုးထဲမှ တစ်ခု ရှိသည်-
Instruction classes 0, 1, 2, 3 သည် memory နှင့်အလုပ်လုပ်ရန်အတွက် command များကိုသတ်မှတ်သည်။ သူတို BPF_LD
, BPF_LDX
, BPF_ST
, BPF_STX
အသီးသီး။ သင်တန်း 4, 7 (BPF_ALU
, BPF_ALU64
) ALU ညွှန်ကြားချက်အစုတစ်ခုဖြင့် ဖွဲ့စည်းသည်။ သင်တန်း 5 6 (BPF_JMP
, BPF_JMP32
) ခုန်လမ်းညွှန်ချက်များပါရှိသည်။
BPF ညွှန်ကြားချက်စနစ်ကို လေ့လာရန် နောက်ထပ်အစီအစဥ်မှာ အောက်ပါအတိုင်းဖြစ်သည်- ညွှန်ကြားချက်များနှင့် ၎င်းတို့၏ ကန့်သတ်ဘောင်များကို စေ့စေ့စပ်စပ်ဖော်ပြမည့်အစား၊ ဤကဏ္ဍရှိ ဥပမာအချို့ကို ကျွန်ုပ်တို့ကြည့်ရှုမည်ဖြစ်ပြီး ၎င်းတို့ထံမှ ညွှန်ကြားချက်များသည် အမှန်တကယ်အလုပ်လုပ်ပုံနှင့် မည်ကဲ့သို့လုပ်ဆောင်ရမည်ကို ရှင်းလင်းပြတ်သားစွာသိရှိနိုင်မည်ဖြစ်သည်။ BPF အတွက် မည်သည့် binary ဖိုင်ကိုမဆို ကိုယ်တိုင် disassemble လုပ်ပါ။ ဆောင်းပါးပါ အကြောင်းအရာကို နောက်ပိုင်းတွင် စုစည်းရန်၊ Verifier၊ JIT compiler၊ classic BPF ဘာသာပြန်ဆိုခြင်းအပြင် မြေပုံများကို လေ့လာသည့်အခါ၊ ခေါ်ဆိုမှုလုပ်ဆောင်ချက်များ အစရှိသည့် ကဏ္ဍများရှိ တစ်ဦးချင်းညွှန်ကြားချက်များနှင့်လည်း တွေ့ဆုံပါမည်။
တစ်ဦးချင်းစီညွှန်ကြားချက်များအကြောင်းပြောသောအခါ၊ ကျွန်ုပ်တို့သည် အဓိကဖိုင်များကို ကိုးကားပါမည်။ bpf.h
bpf_common.h
ဥပမာ- သင့်ခေါင်းတွင် BPF ကို ဖြုတ်ပါ။
ပရိုဂရမ်တစ်ခုကို စုစည်းထားတဲ့ ဥပမာကို ကြည့်ရအောင် readelf-example.c
ရလဒ် binary ကိုကြည့်ပါ။ မူရင်းအကြောင်းအရာကို ဖော်ပြပါမည်။ readelf-example.c
အောက်တွင်၊ ကျွန်ုပ်တို့သည် ၎င်း၏ယုတ္တိဗေဒကို binary ကုဒ်များမှ ပြန်လည်ရယူပြီးနောက်၊
$ 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
indentation တစ်ခုဖြစ်ပြီး ကျွန်ုပ်တို့၏ program သည် command လေးခုပါဝင်သည်-
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
Command Code များသည် တူညီပါသည်။ b7
, 15
, b7
и 95
. သိသာထင်ရှားသောသုံးဘစ်များသည် ညွှန်ကြားချက်အတန်းဖြစ်ကြောင်း သတိရပါ။ ကျွန်ုပ်တို့၏အခြေအနေတွင်၊ ညွှန်ကြားချက်အားလုံး၏စတုတ္ထဘစ်သည် ဗလာဖြစ်သောကြောင့် ညွှန်ကြားချက်အတန်းများသည် 7၊ 5၊ 7၊ 5 အသီးသီးဖြစ်သည်။ Class 7 သည် BPF_ALU64
နှင့် 5 ဖြစ်ပါ သည်။ BPF_JMP
. အတန်းနှစ်ခုလုံးအတွက်၊ ညွှန်ကြားချက်ဖော်မတ်သည် တူညီသည် (အထက်တွင်ကြည့်ပါ) နှင့် ကျွန်ုပ်တို့သည် ဤကဲ့သို့သော ကျွန်ုပ်တို့၏ပရိုဂရမ်ကို ပြန်လည်ရေးသားနိုင်သည် (တစ်ချိန်တည်းတွင် ကျန်ရှိသောကော်လံများကို လူသားပုံစံဖြင့် ပြန်လည်ရေးသားပါမည်)
Op S Class Dst Src Off Imm
b 0 ALU64 0 0 0 1
1 0 JMP 0 1 1 0
b 0 ALU64 0 0 0 2
9 0 JMP 0 0 0 0
စစ်ဆင်ရေး b
класса ALU64
- က s
(ရင်းမြစ်) ထို့နောက်တန်ဖိုးကိုအရင်းအမြစ်မှတ်ပုံတင်မှယူသည်၊ ကျွန်ုပ်တို့ကိစ္စတွင်၊ ၎င်းကိုသတ်မှတ်မထားပါက၊ ထို့နောက်တန်ဖိုးကိုအကွက်မှယူသည်။ Imm
. ဒါကြောင့် ပထမ နဲ့ တတိယ ညွှန်ကြားချက်မှာ စစ်ဆင်ရေးကို လုပ်ဆောင်ပါတယ်။ r0 = Imm
. ထို့အပြင် JMP class 1 လုပ်ဆောင်ချက်သည် S
သုညဖြစ်ပြီး၊ ၎င်းသည် ရင်းမြစ်မှတ်ပုံတင်ခြင်း၏တန်ဖိုးကို အကွက်နှင့် နှိုင်းယှဉ်သည်။ Imm
. တန်ဖိုးများ တိုက်ဆိုင်နေပါက အသွင်ကူးပြောင်းမှု ဖြစ်ပေါ်သည်။ PC + Off
ဘယ်မှာ PC
ထုံးစံအတိုင်း၊ နောက်ညွှန်ကြားချက်၏လိပ်စာပါရှိသည်။ နောက်ဆုံးအနေနဲ့ JMP Class 9 Operation ဖြစ်ပါ တယ်။ 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
ပရိုဂရမ်သည် kernel မှ အကြောင်းအရာဆီသို့ ညွှန်ပြချက်တစ်ခုနှင့် မှတ်ပုံတင်ခြင်းတွင် ဖြတ်သန်းသည်။ r0
တန်ဖိုးကို kernel သို့ပြန်သွားသည်၊ ထို့နောက် အကြောင်းအရာသို့ညွှန်ပြသည့်ညွှန်ပြချက်သည် သုညဖြစ်လျှင် ကျွန်ုပ်တို့သည် 1 သို့ပြန်သွားသည်၊ သို့မဟုတ်မဟုတ်လျှင် - 2 ကိုပြန်ပေးသည်။ အရင်းအမြစ်ကိုကြည့်ခြင်းဖြင့် ကျွန်ုပ်တို့မှန်ကြောင်းစစ်ဆေးကြပါစို့။
$ cat readelf-example.c
int foo(void *ctx)
{
return ctx ? 2 : 1;
}
ဟုတ်တယ်၊ အဲဒါက အဓိပ္ပါယ်မရှိတဲ့ ပရိုဂရမ်တစ်ခုပါ၊ ဒါပေမယ့် ရိုးရှင်းတဲ့ ညွှန်ကြားချက်လေးခုကို ဘာသာပြန်ပါတယ်။
ခြွင်းချက် ဥပမာ- 16-byte ညွှန်ကြားချက်
အချို့သောညွှန်ကြားချက်များသည် 64 bits ထက်ပိုယူကြောင်း အစောပိုင်းတွင်ဖော်ပြထားသည်။ ဤသည်မှာ ဥပမာအားဖြင့် ညွှန်ကြားချက်များနှင့် သက်ဆိုင်ပါသည်။ lddw
(ကုတ်= 0x18
= BPF_LD
BPF_DW
BPF_IMM
Imm
. အမှန်က အဲဒါ Imm
32 အရွယ်အစားရှိပြီး စကားလုံးနှစ်လုံးသည် 64 bits ဖြစ်သောကြောင့် 64-bit ညွှန်ကြားချက်တစ်ခုတွင် မှတ်ပုံတင်တစ်ခုသို့ 64-bit ချက်ချင်းတန်ဖိုးကို တင်ခြင်းသည် အလုပ်မဖြစ်ပါ။ ဒါကိုလုပ်ဖို့၊ အကွက်ထဲမှာ 64-bit တန်ဖိုးရဲ့ ဒုတိယအပိုင်းကို သိမ်းဆည်းဖို့ ကပ်လျက်ညွှန်ကြားချက်နှစ်ခုကို အသုံးပြုပါတယ်။ 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 ပရိုဂရမ်တစ်ခုတွင် ညွှန်ကြားချက်နှစ်ခုသာ ရှိပါသည်။
Binary Disassm
18000000 ddccbbaa 00000000 44332211 r0 = Imm[0]|Imm[1]
95000000 00000000 exit
ညွှန်ကြားချက်တွေနဲ့ ပြန်တွေ့မယ်။ lddw
နေရာပြောင်းရွှေ့ခြင်းနှင့် မြေပုံများနှင့် လုပ်ဆောင်ခြင်းအကြောင်း ပြောဆိုသောအခါ၊
ဥပမာ- စံကိရိယာများကို အသုံးပြု၍ BPF ကို ဖြုတ်တပ်ခြင်း။
ထို့ကြောင့်၊ ကျွန်ုပ်တို့သည် BPF ဒွိကုဒ်များကိုဖတ်ရန် သင်ယူခဲ့ပြီး လိုအပ်ပါက မည်သည့်ညွှန်ကြားချက်ကိုမဆို ပိုင်းခြားရန် အဆင်သင့်ဖြစ်နေပါပြီ။ သို့သော်၊ ဥပမာအားဖြင့်၊ ဥပမာအားဖြင့် ပရိုဂရမ်များကို စံကိရိယာများသုံးပြီး ပရိုဂရမ်များကို ဖြုတ်ထုတ်ရန် လက်တွေ့တွင် ပိုမိုအဆင်ပြေပြီး ပိုမိုမြန်ဆန်သည်ဟု ဆိုရကျိုးနပ်ပါသည်။
$ llvm-objdump -d x64.o
Disassembly of section .text:
0000000000000000 <foo>:
0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
2: 95 00 00 00 00 00 00 00 exit
BPF အရာဝတ္ထုများ၏ ဘဝသံသရာ၊ bpffs ဖိုင်စနစ်
(ဒီပုဒ်မခွဲမှာ ဖော်ပြထားတဲ့ အသေးစိတ်အချက်တချို့ကို ပထမဆုံး လေ့လာခဲ့တယ်။
BPF အရာဝတ္ထုများ - ပရိုဂရမ်များနှင့် မြေပုံများ - ညွှန်ကြားချက်များကို အသုံးပြု၍ အသုံးပြုသူနေရာမှ ဖန်တီးထားသည်။ BPF_PROG_LOAD
и BPF_MAP_CREATE
စနစ်ခေါ်ဆိုမှု bpf(2)
၊ နောက်အပိုင်းမှာ အဲဒါဘယ်လိုဖြစ်သွားလဲ အတိအကျပြောပါမယ်။ ၎င်းသည် kernel ဒေတာတည်ဆောက်ပုံများနှင့် ၎င်းတို့တစ်ခုစီအတွက် ဖန်တီးပေးသည်။ refcount
(ကိုးကားမှုအရေအတွက်) ကို တစ်ခုအဖြစ် သတ်မှတ်ထားပြီး အရာဝတ္တုကို ညွှန်ပြသည့် ဖိုင်ဖော်ပြချက်အား သုံးစွဲသူထံ ပြန်ပေးပါသည်။ လက်ကိုင်ကိုပိတ်ပြီးနောက် refcount
အရာဝတ္တုကို တစ်ခုပြီးတစ်ခု လျှော့ချပြီး သုညသို့ရောက်သောအခါ၊ အရာဝတ္တုသည် ပျက်စီးသွားပါသည်။
အကယ်၍ ပရိုဂရမ်သည် မြေပုံများကို အသုံးပြုပါက၊ refcount
ပရိုဂရမ်ကို တင်ပြီးနောက် ဤမြေပုံများသည် တစ်ခုနှင့်တစ်ခု တိုးလာသည်၊ i.e. ၎င်းတို့၏ ဖိုင်ဖော်ပြချက်များအား အသုံးပြုသူ လုပ်ငန်းစဉ်မှ ပိတ်ထားနိုင်ပါသည်။ refcount
သုညဖြစ်သွားမည်မဟုတ်ပါ
ပရိုဂရမ်တစ်ခုကို အောင်မြင်စွာ တင်ပြီးနောက်၊ ကျွန်ုပ်တို့သည် ၎င်းကို ဖြစ်ရပ် ဂျင်နရေတာ တစ်မျိုးမျိုးသို့ တွဲပေးလေ့ရှိသည်။ ဥပမာအားဖြင့်၊ ကျွန်ုပ်တို့သည် အဝင်အထုပ်များကို လုပ်ဆောင်ရန် သို့မဟုတ် အချို့နှင့် ချိတ်ဆက်ရန် ၎င်းကို ကွန်ရက်ချိတ်ဆက်မှုတွင် ထည့်နိုင်သည်။ tracepoint
core ၌။ ဤအချိန်တွင်၊ ရည်ညွှန်းကောင်တာသည်လည်း တစ်ခုပြီးတစ်ခုတိုးလာမည်ဖြစ်ပြီး loader ပရိုဂရမ်တွင် ဖိုင်ဖော်ပြချက်အား ကျွန်ုပ်တို့ပိတ်နိုင်မည်ဖြစ်သည်။
bootloader ကို အခု ပိတ်လိုက်ရင် ဘာဖြစ်မလဲ။ ၎င်းသည် event generator (hook) အမျိုးအစားပေါ်တွင်မူတည်သည်။ loader ပြီးသွားသောအခါတွင် network hooks များအားလုံးသည် ရှိနေလိမ့်မည်၊ ၎င်းတို့သည် global hooks များဖြစ်သည်။ ဥပမာအားဖြင့်၊ ၎င်းတို့ကို ဖန်တီးသည့် လုပ်ငန်းစဉ်ကို ရပ်ဆိုင်းပြီးနောက် ခြေရာခံပရိုဂရမ်များကို ထုတ်လွှင့်ပေးမည် (ထို့ကြောင့် ဒေသဆိုင်ရာ၊ “ဒေသတွင်းမှ လုပ်ငန်းစဉ်အထိ” ဟုခေါ်သည်)။ နည်းပညာအရ၊ local hooks များသည် user space တွင် သက်ဆိုင်သော file descriptor တစ်ခု အမြဲရှိ၍ ၎င်း process ကို ပိတ်သောအခါတွင် ပိတ်သော်လည်း global hooks သည် မဖြစ်ပါ။ အောက်ဖော်ပြပါပုံတွင်၊ အနီရောင်ကြက်ခြေခတ်များကို အသုံးပြု၍ loader ပရိုဂရမ်အား ရပ်စဲခြင်းသည် ဒေသတွင်းနှင့် ကမ္ဘာလုံးဆိုင်ရာချိတ်များကိစ္စတွင် အရာဝတ္ထုများ၏ သက်တမ်းကို မည်သို့အကျိုးသက်ရောက်ကြောင်း ပြသရန် ကြိုးစားပါသည်။
ပြည်တွင်း နှင့် ကမ္ဘာလုံးဆိုင်ရာ ချိတ်များကြား အဘယ်ကြောင့် ကွာခြားမှု ရှိသနည်း။ ကွန်ရက်ပရိုဂရမ်အမျိုးအစားအချို့ကို လုပ်ဆောင်ခြင်းသည် အသုံးပြုသူနေရာလွတ်မရှိဘဲ အဓိပ္ပါယ်ရှိစေသည်၊ ဥပမာ၊ DDoS ကာကွယ်ရေးကို စိတ်ကူးကြည့်ပါ - bootloader သည် စည်းမျဉ်းများကိုရေးသားပြီး BPF ပရိုဂရမ်အား ကွန်ရက်ချိတ်ဆက်မှုဖြင့် ချိတ်ဆက်ပေးပြီးနောက် bootloader သည် သူ့ကိုယ်သူသတ်သွားနိုင်သည်။ တစ်ဖက်တွင်၊ ဆယ်မိနစ်အတွင်း သင့်ဒူးပေါ်တွင် သင်ရေးခဲ့သော အမှားရှာခြေရာခံပရိုဂရမ်ကို စိတ်ကူးကြည့်ပါ - ၎င်းပြီးသွားသောအခါ၊ စနစ်တွင် အမှိုက်မကျန်တော့ကြောင်းကို လိုလားပြီး ဒေသဆိုင်ရာ ချိတ်များကို သေချာစေမည်ဖြစ်သည်။
အခြားတစ်ဖက်တွင်၊ သင်သည် kernel ရှိ ခြေရာခံအမှတ်တစ်ခုသို့ ချိတ်ဆက်ပြီး နှစ်ပေါင်းများစွာ စာရင်းအင်းများ စုဆောင်းလိုကြောင်း စိတ်ကူးကြည့်ပါ။ ဤကိစ္စတွင်၊ သင်သည် အသုံးပြုသူအပိုင်းကို အပြီးသတ်ပြီး ကိန်းဂဏန်းစာရင်းအင်းများသို့ အခါအားလျော်စွာ ပြန်သွားလိုမည်ဖြစ်သည်။ bpf ဖိုင်စနစ်သည် ဤအခွင့်အရေးကို ပေးသည်။ ၎င်းသည် BPF အရာဝတ္တုများကို ရည်ညွှန်းကာ တိုးပွားစေသည့် ဖိုင်များဖန်တီးမှုကို ခွင့်ပြုသည့် မန်မိုရီ-သီးသန့် pseudo-ဖိုင်စနစ်တစ်ခုဖြစ်သည်။ refcount
အရာဝတ္ထုများ။ ယင်းနောက်တွင်၊ loader သည် ထွက်နိုင်ပြီး၊ ၎င်းဖန်တီးထားသော အရာဝတ္ထုများသည် အသက်ရှင်နေမည်ဖြစ်သည်။
BPF အရာဝတ္ထုများကိုရည်ညွှန်းသည့် bpffs တွင် ဖိုင်များဖန်တီးခြင်းကို "ပင်ထိုးခြင်း" ဟုခေါ်သည် (အောက်ပါစာပိုဒ်တိုများအတိုင်း - "လုပ်ငန်းစဉ်သည် BPF ပရိုဂရမ် သို့မဟုတ် မြေပုံကို ပင်ထိုးနိုင်သည်")။ BPF အရာဝတ္ထုများအတွက် ဖိုင်အရာဝတ္ထုများကို ဖန်တီးခြင်းသည် ဒေသတွင်းရှိ အရာဝတ္တုများ၏ သက်တမ်းကို တိုးစေရုံသာမက ကမ္ဘာလုံးဆိုင်ရာ အရာဝတ္ထုများ၏ အသုံးပြုနိုင်မှုအတွက်လည်း အဓိပ္ပာယ်ရှိပါသည် - ကမ္ဘာလုံးဆိုင်ရာ DDoS ကာကွယ်ရေးပရိုဂရမ်ဖြင့် နမူနာသို့ ပြန်သွားခြင်းဖြင့် စာရင်းဇယားများကို ကြည့်ရှုနိုင်စေလိုပါသည်။ အခါအားလျော်စွာ။
BPF ဖိုင်စနစ်သည် များသောအားဖြင့် တပ်ဆင်ထားသည်။ /sys/fs/bpf
ဥပမာအားဖြင့်၊ ဤကဲ့သို့ စက်တွင်း၌လည်း တပ်ဆင်နိုင်သည်။
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint
ဖိုင်စနစ်အမည်များကို command ဖြင့်ဖန်တီးထားသည်။ 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
ယခု utility ကို အသုံးပြု၍ ကျွန်ုပ်တို့၏ ပရိုဂရမ်ကို ဒေါင်းလုဒ်လုပ်ကြပါစို့ bpftool
ပူးတွဲပါစနစ်ခေါ်ဆိုမှုများကို ကြည့်ရှုပါ။ bpf(2)
(မသက်ဆိုင်သောလိုင်းအချို့ကို strace output မှဖယ်ရှားခဲ့သည်)
$ 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
, kernel မှ ဖိုင်ဖော်ပြချက်တစ်ခုကို လက်ခံရရှိခဲ့သည်။ 3
command ကို အသုံးပြု BPF_OBJ_PIN
ဤဖိုင်ဖော်ပြချက်အား ဖိုင်တစ်ခုအဖြစ် ပင်ထိုးထားသည်။ "bpf-mountpoint/test"
. ပြီးရင် bootloader ပရိုဂရမ်ကိုလုပ်ပါ။ bpftool
အလုပ်ပြီးသွားသော်လည်း၊ ကျွန်ုပ်တို့၏ပရိုဂရမ်သည် မည်သည့် network interface နှင့်မျှ မတွဲထားသော်လည်း၊
$ 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 ပရိုဂရမ် အမျိုးအစားများသည် သင့်အား ပရိုဂရမ်ကို လျင်မြန်စွာ အစားထိုးနိုင်စေသည်၊ ဆိုလိုသည်မှာ၊ sequence atomicity ကိုပေးသည်။ replace = detach old program, attach new program
. ဤကိစ္စတွင်၊ ပရိုဂရမ်၏ ဗားရှင်းဟောင်း၏ တက်ကြွသော ဖြစ်ရပ်များအားလုံးသည် ၎င်းတို့၏ အလုပ်ပြီးဆုံးမည်ဖြစ်ပြီး၊ ဖြစ်ရပ်ကိုင်တွယ်သူအသစ်များကို ပရိုဂရမ်အသစ်မှ ဖန်တီးမည်ဖြစ်ပြီး၊ ဤနေရာတွင် "အက်တမ်" ဆိုသည်မှာ ဖြစ်ရပ်တစ်ခုမျှ လွတ်သွားမည်မဟုတ်ကြောင်း ဆိုလိုပါသည်။
အစီအစဉ်များကို ပွဲသတင်းရင်းမြစ်များသို့ ချိတ်ဆက်ခြင်း။
ဤဆောင်းပါးတွင်၊ ပရိုဂရမ်များကို ပွဲအရင်းအမြစ်များနှင့် ချိတ်ဆက်ခြင်းအား သီးခြားဖော်ပြမည်မဟုတ်ပါ၊ အဘယ်ကြောင့်ဆိုသော် ၎င်းကို သီးခြားပရိုဂရမ်အမျိုးအစားတစ်ခု၏အခြေအနေတွင် လေ့လာခြင်းသည် အဓိပ္ပာယ်ရှိသောကြောင့်ဖြစ်သည်။ စင်တီမီတာ။
bpf စနစ်ခေါ်ဆိုမှုကို အသုံးပြု၍ အရာဝတ္ထုများကို ကိုင်တွယ်ခြင်း။
BPF အစီအစဉ်များ
BPF အရာဝတ္ထုအားလုံးကို စနစ်ခေါ်ဆိုမှုဖြင့် အသုံးပြုသူနေရာမှ ဖန်တီးပြီး စီမံခန့်ခွဲပါသည်။ bpf
အောက်ဖော်ပြပါ နမူနာပုံစံရှိခြင်း၊
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
ဒါကတော့ အဖွဲ့ပါ။ cmd
အမျိုးအစား၏တန်ဖိုးများထဲမှတစ်ခုဖြစ်သည်။ enum bpf_cmd
attr
- တိကျသောပရိုဂရမ်တစ်ခုအတွက် ဘောင်များနှင့် ညွှန်ပြချက် size
— pointer အရ အရာဝတ္ထုအရွယ်အစား၊ i.e. များသောအားဖြင့် ဒီလိုပါ။ sizeof(*attr)
. kernel 5.8 တွင် system ကိုခေါ်ဆိုသည်။ bpf
34 ကွဲပြားခြားနားသော commands များကိုထောက်ခံပါတယ်။ union bpf_attr
လိုင်း 200 ရှိသည်။ သို့သော် ကျွန်ုပ်တို့သည် ဆောင်းပါးများစွာတစ်လျှောက်တွင် commands များနှင့် parameters များကို ရင်းနှီးပြီးသားဖြစ်သောကြောင့် ၎င်းကိုကျွန်ုပ်တို့မထိတ်လန့်သင့်ပါ။
အသင်းနဲ့ စလိုက်ရအောင် BPF_PROG_LOAD
BPF ပရိုဂရမ်များကိုဖန်တီးပေးသော၊ BPF ညွှန်ကြားချက်အစုံကိုယူကာ kernel ထဲသို့ထည့်သည်။ ဒေါင်းလုဒ်လုပ်နေစဉ်တွင်၊ verifier ကိုစတင်ပြီး၊ ထို့နောက် JIT compiler နှင့်၊ အောင်မြင်စွာလုပ်ဆောင်ပြီးနောက်၊ ပရိုဂရမ်ဖိုင်ဖော်ပြချက်အား အသုံးပြုသူထံ ပြန်ပို့ပေးပါသည်။ အရင်အပိုင်းမှာ သူဘာတွေဆက်ဖြစ်မလဲဆိုတာကို ကျွန်တော်တို့တွေ့ခဲ့ရပါတယ်။
ရိုးရှင်းသော 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();
}
ပရိုဂရမ်တစ်ခုရှိ စိတ်ဝင်စားဖွယ်ဖြစ်ရပ်များသည် array တစ်ခု၏ အဓိပ္ပါယ်ဖွင့်ဆိုချက်ဖြင့် စတင်သည်။ insns
- စက်ကုဒ်တွင်ကျွန်ုပ်တို့၏ BPF ပရိုဂရမ်။ ဤကိစ္စတွင်၊ BPF ပရိုဂရမ်၏ညွှန်ကြားချက်တစ်ခုစီကို ဖွဲ့စည်းပုံတွင် ထည့်သွင်းထားသည်။ bpf_insn
insns
ညွှန်ကြားချက်များကိုလိုက်နာပါ။ r0 = 2
, ဒုတိယ - exit
.
ဆုတ်ခွာ။ kernel သည် စက်ကုဒ်များရေးသားရန်အတွက် ပိုမိုအဆင်ပြေသော မက်ခရိုများကို သတ်မှတ်ပေးပြီး kernel header ဖိုင်ကို အသုံးပြုခြင်း၊ tools/include/linux/filter.h
ရေးနိုင်ခဲ့တယ်။
struct bpf_insn insns[] = {
BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
BPF_EXIT_INSN()
};
သို့သော် BPF ပရိုဂရမ်များကို မူရင်းကုဒ်ဖြင့် ရေးသားခြင်းသည် kernel တွင် စမ်းသပ်မှုများနှင့် BPF အကြောင်း ဆောင်းပါးများကို ရေးသားရန်အတွက်သာ လိုအပ်သောကြောင့်၊ အဆိုပါ macros မရှိတော့ခြင်းကြောင့် developer ၏ ဘ၀ကို အမှန်တကယ် မရှုပ်ထွေးစေပါ။
BPF ပရိုဂရမ်ကို သတ်မှတ်ပြီးနောက်၊ ၎င်းကို kernel ထဲသို့ တင်ခြင်းဆီသို့ ဆက်လက်သွားပါမည်။ ကျွန်ုပ်တို့၏ သေးငယ်သော ကန့်သတ်ဘောင်များ attr
ပရိုဂရမ် အမျိုးအစား၊ သတ်မှတ် ချက် ညွှန်ကြားချက် အရေအတွက်၊ လိုအပ်သော လိုင်စင်နှင့် အမည်တို့ ပါဝင်သည်။ "woo"
ဒေါင်းလုဒ်လုပ်ပြီးနောက် စနစ်ပေါ်ရှိ ကျွန်ုပ်တို့၏ပရိုဂရမ်ကို ရှာဖွေရန် ကျွန်ုပ်တို့အသုံးပြုသည်။ ကတိပြုထားသည့်အတိုင်း ပရိုဂရမ်ကို စနစ်ခေါ်ဆိုမှုကို အသုံးပြု၍ စနစ်ထဲသို့ ထည့်သွင်းထားသည်။ bpf
.
ပရိုဂရမ်၏အဆုံးတွင် ကျွန်ုပ်တို့သည် payload ကို တုပသည့် အဆုံးမရှိသော ကွင်းဆက်တစ်ခုဖြင့် အဆုံးသတ်ပါသည်။ ၎င်းမရှိပါက၊ ကျွန်ုပ်တို့ထံပြန်ပေးသည့်စနစ်ခေါ်ဆိုမှုပိတ်သွားသောအခါတွင် kernel သည် ပရိုဂရမ်ကို သတ်ပစ်မည်ဖြစ်သည်။ bpf
၊ ၎င်းကိုစနစ်တွင်ကျွန်ုပ်တို့မြင်မည်မဟုတ်ပါ။
ကောင်းပြီ၊ ကျွန်ုပ်တို့သည် စမ်းသပ်ရန် အဆင်သင့်ဖြစ်နေပါပြီ။ အောက်မှာ ပရိုဂရမ်ကို စုပြီး run ကြရအောင် 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()
. ကျွန်ုပ်တို့၏ ပရိုဂရမ်ကို စနစ်တွင် ရှာဖွေကြည့်ကြပါစို့။ ဒါကိုလုပ်ဖို့ ငါတို့က တခြား terminal ကိုသွားပြီး utility ကိုသုံးမယ်။ 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
ပရိုဂရမ်ကိုညွှန်ပြသော open file descriptor တစ်ခုရှိသည် (ထို့ပြင် အကယ်၍ simple-prog
ဒါဆိုရင် အလုပ်ပြီးမယ်။ woo
ပျောက်သွားမှာပါ)။ မျှော်လင့်ထားသည့်အတိုင်း အစီအစဉ် woo
BPF ဗိသုကာရှိ ဒွိကုဒ်များ၏ 16 bytes - ညွှန်ကြားချက်နှစ်ခု - ယူသော်လည်း ၎င်း၏ မူရင်းပုံစံ (x86_64) သည် 40 bytes ရှိပြီးဖြစ်သည်။ ကျွန်ုပ်တို့၏အစီအစဉ်ကို ၎င်း၏မူရင်းပုံစံဖြင့် ကြည့်ကြပါစို့။
# bpftool prog dump xlated id 390
0: (b7) r0 = 2
1: (95) exit
အံ့သြစရာမရှိပါ။ ယခု JIT compiler မှထုတ်ပေးသောကုဒ်ကို ကြည့်ကြပါစို့။
# 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 compiler မှ ထည့်ထားသော စကားတစ်ခွန်းနှင့် စာတိုလေးသည် လိုအပ်ပါသည်။
Maps ကို
BPF ပရိုဂရမ်များသည် အခြား BPF ပရိုဂရမ်များနှင့် အသုံးပြုသူနေရာရှိ ပရိုဂရမ်များအတွက် နှစ်ခုလုံးရနိုင်သော ဖွဲ့စည်းပုံမှတ်ဉာဏ်ဧရိယာများကို အသုံးပြုနိုင်သည်။ ဤအရာဝတ္ထုများကို မြေပုံများဟုခေါ်ပြီး ဤအပိုင်းတွင် စနစ်ခေါ်ဆိုမှုတစ်ခုအသုံးပြု၍ ၎င်းတို့ကို ကိုင်တွယ်နည်းကို ပြသပါမည်။ bpf
.
မြေပုံများ၏ စွမ်းဆောင်နိုင်ရည်များသည် မျှဝေထားသော မှတ်ဉာဏ်သို့ ဝင်ရောက်ရန်သာ အကန့်အသတ်မရှိဟု ချက်ချင်း ဆိုကြပါစို့။ ဥပမာ၊ BPF ပရိုဂရမ်များသို့ ညွှန်ပြချက်များ သို့မဟုတ် ကွန်ရက်ကြားခံများဆီသို့ ညွှန်ပြချက်များ၊ perf ဖြစ်ရပ်များနှင့် လုပ်ဆောင်ရန်အတွက် မြေပုံများ စသည်တို့ပါ၀င်သော အထူးရည်ရွယ်ချက်မြေပုံများ ရှိပါသည်။ စာဖတ်သူကို မရောယှက်မိစေရန် ဤနေရာတွင် ၎င်းတို့အကြောင်း မပြောလိုပါ။ ၎င်းအပြင်၊ ကျွန်ုပ်တို့သည် ကျွန်ုပ်တို့၏နမူနာများအတွက် အရေးမကြီးသောကြောင့် ထပ်တူပြုခြင်းပြဿနာများကို လျစ်လျူရှုပါသည်။ ရရှိနိုင်သောမြေပုံအမျိုးအစားများစာရင်းအပြည့်အစုံကို တွင်တွေ့နိုင်ပါသည်။ <linux/bpf.h>
BPF_MAP_TYPE_HASH
.
အကယ်၍ သင်သည် hash table ကိုဖန်တီးပါက C++ ဟုပြောနိုင်သည်။ unordered_map<int,long> woo
ရုရှားလိုဆိုလိုသည်မှာ “စားပွဲတစ်လုံးလိုတယ်။ woo
အကန့်အသတ်မရှိအရွယ်အစား၊ သော့များသည် အမျိုးအစားဖြစ်သည်။ int
, နှင့်တန်ဖိုးများအမျိုးအစားများဖြစ်ကြသည်။ long
“ BPF hash table တစ်ခုကို ဖန်တီးရန်အတွက်၊ ကျွန်ုပ်တို့သည် ဇယား၏ အများဆုံးအရွယ်အစားကို သတ်မှတ်ရမည်ဖြစ်ပြီး၊ သော့အမျိုးအစားများနှင့် တန်ဖိုးများကို သတ်မှတ်မည့်အစား၊ ၎င်းတို့၏ အရွယ်အစားများကို bytes သတ်မှတ်ရန် လိုအပ်သည်မှတပါး၊ . မြေပုံများဖန်တီးရန် command ကိုအသုံးပြုပါ။ 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
သော့များနှင့် အရွယ်အစားတန်ဖိုးများပါရှိသော hash table တစ်ခုလိုပါသည်။ 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)
.
ယခုကျွန်ုပ်တို့၏ပရိုဂရမ်ကိုနောက်ခံသို့ပို့ပါ သို့မဟုတ် အခြား terminal ကိုဖွင့်ပြီး utility ကိုအသုံးပြု၍ ကျွန်ုပ်တို့၏အရာဝတ္ထုကိုကြည့်ရှုကြပါစို့ 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
.
ယခုကျွန်ုပ်တို့၏ hash table ဖြင့်ကစားနိုင်ပါပြီ။ ၎င်း၏အကြောင်းအရာများကိုကြည့်ရှုကြပါစို့။
$ 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
ဟူး! ကျွန်ုပ်တို့သည် အစိတ်အပိုင်းတစ်ခုကို ထည့်နိုင်ခဲ့သည်။ ဤအရာကိုလုပ်ဆောင်ရန် ကျွန်ုပ်တို့သည် byte အဆင့်တွင် လုပ်ဆောင်ရမည်ကို သတိပြုပါ။ bptftool
hash table ရှိ တန်ဖိုးများသည် မည်သည့်အမျိုးအစားဖြစ်သည်ကို မသိပါ။ (ဤအသိပညာကို 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
ပထမဦးစွာ ကျွန်ုပ်တို့သည် command ကိုအသုံးပြု၍ ၎င်း၏ကမ္ဘာလုံးဆိုင်ရာ ID ဖြင့်မြေပုံကိုဖွင့်ပါ။ BPF_MAP_GET_FD_BY_ID
и bpf(2)
descriptor 3 ကို ကျွန်ုပ်တို့ထံ ပြန်ပေးခဲ့သည်။ နောက်ထပ် command ကို အသုံးပြုပါ။ BPF_MAP_GET_NEXT_KEY
ဖြတ်သွားခြင်းအားဖြင့် ဇယားထဲမှာ ပထမဆုံးသော့ကို တွေ့တယ်။ NULL
"ယခင်" သော့ကိုညွှန်ပြသည့်အနေဖြင့်။ ငါတို့မှာ သော့ရှိရင် ငါတို့ လုပ်နိုင်တယ်။ BPF_MAP_LOOKUP_ELEM
တန်ဖိုးတစ်ခုကို pointer တစ်ခုသို့ ပြန်ပေးသည်။ 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 နှင့် command ဖြင့်ကျွန်ုပ်တို့၏မြေပုံကိုဖွင့်ပါ။ BPF_MAP_UPDATE_ELEM
element ကို overwrite လုပ်သည်။
ထို့ကြောင့် ပရိုဂရမ်တစ်ခုမှ hash table တစ်ခုကို ဖန်တီးပြီးနောက်၊ ကျွန်ုပ်တို့သည် ၎င်း၏အကြောင်းအရာများကို အခြားတစ်ခုမှ ဖတ်နိုင်၊ ရေးသားနိုင်မည်ဖြစ်သည်။ အကယ်၍ ကျွန်ုပ်တို့သည် ၎င်းကို command line မှ လုပ်ဆောင်နိုင်ပါက၊ စနစ်ရှိ အခြားသော ပရိုဂရမ်များက ၎င်းကို လုပ်ဆောင်နိုင်သည်ကို သတိပြုပါ။ အထက်ဖော်ပြပါ command များအပြင် အသုံးပြုသူနေရာမှ မြေပုံများနှင့် အလုပ်လုပ်ခြင်း၊
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
- ဤသည်မြေပုံမှတန်ဖိုးအားလုံးကိုဖတ်ရှုရန်နှင့်ပြန်လည်သတ်မှတ်ရန်တစ်ခုတည်းသောယုံကြည်စိတ်ချရသောနည်းလမ်းဖြစ်သည်။
ဤအမိန့်များအားလုံးသည် မြေပုံအမျိုးအစားအားလုံးအတွက် အလုပ်မဖြစ်သော်လည်း ယေဘူယျအားဖြင့် အသုံးပြုသူနေရာမှ အခြားမြေပုံအမျိုးအစားများနှင့် လုပ်ဆောင်ခြင်းမှာ hash tables ဖြင့် လုပ်ဆောင်ခြင်းနှင့် အတူတူပင်ဖြစ်ပါသည်။
အမှာစာအတွက်၊ ကျွန်ုပ်တို့၏ hash table စမ်းသပ်မှုများကို အပြီးသတ်လိုက်ကြပါစို့။ သော့လေးခုအထိပါဝင်နိုင်တဲ့ ဇယားတစ်ခုကို ဖန်တီးခဲ့တာကို သတိရပါ။ နောက်ထပ်ဒြပ်စင်အနည်းငယ် ထပ်ထည့်ကြပါစို့။
$ 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 ပရိုဂရမ်များမှ မြေပုံများကို မည်သို့သုံးနိုင်သည်ကို ကြည့်ရှုရန် ယုတ္တိတန်ပါသည်။ စက် macro ကုဒ်များရှိ ဖတ်ရှုရခက်သော ပရိုဂရမ်များ၏ ဘာသာစကားဖြင့် ဤအကြောင်းကို ကျွန်ုပ်တို့ ပြောဆိုနိုင်သော်လည်း အမှန်တကယ်တွင် BPF ပရိုဂရမ်များကို မည်သို့ ရေးသားထိန်းသိမ်းထားသည်ကို ပြသရန် အချိန်ကျရောက်လာပြီဖြစ်သည်။ libbpf
.
(အဆင့်နိမ့်ဥပမာမရှိခြင်းအား မကျေနပ်သောစာဖတ်သူများအတွက်- မြေပုံများနှင့် အထောက်အကူပြုလုပ်ဆောင်ချက်များကို အသုံးပြု၍ ဖန်တီးထားသော အသေးစိတ်ပရိုဂရမ်များကို ကျွန်ုပ်တို့ ခွဲခြမ်းစိတ်ဖြာပါမည်။ libbpf
ညွှန်ကြားချက်အဆင့်မှာ ဘာတွေဖြစ်မလဲဆိုတာ ပြောပြပါ။ မကျေနပ်တဲ့ စာဖတ်သူတွေအတွက်ပါ။ အများကြီး, ငါတို့ကထပ်ပြောသည်။
libbpf ကိုအသုံးပြု၍ BPF ပရိုဂရမ်များရေးသားခြင်း။
စက်ကုဒ်များကိုအသုံးပြု၍ BPF ပရိုဂရမ်များကိုရေးခြင်းသည် ပထမဆုံးအကြိမ်အဖြစ် စိတ်ဝင်စားဖွယ်ကောင်းပြီး ကျေနပ်မှုလည်း ပါဝင်ပါသည်။ ဒီအခိုက်အတန့်မှာ အာရုံပြန်စိုက်ဖို့ လိုပါတယ်။ llvm
BPF ဗိသုကာနှင့် စာကြည့်တိုက်တစ်ခုအတွက် ကုဒ်ထုတ်ပေးရန်အတွက် နောက်ကွယ်တွင် ရှိသည်။ libbpf
BPF အပလီကေးရှင်းများ၏အသုံးပြုသူဘက်ခြမ်းကိုရေးသားရန်နှင့်အသုံးပြု၍ထုတ်လုပ်ထားသော BPF ပရိုဂရမ်များ၏ကုဒ်ကိုတင်ရန်ခွင့်ပြုသည်။ llvm
/clang
.
တကယ်တော့ ဒီဆောင်းပါးနဲ့ နောက်ဆက်တွဲ ဆောင်းပါးတွေမှာ တွေ့ရသလို၊ libbpf
(သို့မဟုတ် အလားတူကိရိယာများမပါဘဲ အလုပ်များစွာလုပ်သည် iproute2
, libbcc
, libbpf-go
စသည်ဖြင့်) အသက်ရှင်ဖို့ မဖြစ်နိုင်ဘူး။ ပရောဂျက်၏ လူသတ်သမား အင်္ဂါရပ်များထဲမှ တစ်ခု libbpf
BPF CO-RE (Compile Once, Run Everywhere) - သည် သင့်အား kernel တစ်ခုမှ တစ်ခုသို့ သယ်ဆောင်သွားနိုင်သော BPF ပရိုဂရမ်များကို ရေးသားနိုင်စေမည့် ပရောဂျက်တစ်ခုဖြစ်ပြီး မတူညီသော APIs များတွင် လုပ်ဆောင်နိုင်သည် (ဥပမာ၊ kernel တည်ဆောက်ပုံသည် ဗားရှင်းမှ ပြောင်းလဲသောအခါ၊ ဗားရှင်းသို့)။ CO-RE နှင့်အလုပ်လုပ်နိုင်စေရန်အတွက်၊ သင်၏ kernel ကို BTF ပံ့ပိုးမှုဖြင့် စုစည်းထားရပါမည် (ဤအရာကို ကဏ္ဍတွင် ကျွန်ုပ်တို့ ဖော်ပြထားပါသည်။
$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux
ဤဖိုင်သည် kernel တွင်အသုံးပြုသည့်ဒေတာအမျိုးအစားအားလုံး၏အချက်အလက်ကိုသိမ်းဆည်းထားပြီးကျွန်ုပ်တို့၏နမူနာများအားလုံးတွင်အသုံးပြုသည်။ libbpf
. နောက်ဆောင်းပါးတွင် CO-RE အကြောင်းအသေးစိတ်ပြောပါလိမ့်မည်၊ သို့သော်ဤတစ်ခုတွင် - သင့်ကိုယ်သင် kernel ဖြင့်တည်ဆောက်ပါ။ CONFIG_DEBUG_INFO_BTF
.
စာကြည့်တိုက် libbpf
လမ်းညွှန်ထဲမှာ မှန်ကန်စွာနေထိုင်တယ်။ tools/lib/bpf
kernel နှင့်၎င်း၏ဖွံ့ဖြိုးတိုးတက်မှုကို mailing list မှတဆင့်ဆောင်ရွက်သည်။ [email protected]
. သို့သော်၊ kernel ပြင်ပတွင်နေထိုင်သော application များ၏လိုအပ်ချက်များအတွက်သီးခြားသိုလှောင်သိမ်းဆည်းထားသည်။
ဤကဏ္ဍတွင် သင်အသုံးပြုသော ပရောဂျက်တစ်ခုကို မည်သို့ဖန်တီးနိုင်သည်ကို လေ့လာပါမည်။ libbpf
စမ်းသပ်မှုပရိုဂရမ်များစွာ (အနည်းနှင့်အများ အဓိပ္ပါယ်မဲ့) ကိုရေးပြီး ၎င်းအားလုံးအလုပ်လုပ်ပုံကို အသေးစိတ်ခွဲခြမ်းစိတ်ဖြာကြည့်ကြပါစို့။ ၎င်းသည် BPF ပရိုဂရမ်များသည် မြေပုံများ၊ kernel အကူအညီပေးသူများ၊ BTF စသည်တို့နှင့် မည်သို့အကျိုးသက်ရောက်ကြောင်းကို အောက်ပါကဏ္ဍများတွင် ပိုမိုလွယ်ကူစွာ ရှင်းပြနိုင်စေမည်ဖြစ်သည်။
အများအားဖြင့် ပရောဂျက်များကို အသုံးပြုကြသည်။ libbpf
GitHub repository ကို git submodule အဖြစ်ထည့်ပါ၊ ကျွန်ုပ်တို့လည်း အလားတူလုပ်ပါမည်။
$ 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 တွင်၊ ကျွန်ုပ်တို့ ၎င်းကိုအသုံးပြု၍ compile လုပ်ပါ။ clang
, နှင့် kernel ထဲသို့ load လုပ်မည့် helper program တစ်ခုရေးပါ။ အောက်ပါကဏ္ဍများတွင် BPF ပရိုဂရမ်နှင့် လက်ထောက်ပရိုဂရမ်နှစ်ခုလုံး၏ စွမ်းဆောင်ရည်များကို ချဲ့ထွင်ပါမည်။
ဥပမာ- libbpf ကို အသုံးပြု၍ ပြည့်စုံသော အပလီကေးရှင်းတစ်ခု ဖန်တီးခြင်း။
စတင်ရန်၊ ကျွန်ုပ်တို့သည် ဖိုင်ကို အသုံးပြုသည်။ /sys/kernel/btf/vmlinux
အထက်တွင်ဖော်ပြထားသော၊ ၎င်းနှင့်ညီမျှသော header file ပုံစံဖြင့် ဖန်တီးပါ-
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
ဤဖိုင်သည် ကျွန်ုပ်တို့၏ kernel တွင် ရရှိနိုင်သော ဒေတာဖွဲ့စည်းပုံအားလုံးကို သိမ်းဆည်းထားလိမ့်မည်၊ ဥပမာ၊ ဤသည်မှာ IPv4 ခေါင်းစီးအား kernel တွင် သတ်မှတ်ပုံဖြစ်သည်-
$ 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 တည်ဆောက်ပုံများ မည်ကဲ့သို့ ပုံသဏ္ဌာန်ရှိသည်ကို သိရှိရန် kernel-headers package ကို ထည့်သွင်းရန် မလိုအပ်ပါ။ အောက်ဖော်ပြပါ ခေါင်းစီးဖိုင်သည် စာကြည့်တိုက်မှ ကျွန်ုပ်တို့ထံ ရောက်လာသည်။ 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
ဟုတ်ကဲ့ အလုပ်ဖြစ်ပါပြီ။ ယခု၊ ကျွန်ုပ်တို့တွင် ပရိုဂရမ်ပါရှိသော binary ဖိုင်တစ်ခုရှိပြီး၊ ၎င်းကို kernel ထဲသို့ တင်မည့် အပလီကေးရှင်းတစ်ခုကို ဖန်တီးလိုပါသည်။ ဤရည်ရွယ်ချက်အတွက် စာကြည့်တိုက် libbpf
ကျွန်ုပ်တို့အား ရွေးချယ်စရာနှစ်ခုပေးသည် - အောက်ခြေအဆင့် API သို့မဟုတ် အဆင့်မြင့် API ကိုသုံးပါ။ BPF ပရိုဂရမ်များကို ၎င်းတို့၏နောက်ဆက်တွဲလေ့လာမှုအတွက် အားထုတ်မှုအနည်းဆုံးဖြင့် BPF ပရိုဂရမ်များကို မည်သို့ရေးရမည်၊ တင်ရန်နှင့် ချိတ်ဆက်နည်းကို လေ့လာလိုသောကြောင့် ဒုတိယလမ်းကို သွားပါမည်။
ပထမဦးစွာ၊ ကျွန်ုပ်တို့သည် တူညီသော အသုံးဝင်မှုကို အသုံးပြု၍ ကျွန်ုပ်တို့၏ ပရိုဂရမ်၏ "အရိုးစု" ကို ၎င်း၏ binary မှ ထုတ်လုပ်ရန် လိုအပ်ပါသည်။ bpftool
- BPF ကမ္ဘာ၏ ဆွဇ်ဓား (BPF ၏ ဖန်တီးသူနှင့် ထိန်းသိမ်းသူ တစ်ဦးဖြစ်သည့် Daniel Borkman သည် Swiss ဖြစ်သောကြောင့် စာသားအရ ယူနိုင်သည်)
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
ဖိုင်ထဲမှာ xdp-simple.skel.h
ကျွန်ုပ်တို့၏ပရိုဂရမ်၏ ဒွိကုဒ်များနှင့် ကျွန်ုပ်တို့၏အရာဝတ္တုကို တင်ခြင်း၊ ပူးတွဲခြင်း၊ ဖျက်ခြင်းတို့ကို စီမံခန့်ခွဲရန်အတွက် လုပ်ဆောင်ချက်များပါရှိသည်။ ကျွန်ုပ်တို့၏ရိုးရှင်းသောကိစ္စတွင်၎င်းသည် overkill နှင့်တူသည်၊ သို့သော်အရာဝတ္ထုဖိုင်တွင် BPF ပရိုဂရမ်များနှင့်မြေပုံများစွာပါ ၀ င်ပြီးဤဧရာမ ELF ကိုတင်ရန်အတွက်ကျွန်ုပ်တို့သည်အရိုးစုကိုထုတ်လုပ်ရန်နှင့်ကျွန်ုပ်တို့စိတ်ကြိုက် application မှလုပ်ဆောင်ချက်တစ်ခုသို့မဟုတ်နှစ်ခုကိုခေါ်ဆိုသည့်ကိစ္စတွင်လည်းအလုပ်လုပ်သည်။ Let's move on now ရေးနေတာ။
အတိအကျပြောရလျှင် ကျွန်ုပ်တို့၏ loader ပရိုဂရမ်သည် အသေးအဖွဲဖြစ်သည်။
#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 သည် အခြားကဏ္ဍများပါရှိသည် - ဒေတာ၊ ဖတ်ရန်သာဒေတာ၊ အမှားရှာပြင်ခြင်းအချက်အလက်၊ လိုင်စင်စသည်ဖြင့်) ပြီးနောက် ၎င်းကို စနစ်တစ်ခုအသုံးပြု၍ kernel ထဲသို့ တင်ပေးသည်။ ခေါ်ဆိုပါ။ 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 (ကျွန်ုပ်တို့သည် command ၏အတိုကောက်ပုံစံကိုအသုံးပြုသည်။ 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
binary တွင် အမှားအယွင်း အပိုင်းကို တွေ့ရှိသော၊ ၎င်းကို BTF အရာဝတ္ထုတစ်ခုအဖြစ် စုစည်းပြီး အသုံးပြု၍ kernel တွင် တင်ထားသည်။ BPF_BTF_LOAD
၊ ထို့နောက် command ဖြင့် ပရိုဂရမ်ကို တင်သည့်အခါ ရရှိလာသော ဖိုင်ဖော်ပြသူကို သတ်မှတ်ပေးသည်။ BPG_PROG_LOAD
.
Kernel Helpers
BPF ပရိုဂရမ်များသည် “ပြင်ပ” လုပ်ဆောင်ချက်များကို လုပ်ဆောင်နိုင်သည် - kernel helpers။ ဤအကူအညီပေးသည့်လုပ်ဆောင်ချက်များသည် BPF ပရိုဂရမ်များကို kernel တည်ဆောက်ပုံများကို ဝင်ရောက်ကြည့်ရှုရန်၊ မြေပုံများကို စီမံခန့်ခွဲရန်နှင့် “အစစ်အမှန်ကမ္ဘာ” နှင့်လည်း ဆက်သွယ်နိုင်စေသည် - 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 helper function အဓိပ္ပါယ်ဖွင့်ဆိုချက်များသည် Linux စနစ်ခေါ်ဆိုမှု အဓိပ္ပါယ်ဖွင့်ဆိုချက်များနှင့် ဆင်တူသည်။ ဤတွင်၊ ဥပမာ၊ အကြောင်းပြချက်မရှိသော လုပ်ဆောင်ချက်တစ်ခုကို သတ်မှတ်သည်။ (macro ကိုသုံးပြီး argument သုံးခုကို ယူတဲ့ function ကို သတ်မှတ်ပါတယ်။ 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,
};
Helper လုပ်ဆောင်ချက်များကို မှတ်ပုံတင်ခြင်း။
အမျိုးအစားတစ်ခု၏ BPF ပရိုဂရမ်များသည် ဤလုပ်ဆောင်ချက်ကို အသုံးပြုရန်အတွက်၊ အမျိုးအစားအတွက် ဥပမာအားဖြင့် ၎င်းကို စာရင်းသွင်းရပါမည်။ BPF_PROG_TYPE_XDP
function တစ်ခုသည် kernel တွင်သတ်မှတ်ထားသည်။ xdp_func_proto
XDP သည် ဤလုပ်ဆောင်ချက်ကို ပံ့ပိုးသည်ဖြစ်စေ မထောက်ခံသည်ဖြစ်စေ ကူညီသူလုပ်ဆောင်မှု ID မှ ဆုံးဖြတ်ပေးသည်။ ကျွန်ုပ်တို့၏လုပ်ဆောင်ချက်သည်
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
. ကိုးကားချက်များတွင် ကျိုးကြောင်းဆီလျော်သော အဓိပ္ပါယ်ဖွင့်ဆိုချက်ဖြစ်သောကြောင့် C ဘာသာစကားတွင် ကွန်ကရစ်တည်ဆောက်မှုအစုအဝေး၏ အဓိပ္ပါယ်ဖွင့်ဆိုချက်သည် အခြားနေရာများတွင် ဖြစ်ပေါ်ပါသည်။ အထူးသဖြင့် ဖိုင်ထဲမှာ kernel/bpf/verifier.c
ဖိုင်မှ အဓိပ္ပါယ်အားလုံး bpf_types.h
array တစ်ခုဖန်တီးရန်အသုံးပြုကြသည်။ 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
kernel တွင် script တစ်ခုမှထုတ်ပေးသည်၊ ထို့ကြောင့် "magic" နံပါတ်များသည် ok)။ ဤလုပ်ဆောင်ချက်သည် အကြောင်းပြချက်များကို မယူဘဲ အမျိုးအစားတန်ဖိုးကို ပြန်ပေးသည်။ __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
, parameter IMM
8 နှင့် ညီမျှသည်။ SRC_REG
- သုည။ Verifier အသုံးပြုသော ABI သဘောတူညီချက်အရ၊ ၎င်းသည် helper function နံပါတ် XNUMX သို့ခေါ်ဆိုခြင်းဖြစ်သည်။ စတင်လိုက်သည်နှင့်၊ ယုတ္တိဗေဒသည်ရိုးရှင်းသည်။ မှတ်ပုံတင်မှတန်ဖိုးကိုပြန်ပေးပါ။ r0
မှကူးယူသည်။ r1
လိုင်း 2,3 တွင် ၎င်းကို အမျိုးအစားအဖြစ် ပြောင်းလဲထားသည်။ u32
- အထက် 32 bits များကို ရှင်းလင်းထားသည်။ လိုင်း ၄၊၅၊၆၊၇ မှာ ၂(XDP_PASS
) သို့မဟုတ် 1 (XDP_DROP
) လိုင်း 0 မှ helper function သည် သုည သို့မဟုတ် သုညမဟုတ်သော တန်ဖိုးကို ပြန်ပေးသည်ဆိုခြင်းအပေါ် မူတည်သည်။
မိမိကိုယ်မိမိ စမ်းသပ်ကြည့်ရအောင်- ပရိုဂရမ်ကို တင်ပြီး အထွက်ကို ကြည့်လိုက်ပါ။ 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
အိုကေ၊ အတည်ပြုသူသည် မှန်ကန်သော kernel-helper ကို တွေ့ရှိခဲ့သည်။
ဥပမာ- ငြင်းခုံမှုများကို ကျော်ဖြတ်ပြီး နောက်ဆုံးတွင် ပရိုဂရမ်ကို လုပ်ဆောင်ပါ။
run-level helper လုပ်ဆောင်ချက်အားလုံးတွင် ရှေ့ပြေးပုံစံရှိသည်။
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
အထောက်အကူပေးသည့်လုပ်ဆောင်ချက်များအတွက် ကန့်သတ်ချက်များကို မှတ်ပုံတင်များတွင် ပေးပို့ပါသည်။ r1
-r5
၊ နှင့်တန်ဖိုးကိုမှတ်ပုံတင်၌ပြန်ပေးသည်။ r0
. အငြင်းအခုံ ငါးခုထက် ပိုယူသည့် လုပ်ဆောင်ချက်များ မရှိပါ၊ ၎င်းတို့အတွက် ပံ့ပိုးမှုအား အနာဂတ်တွင် ထပ်ထည့်ရန် မျှော်လင့်မည်မဟုတ်ပါ။
kernel helper အသစ်နှင့် 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 တွင် string ကိုရေးသည်။ 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);
}
ဤတွင်ကျွန်ုပ်တို့သည် function ကိုအသုံးပြုသည်။ bpf_set_link_xdp_fd
XDP-type BPF ပရိုဂရမ်များကို ကွန်ရက်အင်တာဖေ့စ်များနှင့် ချိတ်ဆက်ပေးသည်။ ကျွန်ုပ်တို့သည် အင်တာဖေ့စ်နံပါတ်ကို hardcode လုပ်ထားပါသည်။ lo
အမြဲတမ်း ၁။ ၎င်းသည် ပရိုဂရမ်ဟောင်းကို တွဲထားပါက ပထမဦးစွာ ဖယ်ရှားရန် လုပ်ဆောင်ချက်ကို ကျွန်ုပ်တို့ နှစ်ကြိမ် လုပ်ဆောင်သည်။ ယခု ကျွန်ုပ်တို့သည် စိန်ခေါ်မှု မလိုအပ်ကြောင်း သတိပြုပါ။ pause
သို့မဟုတ် အဆုံးမရှိ ကွင်းဆက်- ကျွန်ုပ်တို့၏ loader ပရိုဂရမ်သည် ထွက်ပါမည်၊ သို့သော် ၎င်းသည် ဖြစ်ရပ်အရင်းအမြစ်နှင့် ချိတ်ဆက်ထားသောကြောင့် BPF ပရိုဂရမ်ကို သတ်မည်မဟုတ်ပါ။ ဒေါင်းလုဒ်လုပ်ပြီး ချိတ်ဆက်မှု အောင်မြင်ပြီးနောက်၊ ရောက်ရှိလာသော ကွန်ရက်ပက်ကတ်တစ်ခုစီအတွက် ပရိုဂရမ်ကို စတင်ပါမည်။ lo
.
ပရိုဂရမ်ကိုဒေါင်းလုဒ်ဆွဲပြီး interface ကိုကြည့်ရှုကြပါစို့ 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
ယခုတော့ debug virtual file ၏ အကြောင်းအရာများကို ကြည့်ကြပါစို့ /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 ပရိုဂရမ်မှ မြေပုံကို အသုံးပြုခြင်း။
ယခင်အပိုင်းများတွင် အသုံးပြုသူနေရာမှ မြေပုံများကို ဖန်တီးနည်းနှင့် အသုံးပြုနည်းကို လေ့လာခဲ့ပြီး ယခု kernel အပိုင်းကို ကြည့်ကြပါစို့။ ထုံးစံအတိုင်း ဥပမာတစ်ခုနဲ့ စလိုက်ရအောင်။ ကျွန်ုပ်တို့၏ အစီအစဉ်ကို ပြန်လည်ရေးသားကြပါစို့ 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
(C တွင်ကျွန်ုပ်တို့သည်ထိုကဲ့သို့သော array ကိုသတ်မှတ်သည်။ u64 woo[8]
) အစီအစဉ်တစ်ခုတွင် "xdp/simple"
ကျွန်ုပ်တို့သည် လက်ရှိ ပရိုဆက်ဆာနံပါတ်ကို ကိန်းရှင်တစ်ခုအဖြစ် ရယူသည်။ key
ထို့နောက် helper function ကိုအသုံးပြုပါ။ bpf_map_lookup_element
ကျွန်ုပ်တို့သည် တစ်ခုပြီးတစ်ခု တိုးပေးသော array အတွင်းရှိ သက်ဆိုင်ရာ entry ကို pointer တစ်ခုရရှိသည်။ ရုရှားဘာသာသို့ ဘာသာပြန်ထားသည်- ကျွန်ုပ်တို့သည် 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
အခု Array ရဲ့ အကြောင်းအရာတွေကို ကြည့်ရအောင်။
$ 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);
helper function က ဘယ်မှာလဲ။
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
သို့သော် ကျွန်ုပ်တို့သည် ညွှန်ပြချက်ကို ဖြတ်သန်းနေသည်။ &woo
အမည်မဖော်လိုသောဖွဲ့စည်းပုံသို့ struct { ... }
...
ပရိုဂရမ် တပ်ဆင်သူအား ကြည့်လျှင် တန်ဖိုးကို ကျွန်ုပ်တို့ မြင်သည်။ &woo
အမှန်တကယ် သတ်မှတ်ခြင်းမဟုတ်ပါ (စာကြောင်း ၄)။
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]
...
ထို့ကြောင့် ကျွန်ုပ်တို့သည် ကျွန်ုပ်တို့၏ loader ပရိုဂရမ်ကို စတင်ချိန်တွင် လင့်ခ်မှ ဖြစ်သည်ဟု ကျွန်ုပ်တို့ ကောက်ချက်ချနိုင်ပါသည်။ &woo
စာကြည့်တိုက်တစ်ခုခုဖြင့် အစားထိုးခဲ့သည်။ libbpf
. အရင်ဆုံး Output ကိုကြည့်ပါမယ်။ 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
output ၌ strace
.) နောက်တစ်ခုက function ကိုခေါ်တယ်။ 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
ပြီးလျှင် ၎င်းရှိ source register ဖြင့် အစားထိုးပါ။ 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
algorithm မှာ အောက်ပါအတိုင်းဖြစ်သည် ။
- စုဆောင်းနေစဉ်အတွင်း မြေပုံများနှင့် လင့်ခ်များအတွက် နေရာရွှေ့ပြောင်းခြင်းဇယားတွင် မှတ်တမ်းများကို ဖန်တီးထားသည်။
libbpf
ELF အရာဝတ္ထုစာအုပ်ကိုဖွင့်ပြီး အသုံးပြုထားသောမြေပုံအားလုံးကို ရှာဖွေပြီး ၎င်းတို့အတွက် ဖိုင်ဖော်ပြချက်များကို ဖန်တီးပေးသည်။- ညွှန်ကြားချက်၏တစ်စိတ်တစ်ပိုင်းအနေဖြင့် ဖိုင်ဖော်ပြချက်များအား kernel တွင် တင်ထားသည်။
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 binary ကို အသုံးပြု၍ ဒေါင်းလုဒ်လုပ်သောအခါ libbpf
နောက်ထပ်အများကြီးရှိသေးပေမယ့် တခြားဆောင်းပါးတွေမှာ ဆွေးနွေးပါမယ်။
libbpf မပါဘဲ ပရိုဂရမ်များနှင့် မြေပုံများကို တင်နေသည်။
ကတိပြုထားသည့်အတိုင်း၊ ဤနေရာတွင် အကူအညီမပါဘဲ မြေပုံများကို အသုံးပြုသည့် ပရိုဂရမ်တစ်ခုကို ဖန်တီးပြီး တင်နည်းကို သိလိုသော စာဖတ်သူများအတွက် ဥပမာတစ်ခုဖြစ်သည်။ libbpf
. မှီခိုမှုတည်ဆောက်၍မရသော သို့မဟုတ် တစ်နည်းနည်းချင်းစီ ချွေတာခြင်း သို့မဟုတ် ပရိုဂရမ်ရေးခြင်းကဲ့သို့သော ပတ်ဝန်းကျင်တစ်ခုတွင် သင်လုပ်ဆောင်နေသည့်အခါ ၎င်းသည် အသုံးဝင်နိုင်သည် ply
ယုတ္တိဗေဒကို လိုက်နာရန် ပိုမိုလွယ်ကူစေရန်၊ ဤရည်ရွယ်ချက်များအတွက် ကျွန်ုပ်တို့၏ နမူနာကို ပြန်လည်ရေးသားပါမည်။ xdp-simple
. ဤဥပမာတွင် ဆွေးနွေးထားသော ပရိုဂရမ်၏ ပြီးပြည့်စုံပြီး အနည်းငယ် ချဲ့ထွင်ထားသော ကုဒ်ကို ဤနေရာတွင် တွေ့နိုင်ပါသည်။
ကျွန်ုပ်တို့၏လျှောက်လွှာ၏ယုတ္တိမှာ အောက်ပါအတိုင်းဖြစ်သည် ။
- မြေပုံအမျိုးအစားတစ်ခုဖန်တီးပါ။
BPF_MAP_TYPE_ARRAY
command ကို အသုံးပြု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
- "kernel၊ ကျေးဇူးပြု၍ ကျွန်ုပ်အား မြေပုံအသစ်တစ်ခု ဖန်တီးပါ __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 ပရိုဂရမ်၏ အဓိပ္ပါယ်ဖွင့်ဆိုချက်မှာ array of structure တစ်ခုဖြစ်သည်။ 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
စုစုပေါင်း၊ ညွှန်ကြားချက် ၁၄ ခုကို အဆောက်အဦပုံစံနဲ့ ရေးရမယ်။ 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 တို့ကို ဖန်တီးသူများသည် အွန်လိုင်း Linux အသိုင်းအဝိုင်းမှ ဖြစ်ကြသည်၊ ဆိုလိုသည်မှာ ၎င်းတို့နှင့် အရင်းနှီးဆုံးသူကို အသုံးပြုခဲ့သည် (သို့သော် မဟုတ်ပါ။ ပုံမှန် လူများ) kernel နှင့်အပြန်အလှန်ဆက်သွယ်ရန်အတွက်အင်တာဖေ့စ်- xdp_attach
ကုဒ်မှကူးယူသည်။ libbpf
ပြောရရင် ဖိုင်ကနေ netlink.c
netlink sockets များကမ္ဘာမှလှိုက်လှဲစွာကြိုဆိုပါသည်။
netlink socket အမျိုးအစားကိုဖွင့်ပါ။ 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;
}
ဤ socket မှဖတ်ရသည်
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;
}
နောက်ဆုံးတွင်၊ ဤသည်မှာ socket တစ်ခုကိုဖွင့်ပြီး ဖိုင်ဖော်ပြချက်ပါရှိသော ၎င်းထံသို့ အထူးမက်ဆေ့ချ်ပေးပို့သည့် ကျွန်ုပ်တို့၏လုပ်ဆောင်ချက်ဖြစ်သည်-
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
Pings ပို့ပြီး မြေပုံကို ကြည့်ကြရအောင်။
$ 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
ဟား၊ အရာအားလုံး အဆင်ပြေပါတယ်။ စကားမစပ်၊ ကျွန်ုပ်တို့၏မြေပုံကို bytes ပုံစံဖြင့် ထပ်မံပြသထားကြောင်း သတိပြုပါ။ ဒါက မကြိုက်တဲ့အချက်ကြောင့်ပါ။ libbpf
အမျိုးအစားအချက်အလက် (BTF) ကို ကျွန်ုပ်တို့ မတင်ခဲ့ပါ။ ဒါပေမယ့် ဒီအကြောင်းကို နောက်တစ်ကြိမ် ထပ်ပြောပါဦးမယ်။
ဖွံ့ဖြိုးတိုးတက်ရေးကိရိယာများ
ဤကဏ္ဍတွင်၊ အနည်းဆုံး BPF developer toolkit ကို ကြည့်ပါမည်။
ယေဘူယျအားဖြင့်၊ BPF ပရိုဂရမ်များ ဖွံ့ဖြိုးတိုးတက်ရန် အထူးဘာမှ မလိုအပ်ပါ - BPF သည် သင့်တင့်လျောက်ပတ်သော ဖြန့်ချီရေး kernel ပေါ်တွင် လုပ်ဆောင်ပြီး ပရိုဂရမ်များကို အသုံးပြု၍ တည်ဆောက်ထားသည်။ clang
ထုပ်ပိုးမှုကနေ ပံ့ပိုးပေးနိုင်ပါတယ်။ သို့သော် BPF သည် ဖွံ့ဖြိုးတိုးတက်နေဆဲဖြစ်သောကြောင့်၊ kernel နှင့် tools များသည် 2019 ခုနှစ်မှ ခေတ်ဟောင်းနည်းလမ်းများကို အသုံးပြု၍ BPF ပရိုဂရမ်များကို မရေးချင်ပါက၊ စုစည်းရမည်ဖြစ်ပါသည်။
llvm
/clang
pahole
- ၎င်း၏အဓိက
bpftool
(ကိုးကားရန်အတွက်၊ ဤကဏ္ဍနှင့် ဆောင်းပါးရှိ ဥပမာအားလုံးကို Debian 10 တွင် အသုံးပြုထားသည်။)
llvm/clang
BPF သည် LLVM နှင့် ဖော်ရွေပြီး BPF အတွက် မကြာသေးမီက ပရိုဂရမ်များကို gcc ကို အသုံးပြု၍ ပြုစုနိုင်သော်လည်း လက်ရှိ ဖွံ့ဖြိုးတိုးတက်မှုအားလုံးကို LLVM အတွက် လုပ်ဆောင်ပါသည်။ ထို့ကြောင့်၊ ပထမဦးစွာ၊ ကျွန်ုပ်တို့သည် လက်ရှိဗားရှင်းကို တည်ဆောက်ပါမည်။ clang
git မှ
$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86"
-DLLVM_ENABLE_PROJECTS="clang"
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_BUILD_TYPE=Release
-DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... много времени спустя
$
ယခု ကျွန်ုပ်တို့သည် အရာအားလုံး မှန်ကန်မှုရှိမရှိ စစ်ဆေးနိုင်သည်-
$ ./bin/llc --version
LLVM (http://llvm.org/):
LLVM version 11.0.0git
Optimized build.
Default target: x86_64-unknown-linux-gnu
Host CPU: znver1
Registered Targets:
bpf - BPF (host endian)
bpfeb - BPF (big endian)
bpfel - BPF (little endian)
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
(စည်းဝေးပွဲ ညွှန်ကြားချက် clang
ငါ့ထံမှယူဆောင်
ကျွန်ုပ်တို့တည်ဆောက်ထားသော ပရိုဂရမ်များကို ထည့်သွင်းမည်မဟုတ်သော်လည်း ၎င်းတို့ကို ထည့်သွင်းရုံသာဖြစ်သည်။ PATH
ဥပမာ:
export PATH="`pwd`/bin:$PATH"
(ဤသို့ထည့်နိုင်သည်။ .bashrc
သို့မဟုတ် သီးခြားဖိုင်တစ်ခုသို့။ ပုဂ္ဂိုလ်ရေးအရ၊ ဤကဲ့သို့သောအရာများကိုထည့်ပါ။ ~/bin/activate-llvm.sh
လိုအပ်ရင် ငါလုပ်တယ်။ . activate-llvm.sh
.)
Pahole နှင့် BTF
အသုံးဝင်သည် pahole
BTF ဖော်မတ်ဖြင့် အမှားရှာအချက်အလက်ဖန်တီးရန် kernel ကိုတည်ဆောက်ရာတွင် အသုံးပြုသည်။ BTF နည်းပညာ၏အသေးစိတ်အချက်အလက်များနှင့်ပတ်သက်၍ ကျွန်ုပ်တို့သည် ဤဆောင်းပါးတွင် အဆင်ပြေပြီး ၎င်းကိုအသုံးပြုလိုသည့်အချက်မှလွဲ၍ အခြားအသေးစိတ်အချက်အလက်များကို မဖော်ပြပါ။ ဒါကြောင့် သင့် kernel ကိုတည်ဆောက်တော့မယ်ဆိုရင် အရင်ဆုံးတည်ဆောက်ပါ။ pahole
(မပါဘဲ pahole
ရွေးချယ်မှုဖြင့် kernel ကို သင်တည်ဆောက်နိုင်မည်မဟုတ်ပါ။ 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 ၏ဖြစ်နိုင်ခြေများကိုရှာဖွေသောအခါ၊ ကျွန်ုပ်သည် ကျွန်ုပ်၏ကိုယ်ပိုင် core ကိုစုစည်းလိုပါသည်။ ယေဘူယျအားဖြင့်ပြောရလျှင် ၎င်းသည် မလိုအပ်ပါ၊ အဘယ်ကြောင့်ဆိုသော် သင်သည် ဖြန့်ဖြူးမှု kernel တွင် BPF ပရိုဂရမ်များကို စုစည်း၍ တင်နိုင်မည်ဖြစ်ပြီး၊ သင်၏ကိုယ်ပိုင် kernel ရှိခြင်းက သင့်အား လပိုင်းအတွင်း သင့်ဖြန့်ဖြူးမှုတွင် အကောင်းဆုံးပေါ်လာမည့် နောက်ဆုံးပေါ် BPF အင်္ဂါရပ်များကို အသုံးပြုခွင့်ပေးသည်။ သို့မဟုတ်၊ အချို့သော အမှားရှာပြင်သည့်ကိရိယာများ၏ကိစ္စတွင်ကဲ့သို့ပင် မကြာမီအနာဂတ်တွင် လုံးဝထုပ်ပိုးမည်မဟုတ်ပါ။ ထို့အပြင်၊ ၎င်း၏ကိုယ်ပိုင် core သည် code ကိုစမ်းသပ်ရန်အရေးကြီးသည်ဟုခံစားရစေသည်။
Kernel တစ်ခုတည်ဆောက်ရန်အတွက် ပထမအချက်မှာ kernel ကိုယ်တိုင်နှင့် ဒုတိယအချက်မှာ kernel configuration file တစ်ခု လိုအပ်ပါသည်။ BPF ကို စမ်းသပ်ရန် ကျွန်ုပ်တို့သည် ပုံမှန်အတိုင်း အသုံးပြုနိုင်သည်။ net
net-next
bpf
bpf-next
*-next
kernels များသည် စာရင်းသွင်းထားသော အတည်ငြိမ်ဆုံး ဖြစ်သည်)။
kernel configuration ဖိုင်များကို စီမံခန့်ခွဲနည်းကို ဆွေးနွေးရန် ဤဆောင်းပါး၏ နယ်ပယ်ထက်ကျော်လွန်သည် - ၎င်းကို သင်မည်ကဲ့သို့ လုပ်ဆောင်ရမည်ကို သိပြီးဖြစ်သည်ဟု ယူဆရမည် သို့မဟုတ်၊
အထက်ပါ kernel များထဲမှ တစ်ခုကို ဒေါင်းလုဒ်လုပ်ပါ။
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next
အနည်းငယ်မျှသာအလုပ်လုပ်သော kernel config ကိုတည်ဆောက်ပါ-
$ cp /boot/config-`uname -r` .config
$ make localmodconfig
ဖိုင်တွင် BPF ရွေးချယ်မှုများကို ဖွင့်ပါ။ .config
သင့်ကိုယ်ပိုင်ရွေးချယ်မှု (ဖြစ်နိုင်ချေအများဆုံး CONFIG_BPF
systemd ကိုအသုံးပြုထားသောကြောင့် ၎င်းကို enable လုပ်ထားပြီးဖြစ်သည်။) ဤဆောင်းပါးအတွက် အသုံးပြုသည့် kernel မှ ရွေးချယ်စရာများစာရင်းသည် ဤတွင်ဖြစ်သည်-
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
ထို့နောက် module များနှင့် kernel များကို အလွယ်တကူ စုစည်းတပ်ဆင်နိုင်သည် (နည်းလမ်းအားဖြင့်၊ သင်သည် အသစ်တပ်ဆင်ထားသော kernel ကို အသုံးပြု၍ kernel ကို စုစည်းနိုင်သည်။ clang
ပေါင်းထည့်ခြင်းဖြင့် CC=clang
):
$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install
kernel အသစ်ဖြင့် reboot လုပ်ပါ (ဒီအတွက် ကျွန်တော်သုံးပါတယ်။ 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
ဆောင်းပါးတွင် အသုံးအများဆုံး utility သည် utility ဖြစ်လိမ့်မည်။ bpftool
Linux kernel ၏ တစ်စိတ်တစ်ပိုင်းအဖြစ် ပံ့ပိုးပေးထားသည်။ ၎င်းကို BPF developer များအတွက် BPF developer များက ရေးသားထိန်းသိမ်းထားပြီး BPF objects အမျိုးအစားအားလုံးကို စီမံခန့်ခွဲရန် - ပရိုဂရမ်များတင်ရန်၊ မြေပုံများကို ဖန်တီးခြင်းနှင့် တည်းဖြတ်ခြင်း၊ BPF ဂေဟစနစ်၏ အသက်တာကို စူးစမ်းလေ့လာခြင်း စသည်ဖြင့် အသုံးပြုနိုင်ပါသည်။ လူစာမျက်နှာများအတွက် အရင်းအမြစ်ကုဒ်များပုံစံဖြင့် စာရွက်စာတမ်းများကို တွေ့ရှိနိုင်သည်။
ဒီစာရေးနေတဲ့အချိန် bpftool
RHEL၊ Fedora နှင့် Ubuntu အတွက်သာ အဆင်သင့်လုပ်ထားသည် (ဥပမာ၊ ကြည့်ပါ၊ bpftool
Debian တွင်)။ ဒါပေမယ့် သင့် kernel ကိုတည်ဆောက်ပြီးပြီဆိုရင်တော့ build လုပ်လိုက်ပါ။ bpftool
pie ကဲ့သို့လွယ်ကူသည်
$ 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}
- ဒါက မင်းရဲ့ kernel directory ပါ။) ဒီ commands တွေကို execute လုပ်ပြီးနောက် bpftool
မှတ်တမ်းတစ်ခုတွင် စုဆောင်းမည်ဖြစ်သည်။ ${linux}/tools/bpf/bpftool
၎င်းကို လမ်းကြောင်းထဲသို့ ထည့်နိုင်သည်။ root
) သို့မဟုတ် ကော်ပီကူးယူပါ။ /usr/local/sbin
.
စုဆောင်းပါ။ bpftool
နောက်ဆုံးကိုသုံးတာ အကောင်းဆုံးပါ။ clang
အထက်တွင်ဖော်ပြထားသည့်အတိုင်း စုစည်းပြီး ၎င်းကို မှန်ကန်စွာ စုဝေးခြင်းရှိမရှိ စစ်ဆေးပါ - ဥပမာ၊ command ကိုအသုံးပြုခြင်း။
$ 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
...
၎င်းသည် သင်၏ kernel တွင် မည်သည့် BPF အင်္ဂါရပ်များကို ဖွင့်ထားသည်ကို ပြသမည်ဖြစ်သည်။
စကားမစပ်၊ ယခင် command အတိုင်း run နိုင်သည်။
# bpftool f p k
၎င်းကို package မှ utilities များနှင့် နှိုင်းယှဉ်ခြင်းဖြင့် လုပ်ဆောင်သည်။ iproute2
ဥပမာဆိုပါတော့ ip a s eth0
အစား ip addr show dev eth0
.
ကောက်ချက်
BPF သည် သင့်အား core ၏လုပ်ဆောင်နိုင်စွမ်းကို ထိရောက်စွာတိုင်းတာနိုင်ပြီး ပျံသန်းမှုပြောင်းလဲရန်အတွက် လှေးများကို ဖိနပ်စီးနိုင်စေပါသည်။ စနစ်သည် UNIX ၏ အကောင်းဆုံး ဓလေ့ထုံးတမ်းများတွင် အလွန်အောင်မြင်ခဲ့သည်- kernel သည် လူများနှင့် အဖွဲ့အစည်းအများအပြားကို စမ်းသပ်ရန် ခွင့်ပြုထားသော ရိုးရှင်းသော ယန္တရားတစ်ခုဖြစ်သည်။ လက်တွေ့စမ်းသပ်မှုများအပြင် BPF အခြေခံအဆောက်အဦများ ဖွံ့ဖြိုးတိုးတက်ရေးသည် မပြီးဆုံးသေးသော်လည်း၊ စနစ်တွင် ယုံကြည်စိတ်ချရပြီး အရေးကြီးဆုံးမှာ ထိရောက်သော စီးပွားရေးယုတ္တိကို တည်ဆောက်နိုင်စေမည့် တည်ငြိမ်သော ABI ရှိပြီးသားဖြစ်သည်။
ကျွန်တော့်အမြင်အရတော့ နည်းပညာဟာ တစ်ဖက်မှာ တတ်နိုင်လို့ ပေါ်ပြူလာဖြစ်လာတာကို သတိပြုစေချင်ပါတယ်။ ကစား (စက်တစ်လုံး၏တည်ဆောက်ပုံအား တစ်ညနေတွင် အနည်းနှင့်အများနားလည်နိုင်သည်) နှင့် အခြားတစ်ဖက်တွင်၊ (လှပစွာမဖြေရှင်းနိုင်သော) ပြဿနာများကို ဖြေရှင်းရန်။ ဤအစိတ်အပိုင်းနှစ်ခုသည် လူတို့အား စမ်းသပ်မှုနှင့် အိပ်မက်မက်ရန် တွန်းအားပေးကာ တီထွင်ဆန်းသစ်သောဖြေရှင်းနည်းများ ပိုမိုပေါ်ပေါက်လာစေသည်။
ဤဆောင်းပါးသည် အထူးအတိုမဟုတ်သော်လည်း၊ BPF ၏ကမ္ဘာကြီးအတွက် နိဒါန်းတစ်ခုမျှသာဖြစ်ပြီး “အဆင့်မြင့်” အင်္ဂါရပ်များနှင့် ဗိသုကာပညာ၏ အရေးကြီးသောအစိတ်အပိုင်းများကို ဖော်ပြထားခြင်းမရှိပါ။ ရှေ့ဆက်သွားမည့် အစီအစဉ်သည် ဤကဲ့သို့သော အရာဖြစ်သည်- နောက်ဆောင်းပါးတွင် BPF ပရိုဂရမ်အမျိုးအစားများ၏ ခြုံငုံသုံးသပ်ချက် (5.8 kernel တွင် ပံ့ပိုးထားသော ပရိုဂရမ်အမျိုးအစား 30 ရှိသည်) ထို့နောက် kernel ခြေရာခံပရိုဂရမ်များကို အသုံးပြု၍ အမှန်တကယ် BPF အပလီကေးရှင်းများကို မည်သို့ရေးရမည်ကို လေ့လာကြည့်ပါမည်။ ဥပမာအနေဖြင့်၊ ထို့နောက် BPF ဗိသုကာပညာဆိုင်ရာ ပိုမိုနက်ရှိုင်းသော သင်တန်းတက်ရန် အချိန်ကျရောက်ပြီး BPF ကွန်ရက်ချိတ်ဆက်မှုနှင့် လုံခြုံရေးဆိုင်ရာ အသုံးချပရိုဂရမ်များ၏ နမူနာများဖြင့် လုပ်ဆောင်ရန် အချိန်ဖြစ်သည်။
ဤစီးရီးရှိ ယခင်ဆောင်းပါးများ
လင့်များ
-
BPF နှင့် XDP ရည်ညွှန်းလမ်းညွှန် - cilium မှ BPF ဆိုင်ရာစာရွက်စာတမ်းများ သို့မဟုတ် BPF ၏ဖန်တီးသူနှင့်ထိန်းသိမ်းသူတဦးဖြစ်သည့် Daniel Borkman ထံမှ ပို၍တိကျစွာဖော်ပြထားသည်။ ဒါက ပထမဆုံး လေးနက်တဲ့ ဖော်ပြချက်တွေထဲက တစ်ခုဖြစ်ပြီး၊ ဒံယေလက သူရေးတဲ့အကြောင်း အတိအကျသိပြီး အဲဒီမှာ အမှားအယွင်းတွေ မရှိတဲ့အတွက် တခြားသူတွေနဲ့ မတူပါဘူး။ အထူးသဖြင့်၊ ဤစာတမ်းသည် နာမည်ကြီး utility ကို အသုံးပြု၍ XDP နှင့် TC အမျိုးအစားများ၏ BPF ပရိုဂရမ်များနှင့် မည်သို့လုပ်ဆောင်ရမည်ကို ဖော်ပြသည်ip
အထုပ်ထဲကiproute2
. -
Documentation/networking/filter.txt — ဂန္ထဝင်အတွက် စာရွက်စာတမ်းပါရှိသော မူရင်းဖိုင်၊ ထို့နောက် ထပ်တိုး BPF။ စုစည်းမှုဘာသာစကားနှင့် နည်းပညာဗိသုကာဆိုင်ရာ အသေးစိတ်အချက်အလက်များကို လေ့လာလိုပါက ကောင်းကောင်းဖတ်ပါ။ -
facebook မှ BPF အကြောင်း ဘလော့ဂ် . Alexei Starovoitov (eBPF ၏ရေးသားသူ) နှင့် Andrii Nakryiko - (ထိန်းသိမ်းသူ) ရေးသကဲ့သို့၎င်းကိုမွမ်းမံခဲသော်လည်းသင့်လျော်စွာ၊libbpf
). -
bpftool ၏လျှို့ဝှက်ချက်များ . bpftool ကိုအသုံးပြုခြင်း၏နမူနာများနှင့်လျှို့ဝှက်ချက်များနှင့်အတူ Quentin Monnet မှပျော်စရာကောင်းသော twitter လိုင်းတစ်ခု။ -
BPF သို့ စေ့ငုပါ- စာဖတ်ခြင်းဆိုင်ရာ ပစ္စည်းစာရင်း . Quentin Monnet မှ BPF စာရွက်စာတမ်းဆိုင်ရာ လင့်ခ်ကြီးများ (ဆက်လက်ထိန်းသိမ်းထားဆဲ) စာရင်း။
source: www.habr.com