د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

په پیل کې یوه ټیکنالوژي وه او دا د BPF په نوم یادیږي. موږ هغې ته وکتل تیر، د دې لړۍ د زاړه عهد نامې مقاله. په 2013 کې، د الیکسي سټاروویتوف او ډینیل بورکمن د هڅو له لارې، د دې یوه پرمختللې نسخه، د عصري 64-bit ماشینونو لپاره غوره شوې، پراختیا او د لینکس کرنل کې شامله شوه. دا نوې ټیکنالوژي په لنډه توګه د داخلي BPF په نوم یادیږي، بیا یې د توسیع شوي BPF نوم بدل کړ، او اوس، د څو کلونو وروسته، هرڅوک په ساده ډول دا BPF بولي.

په لنډه توګه ، BPF تاسو ته اجازه درکوي د لینوکس کرنل ځای کې د خپل سري کارونکي لخوا چمتو شوي کوډ چل کړئ ، او نوی جوړښت دومره بریالی وګرځید چې موږ به د دې ټولو غوښتنلیکونو تشریح کولو لپاره لسګونو نورو مقالو ته اړتیا ولرو. (یوازې شی چې پراختیا کونکو ښه کار نه دی کړی ، لکه څنګه چې تاسو لاندې د فعالیت کوډ کې لیدلی شئ ، یو ښه لوګو رامینځته کول وو.)

دا مقاله د BPF مجازی ماشین جوړښت بیانوي، د BPF سره کار کولو لپاره د کرنل انٹرفیسونه، پراختیایی وسیلې، او همدارنګه د موجوده وړتیاو لنډ، خورا لنډه کتنه، د بیلګې په توګه. هرڅه چې موږ به په راتلونکي کې د BPF د عملي غوښتنلیکونو ژورې مطالعې لپاره اړتیا ولرو.
د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

د مقالې لنډیز

د BPF معمارۍ پیژندنه. لومړی، موږ به د BPF جوړښت ته د مرغیو د سترګو لید واخلو او اصلي برخې به په ګوته کړو.

د BPF مجازی ماشین راجستر او کمانډ سیسټم. مخکې له دې چې په بشپړ ډول د معمارۍ مفکوره ولرئ، موږ به د BPF مجازی ماشین جوړښت تشریح کړو.

د BPF شیانو د ژوند دوره، د bpffs فایل سیسټم. په دې برخه کې، موږ به د BPF شیانو د ژوند دورې ته نږدې کتنه وکړو - پروګرامونه او نقشې.

د Bpf سیسټم کال په کارولو سره د شیانو اداره کول. د سیسټم په اړه د یو څه پوهیدو سره چې دمخه شتون لري ، موږ به په پای کې وګورو چې څنګه د ځانګړي سیسټم کال په کارولو سره د کارونکي ځای څخه توکي رامینځته او اداره کړو - bpf(2).

Пишем программы BPF с помощью libbpf. البته، تاسو کولی شئ د سیسټم کال په کارولو سره پروګرامونه ولیکئ. خو دا ستونزمنه ده. د یو ډیر حقیقي سناریو لپاره، اټومي پروګرام جوړونکو یو کتابتون جوړ کړ libbpf. موږ به د BPF غوښتنلیک بنسټیز کنکال جوړ کړو چې موږ به یې په راتلونکو مثالونو کې وکاروو.

د کرنل مرسته کونکي. دلته به موږ زده کړو چې څنګه د BPF پروګرامونه کولی شي د کرنل مرستندویه کارونو ته لاسرسی ومومي - یوه وسیله چې د نقشو سره یوځای د کلاسیک په پرتله د نوي BPF وړتیاوې په بنسټیز ډول پراخوي.

د BPF برنامو څخه نقشو ته لاسرسی. په دې وخت کې، موږ به په سمه توګه پوه شو چې څنګه موږ کولی شو هغه پروګرامونه جوړ کړو چې نقشه کاروي. او اجازه راکړئ حتی عالي او ځواکمن تصدیق کونکي ته یو ګړندي کتنه وکړو.

د پراختیا وسایل. د تجربو لپاره د اړتیا وړ اسانتیاو او کرنل راټولولو څرنګوالي په اړه د مرستې برخه.

پایله. د مقالې په پای کې ، هغه څوک چې دا لرې لوستل به هڅوونکي ټکي ومومي او په لاندې مقالو کې به څه پیښ شي لنډ توضیحات ومومي. موږ به د هغو کسانو لپاره د ځان مطالعې لپاره یو شمیر لینکونه هم لیست کړو څوک چې د دوام لپاره د انتظار کولو لیوالتیا یا وړتیا نلري.

د BPF معمارۍ پیژندنه

مخکې لدې چې موږ د BPF جوړښت ته پام وکړو ، موږ به یو وروستی ځل (او) ته راجع کړو کلاسیک BPF، کوم چې د RISC ماشینونو راتګ ته د ځواب په توګه رامینځته شوی او د اغیزمن پاکټ فلټر کولو ستونزه یې حل کړې. جوړښت دومره بریالی وګرځید چې په برکلي UNIX کې د نولسمې پیړۍ کې زیږیدلی، دا ډیری موجوده عملیاتي سیسټمونو ته لیږدول شوی، په شلو لسیزو کې ژوندی پاتې شوی او لاهم نوي غوښتنلیکونه لټوي.

نوی BPF د 64-bit ماشینونو د هر اړخیزې ځواب په توګه رامینځته شوی ، د کلاوډ خدماتو او د SDN رامینځته کولو لپاره وسیلو ته د اړتیا ډیروالی (Sسامان-dايفينډ nکار کول). د کرنل شبکې انجینرانو لخوا د کلاسیک BPF لپاره د ښه بدیل په توګه رامینځته شوی ، نوی BPF په حقیقت کې شپږ میاشتې وروسته د لینکس سیسټمونو تعقیب په ستونزمن کار کې غوښتنلیکونه وموندل ، او اوس ، د هغې له ظهور څخه شپږ کاله وروسته ، موږ به یوازې راتلونکي مقالې ته اړتیا ولرو. د پروګرامونو مختلف ډولونه لیست کړئ.

په زړه پورې انځورونه

د دې په اصلي برخه کې، BPF یو سینڈ باکس مجازی ماشین دی چې تاسو ته اجازه درکوي د امنیت سره موافقت پرته د کرنل ځای کې "خپلواک" کوډ پرمخ بوځي. د BPF برنامې د کارونکي ځای کې رامینځته شوي ، په کرنل کې بار شوي ، او د پیښې ځینې سرچینې سره وصل شوي. یوه پیښه کیدی شي ، د مثال په توګه ، د شبکې انٹرفیس ته د کڅوړې تحویلول ، د ځینې کرنل فعالیت پیل کول ، او داسې نور. د کڅوړې په حالت کې، د BPF برنامه به د کڅوړې ډیټا او میټاډاټا ته لاسرسی ولري (د لوستلو او احتمالي لیکلو لپاره ، د برنامه ډول پورې اړه لري)؛ د کرنل فنکشن چلولو په حالت کې ، دلیلونه فنکشن، په شمول د کرنل حافظې ته اشارې، او داسې نور.

راځئ چې دې پروسې ته نږدې کتنه وکړو. د پیل کولو لپاره، راځئ چې د کلاسیک BPF څخه د لومړي توپیر په اړه وغږیږو، هغه پروګرامونه چې په ترکیب کې لیکل شوي. په نوې نسخه کې، جوړښت پراخ شوی ترڅو پروګرامونه د لوړې کچې ژبو کې ولیکل شي، په ابتدايي توګه، البته، په C کې، د دې لپاره، د llvm لپاره یو پس منظر رامینځته شوی، کوم چې تاسو ته اجازه درکوي د BPF جوړښت لپاره بایټ کوډ تولید کړي.

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

د BPF جوړښت ډیزاین شوی و، په یوه برخه کې، په عصري ماشینونو کې په اغیزمنه توګه چلولو لپاره. د دې لپاره چې دا کار په عمل کې ترسره شي، د BPF بایټ کوډ، یوځل چې په کرنل کې بار شوی، د JIT کمپیلر په نوم د یوې برخې په کارولو سره اصلي کوډ ته ژباړل کیږي (Just In Time). بل ، که تاسو په یاد ولرئ ، په کلاسیک BPF کې برنامه په کرنل کې بار شوې او د پیښې سرچینې سره په اټومي ډول وصل شوې - د یو واحد سیسټم کال په شرایطو کې. په نوي جوړښت کې، دا په دوو مرحلو کې پیښیږي - لومړی، کوډ د سیسټم کال په کارولو سره د کرنل کې بار شوی bpf(2)او بیا، وروسته، د نورو میکانیزمونو له لارې چې د پروګرام ډول پورې اړه لري، برنامه د پیښې سرچینې سره نښلوي.

دلته شاید لوستونکی پوښتنه ولري: ایا دا ممکنه وه؟ د داسې کوډ اجرا کولو خوندیتوب څنګه تضمین شوی؟ د اعدام خوندیتوب موږ ته د BPF برنامو بارولو مرحلې لخوا تضمین شوی چې د ویریفیر په نوم یادیږي (په انګلیسي کې دې مرحله ته ویریفیر ویل کیږي او زه به د انګلیسي کلمې کارولو ته دوام ورکړم):

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

تصدیق کونکی یو جامد تحلیل کونکی دی چې ډاډ ترلاسه کوي چې برنامه د کرنل نورمال عملیات نه ګډوډوي. دا، په هرصورت، دا پدې معنی ندي چې برنامه نشي کولی د سیسټم په عملیاتو کې مداخله وکړي - د BPF پروګرامونه، د ډول پورې اړه لري، د کرنل حافظې برخې لوستل او بیا لیکلی شي، د دندو ارزښتونه بیرته راګرځول، ټرم، ضمیمه، بیا لیکل او حتی د شبکې کڅوړې وړاندې کوي. تصدیق کونکی تضمین کوي ​​​​چې د BPF برنامه چلول به کرنل خراب نکړي او دا چې یو برنامه چې د مقرراتو سره سم ، لیکلو ته لاسرسی لري ، د مثال په توګه ، د وتلو پاکټ ډیټا به د دې وړتیا ونلري چې د کڅوړې بهر د کرنل حافظه له سره لیکي. موږ به په اړونده برخه کې یو څه نور تفصیل سره تصدیق کونکي وګورو ، وروسته له دې چې موږ د BPF نورو ټولو برخو سره آشنا شو.

نو تر اوسه مو څه زده کړل؟ کارونکي په C کې برنامه لیکي ، د سیسټم کال په کارولو سره دا په کرنل کې باروي bpf(2)، چیرې چې دا د تصدیق کونکي لخوا چک کیږي او په اصلي بایټکوډ کې ژباړل کیږي. بیا ورته یا بل کارونکي برنامه د پیښې سرچینې سره وصل کوي او دا پلي کول پیل کوي. د بوټ او پیوستون جلا کول د ډیری دلیلونو لپاره اړین دي. لومړی، د تصدیق کونکي چلول نسبتا ګران دي او د ورته پروګرام څو ځله ډاونلوډ کولو سره موږ د کمپیوټر وخت ضایع کوو. دوهم، په ریښتیا چې څنګه یو برنامه وصل کیږي د هغې په ډول پورې اړه لري، او یو "نړیوال" انٹرفیس چې یو کال دمخه رامینځته شوی ممکن د نوي ډول برنامو لپاره مناسب نه وي. (که څه هم اوس چې جوړښت ډیر بالغ کیږي، د دې انٹرفیس په کچه د متحد کولو لپاره یو نظر شتون لري libbpf.)

پام لرونکی لوستونکی شاید پوه شي چې موږ لاهم د عکسونو سره بشپړ شوي نه یو. په حقیقت کې، پورته ټول نه تشریح کوي چې ولې BPF د کلاسیک BPF په پرتله انځور په بنسټیز ډول بدلوي. دوه نوښتونه چې د پام وړ د تطبیق ساحه پراخه کوي د شریک حافظې او د کرنل مرستندویه دندو کارولو وړتیا دي. په BPF کې، شریکه حافظه د تش په نامه نقشو په کارولو سره پلي کیږي - د ځانګړي API سره د شریک شوي ډاټا جوړښتونه. دوی شاید دا نوم ترلاسه کړي ځکه چې د لومړي ډول نقشه څرګندیدو لپاره د هش میز و. بیا صفونه ښکاره شول، ځایی (per-CPU) هش میزونه او محلي صفونه، د لټون ونې، نقشې چې د BPF پروګرامونو ته اشاره کوي او نور ډیر څه. هغه څه چې اوس زموږ لپاره په زړه پوري دي دا دي چې د BPF برنامې اوس د دې وړتیا لري چې د تلیفونونو ترمینځ حالت ته دوام ورکړي او دا د نورو برنامو او کارونکي ځای سره شریک کړي.

نقشې د سیسټم کال په کارولو سره د کارونکي پروسې څخه لاسرسی کیږي bpf(2)، او د BPF پروګرامونو څخه چې د مرستندویه کارونو په کارولو سره په کرنل کې روان دي. برسېره پردې، مرستندویان نه یوازې د نقشو سره کار کولو لپاره شتون لري، بلکې د نورو دانه وړتیاوو ته هم لاسرسی لري. د مثال په توګه، د BPF پروګرامونه کولی شي د نورو انټرفیسونو ته د پیکټو لیږلو لپاره د مرستندویه فعالیتونو څخه کار واخلي، د پرف پیښې رامینځته کړي، د کرنل جوړښتونو ته لاسرسی ومومي، او داسې نور.

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

په لنډیز کې، BPF دا وړتیا چمتو کوي چې په خپل سري ډول بار کړي، د بیلګې په توګه، د تصدیق کونکي-ټیسټ شوي، کارن کوډ د کرنل ځای ته. دا کوډ کولی شي د تلیفونونو ترمینځ حالت خوندي کړي او د کارونکي ځای سره ډیټا تبادله کړي ، او د دې ډول برنامې لخوا اجازه ورکړل شوي د کرنل فرعي سیسټمونو ته هم لاسرسی لري.

دا دمخه د کرنل ماډلونو لخوا چمتو شوي ظرفیتونو سره ورته دی ، د دې په پرتله چې BPF ځینې ګټې لري (البته ، تاسو یوازې ورته غوښتنلیکونه پرتله کولی شئ ، د مثال په توګه ، د سیسټم تعقیب - تاسو نشئ کولی د BPF سره خپل سري چلونکی ولیکئ). تاسو کولی شئ د ننوتلو ټیټ حد یاد کړئ (ځینې اسانتیاوې چې BPF کاروي کارن ته اړتیا نلري چې د کرنل برنامه کولو مهارتونه ولري ، یا په عمومي ډول د برنامه کولو مهارتونه ولري) ، د چلولو خوندیتوب (د هغو کسانو لپاره په نظرونو کې خپل لاس پورته کړئ چې د لیکلو پرمهال سیسټم نه ماتوي. یا د ازموینې ماډلونه)، اتومیت - د ماډلونو د بیا پورته کولو په وخت کې د ځنډ وخت شتون لري، او د BPF فرعي سیسټم ډاډ ورکوي چې هیڅ پیښې له لاسه نه ورکول کیږي (د عادلانه وي، دا د BPF پروګرامونو ټولو ډولونو لپاره ریښتیا ندي).

د دې ډول وړتیاو شتون BPF د کرنل پراخولو لپاره نړیواله وسیله ګرځوي ، کوم چې په عمل کې تایید شوی: په BPF کې ډیر او ډیر نوي ډولونه اضافه شوي ، ډیر او ډیر لوی شرکتونه BPF په جنګي سرورونو 24 × 7 کې کاروي. پیل خپل سوداګرۍ د حلونو پراساس رامینځته کوي چې د BPF پراساس دي. BPF هرچیرې کارول کیږي: د DDoS بریدونو پروړاندې محافظت کې ، د SDN رامینځته کول (د مثال په توګه ، د کبرنیټ لپاره شبکې پلي کول) ، د اصلي سیسټم تعقیب وسیلې او احصایې راټولونکي په توګه ، د مداخلې کشف سیسټمونو او سینڈ باکس سیسټمونو کې ، او داسې نور.

راځئ چې د مقالې عمومي لید برخه دلته پای ته ورسوو او مجازی ماشین او د BPF ایکوسیستم په ډیر تفصیل سره وګورو.

تحلیل: اسانتیاوې

په لاندې برخو کې د مثالونو د چلولو لپاره، تاسو ممکن یو شمیر اسانتیاوو ته اړتیا ولرئ، لږترلږه llvm/clang د bpf ملاتړ او bpftool. برخه کې د پراختیا وسیلې تاسو کولی شئ د اسانتیاوو د راټولولو لارښوونې ولولئ، او همدارنګه ستاسو دانه. دا برخه لاندې ایښودل شوې ترڅو زموږ د پریزنټشن همغږي ګډوډ نه کړي.

د BPF مجازی ماشین راجستر او لارښوونې سیسټم

د BPF جوړښت او قوماندې سیسټم د دې حقیقت په پام کې نیولو سره رامینځته شوی چې برنامه به په C ژبه کې لیکل کیږي او په کرنل کې د بار کولو وروسته به اصلي کوډ ته ژباړل کیږي. له همدې امله، د راجسترونو شمیر او د کمانډونو سیټ د تقاطع په نظر کې نیولو سره غوره شوي، په ریاضياتي لحاظ، د عصري ماشینونو وړتیاوو. سربیره پردې ، په برنامو مختلف محدودیتونه لګول شوي ، د مثال په توګه ، تر دې وروستیو پورې د لوپونو او سبروټینونو لیکل ممکن ندي ، او د لارښوونو شمیر 4096 پورې محدود و (اوس امتیاز شوي برنامې تر یو ملیون لارښوونې پورته کولی شي).

BPF یوولس د کارونکي د لاسرسي وړ 64-bit راجسترونه لري r0-r10 او د پروګرام کاونټر. راجستر r10 د چوکاټ پوائنټر لري او یوازې لوستل کیږي. پروګرامونه د چلولو په وخت کې د 512-بایټ سټیک ته لاسرسی لري او د نقشې په بڼه غیر محدود اندازه شریکه حافظه لري.

د BPF پروګرامونو ته اجازه ورکول کیږي چې د پروګرام ډول د کرنل مرستندویانو یو ځانګړی سیټ پرمخ بوځي او په دې وروستیو کې، منظم فعالیتونه. هر نومول شوی فنکشن کولی شي تر پنځو پورې دلیلونه واخلي، په راجسترونو کې تیریږي r1-r5، او د بیرته ستنیدو ارزښت ته لیږدول کیږي r0. دا تضمین دی چې د فعالیت څخه بیرته راستنیدو وروسته، د راجستر منځپانګې r6-r9 نه به بدل شي.

د مؤثره پروګرام ژباړې لپاره، راجستر کړئ r0-r11 د ټولو ملاتړ شوي معمارۍ لپاره په ځانګړي ډول اصلي راجسترونو ته نقشه شوي ، د اوسني جوړښت د ABI ځانګړتیاو په پام کې نیولو سره. د مثال په توګه، لپاره x86_64 ثبتونه r1-r5، د فعالیت پیرامیټونو تیرولو لپاره کارول کیږي ، په کې ښودل شوي rdi, rsi, rdx, rcx, r8، کوم چې د فعالیت لپاره د پیرامیټونو لیږدولو لپاره کارول کیږي x86_64. د مثال په توګه، په چپ اړخ کې کوډ په ښي خوا کې کوډ ته ژباړل کیږي لکه:

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

راجستر r0 د برنامه اجرا کولو پایلې بیرته راستنولو لپاره هم کارول کیږي، او په راجستر کې r1 برنامه شرایطو ته اشاره کوي - د برنامې ډول پورې اړه لري ، دا کیدی شي ، د مثال په توګه ، جوړښت struct xdp_md (د XDP لپاره) یا جوړښت struct __sk_buff (د مختلفو شبکو پروګرامونو لپاره) یا جوړښت struct pt_regs (د مختلفو ډولونو د تعقیب پروګرامونو لپاره)، او داسې نور.

نو، موږ د نقشو په بڼه د راجسترونو، کرنل مرستندویانو، یو سټیک، د شرایطو پوائنټر او شریک حافظه درلوده. داسې نه چې دا ټول په سفر کې اړین دي، مګر ...

راځئ چې توضیحاتو ته دوام ورکړو او د دې شیانو سره د کار کولو لپاره د کمانډ سیسټم په اړه وغږیږو. ټول (نږدې ټول) د BPF لارښوونې یو ثابت 64-bit اندازه لري. که تاسو په 64-bit لوی انډین ماشین کې یوې لارښوونې ته ګورئ تاسو به وګورئ

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

دا Code - دا د لارښوونې کوډ کول دي، Dst/Src د رسیدونکي او سرچینې کوډونه دي، په ترتیب سره، Off - 16-بټ لاسلیک شوی انډینټیشن، او Imm یو 32-bit لاسلیک شوی عدد دی چې په ځینو لارښوونو کې کارول کیږي (د cBPF ثابت K سره ورته دی). کوډ کول Code یو له دوو ډولونو څخه دی:

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

د لارښوونې ټولګي 0، 1، 2، 3 د حافظې سره کار کولو لپاره کمانډونه تعریفوي. دوی نومیږي, BPF_LD, BPF_LDX, BPF_ST, BPF_STXپه ترتیب سره. ټولګي 4, 7 (BPF_ALU, BPF_ALU64) د ALU لارښوونو یوه ټولګه جوړه کړئ. ټولګي 5, 6 (BPF_JMP, BPF_JMP32) د کود لارښوونې لري.

د BPF لارښوونې سیسټم مطالعې لپاره نور پلان په لاندې ډول دی: د دې پرځای چې ټول لارښوونې او د دوی پیرامیټرونه په دقت سره لیست کړو ، موږ به پدې برخه کې یو څو مثالونه وګورو او له دوی څخه به څرګنده شي چې لارښوونې واقعیا څنګه کار کوي او څنګه. د BPF لپاره هر ډول بائنری فایل په لاسي ډول جلا کړئ. په مقاله کې وروسته د موادو د یوځای کولو لپاره، موږ به د تصدیق کونکي، JIT کمپیلر، د کلاسیک BPF ژباړه، او همدارنګه د نقشو مطالعه کولو، د زنګ وهلو افعال، او نور په اړه برخو کې د انفرادي لارښوونو سره هم لیدنه وکړو.

کله چې موږ د انفرادي لارښوونو په اړه خبرې کوو، موږ به اصلي فایلونو ته مراجعه وکړو bpf.h и bpf_common.h، کوم چې د BPF لارښوونو شمیري کوډونه تعریفوي. کله چې پخپله د معمارۍ مطالعه کوئ او / یا د بائنریونو تحلیل کول، تاسو کولی شئ په لاندې سرچینو کې سیمانټیک ومومئ، د پیچلتیا په ترتیب سره ترتیب شوي: د eBPF غیر رسمي مشخصات, د BPF او XDP حوالې لارښود، لارښوونې ترتیب, Documentation/networking/filter.txt او البته، د لینکس سرچینې کوډ کې - تصدیق کوونکی، JIT، BPF ژباړونکی.

بېلګه: ستاسو په سر کې د BPF جلا کول

راځئ چې یو مثال وګورو په کوم کې چې موږ یو برنامه ترتیب کوو readelf-example.c او پایله لرونکی بائنری وګورئ. موږ به اصلي مینځپانګه ښکاره کړو readelf-example.c لاندې، وروسته له دې چې موږ د بائنری کوډونو څخه د هغې منطق بیرته راوړو:

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

په تولید کې لومړی کالم readelf یو انډینټیشن دی او زموږ برنامه پدې توګه څلور حکمونه لري:

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

د کمانډ کوډونه مساوي دي b7, 15, b7 и 95. په یاد ولرئ چې لږترلږه د پام وړ درې بټونه د لارښوونې ټولګي دي. زموږ په قضیه کې، د ټولو لارښوونو څلورم بټ خالي دی، نو د لارښوونې ټولګي په ترتیب سره 7، 5، 7، 5 دي. اووم ټولګی دی. BPF_ALU64، او 5 دی BPF_JMP. د دواړو ټولګیو لپاره، د لارښوونې بڼه یو شان ده (پورته وګورئ) او موږ کولی شو خپل برنامه د دې په څیر بیا ولیکو (په ورته وخت کې به موږ پاتې کالمونه په انساني بڼه بیا لیکو):

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

عملیات b کلونه ALU64 دی BPF_MOV. دا د منزل راجستر ته ارزښت ورکوي. که بیټ ترتیب شوی وي s (سرچینه)، بیا ارزښت د سرچینې راجستر څخه اخیستل کیږي، او که زموږ په قضیه کې، دا ټاکل شوی نه وي، نو بیا ارزښت د ساحې څخه اخیستل کیږي Imm. نو په لومړي او دریم لارښوونو کې موږ عملیات ترسره کوو r0 = Imm. برسېره پردې، د JMP ټولګي 1 عملیات دي BPF_JEQ (که مساوي ټوپ کړئ). زموږ په قضیه کې، د بټ څخه S صفر دی، دا د سرچینې راجستر ارزښت د ساحې سره پرتله کوي Imm. که ارزښتونه سره سمون ولري، نو بیا لیږد واقع کیږي PC + Offچیرته PCد معمول په څیر، د راتلونکي لارښوونې پته لري. په نهایت کې ، د JMP ټولګي 9 عملیات دي BPF_EXIT. دا لارښوونه برنامه پای ته رسوي ، کرنل ته راستنیږي r0. راځئ چې زموږ په میز کې یو نوی کالم اضافه کړو:

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

موږ کولی شو دا په ډیر مناسب شکل کې بیا ولیکو:

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

که موږ په یاد ولرئ چې په راجستر کې څه دي r1 برنامه د کرنل څخه شرایطو ته اشاره کوي ، او په راجستر کې r0 ارزښت د کرنل ته راستانه شوی، بیا موږ لیدلی شو چې که د شرایطو اشاره صفر وي، نو موږ 1 بیرته راګرځوو، او بل ډول - 2. راځئ چې د سرچینې په کتلو سره وګورو چې موږ سم یو:

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

هو، دا یو بې معنی پروګرام دی، مګر دا یوازې په څلورو ساده لارښوونو کې ژباړه کوي.

د استثنا مثال: 16-بایټ لارښوونه

موږ مخکې یادونه وکړه چې ځینې لارښوونې له 64 بټونو څخه ډیر وخت نیسي. دا د مثال په توګه په لارښوونو باندې تطبیق کیږي lddw (کوډ = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - د ساحو څخه دوه کلمه راجستر ته پورته کړئ Imm... حقیقت دا دی Imm د 32 اندازه لري، او دوه کلمه 64 بټ ده، نو په یوه 64-بټ لارښوونې کې په راجستر کې د 64-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                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

موږ به بیا د لارښوونو سره ووینو lddw، کله چې موږ د ځای په ځای کولو او د نقشو سره کار کولو په اړه خبرې کوو.

بېلګه: د معیاري وسیلو په کارولو سره د BPF جلا کول

نو، موږ د BPF بائنری کوډونو لوستل زده کړل او د اړتیا په صورت کې هر ډول لارښوونې پارس کولو ته چمتو یو. په هرصورت، دا د ویلو وړ ده چې په عمل کې دا د معیاري وسیلو په کارولو سره د پروګرامونو جلا کول خورا اسانه او ګړندي دي، د بیلګې په توګه:

$ llvm-objdump -d x64.o

Disassembly of section .text:

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

د BPF شیانو ژوند دوره، د bpffs فایل سیسټم

(ما لومړی په دې فرعي برخه کې تشریح شوي ځینې توضیحات زده کړل روژه الیکسي ستاروویتوف په کې د BPF بلاګ.)

د BPF توکي - برنامې او نقشې - د امرونو په کارولو سره د کارونکي ځای څخه رامینځته شوي BPF_PROG_LOAD и BPF_MAP_CREATE سیسټم زنګ bpf(2)، موږ به په دې اړه وغږیږو چې دا څنګه په راتلونکي برخه کې پیښیږي. دا د کرنل ډیټا جوړښتونه رامینځته کوي او د هر یو لپاره refcount (د حوالې شمیره) یو ته ټاکل شوې، او د فایل تشریح کونکی چې اعتراض ته اشاره کوي کارونکي ته بیرته راستانه کیږي. وروسته له دې چې لاسوند بند شي refcount څيز يو له بل سره کميږي او کله چې صفر ته ورسيږي څيز له منځه ځي.

که برنامه نقشه کاروي، بیا refcount دا نقشې د برنامه له پورته کولو وروسته یو له بل سره لوړیږي ، د بیلګې په توګه. د دوی د فایل توضیحات د کارونکي پروسې څخه تړل کیدی شي او لاهم refcount صفر به نه شي:

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

د برنامه په بریالیتوب سره پورته کولو وروسته ، موږ معمولا دا د یو ډول پیښې جنریټر سره ضمیمه کوو. د مثال په توګه، موږ کولی شو دا د شبکې انٹرفیس کې واچوو ترڅو د راتلونکو کڅوړو پروسس کولو یا ځینې سره وصل کړو tracepoint په اصلي. په دې وخت کې، د حوالې کاونټر به یو له خوا زیات شي او موږ به وکولی شو د فایل ډیسکریټر په لوډر پروګرام کې بند کړو.

څه پیښیږي که موږ اوس بوټلوډر بند کړو؟ دا د پیښې جنراتور (هک) ډول پورې اړه لري. د لوډر بشپړولو وروسته به د شبکې ټول هکونه شتون ولري، دا تش په نامه نړیوال هکونه دي. او، د بیلګې په توګه، د ټریس پروګرامونه به د پروسې وروسته خوشې شي چې دوی یې پای ته رسولي (او له همدې امله محلي بلل کیږي، له "محلي څخه پروسې ته"). په تخنیکي توګه، محلي هکونه تل د کاروونکي ځای کې د اړونده فایل تشریح کونکی لري او له همدې امله تړل کیږي کله چې پروسه بنده شي، مګر نړیوال هکونه نه کوي. په لاندې شکل کې، د سره صلیب په کارولو سره، زه هڅه کوم چې وښیم چې څنګه د لوډر برنامه پای ته رسیدل د ځایی او نړیوالو هکونو په قضیه کې د شیانو په ژوند اغیزه کوي.

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

ولې د محلي او نړیوالو هکونو ترمنځ توپیر شتون لري؟ د ځینې ډوله شبکې برنامو چلول د کارونکي ځای پرته معنی لري ، د مثال په توګه ، د DDoS محافظت تصور کړئ - بوټلوډر قواعد لیکي او د BPF برنامه د شبکې انٹرفیس سره وصل کوي ، وروسته له دې چې بوټلوډر کولی شي ځان وژني. له بلې خوا، د ډیبګ کولو ټریس برنامه تصور کړئ چې تاسو په لسو دقیقو کې په خپلو زنګونونو لیکلی - کله چې دا پای ته ورسیږي، تاسو غواړئ په سیسټم کې هیڅ کثافات پاتې نه وي، او ځایی هکونه به دا یقیني کړي.

له بلې خوا، تصور وکړئ چې تاسو غواړئ په کرنل کې د ټریس پواینټ سره وصل شئ او د ډیرو کلونو لپاره احصایې راټول کړئ. په دې حالت کې، تاسو غواړئ د کاروونکي برخه بشپړه کړئ او وخت په وخت احصایې ته راستانه شئ. د bpf فایل سیسټم دا فرصت چمتو کوي. دا یوازې په حافظه کې د سیډو فایل سیسټم دی چې د فایلونو رامینځته کولو ته اجازه ورکوي چې د BPF شیانو ته اشاره کوي او پدې توګه وده کوي refcount توکي له دې وروسته، لوډر کولی شي ووځي، او هغه شیان چې دا جوړ کړي ژوندي پاتې شي.

د کوچنيانو لپاره BPF، لومړۍ برخه: پراخ شوی BPF

په bpffs کې د فایلونو رامینځته کول چې د BPF شیانو ته اشاره کوي د "پننګ" په نوم یادیږي (لکه څنګه چې په لاندې جمله کې: "پروسس کولی شي د BPF برنامه یا نقشه پین ​​کړي"). د BPF شیانو لپاره د فایل څیزونو رامینځته کول نه یوازې د ځایی شیانو د ژوند اوږدولو لپاره ، بلکه د نړیوال شیانو کارولو لپاره هم معنی لري - د نړیوال DDoS محافظت برنامې سره مثال ته بیرته تلل ، موږ غواړو د دې وړتیا ولرو چې راشي او احصایې وګورو. وخت په وخت.

د BPF فایل سیسټم معمولا نصب شوی /sys/fs/bpf، مګر دا په محلي توګه هم نصب کیدی شي، د بیلګې په توګه، دا:

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

د فایل سیسټم نومونه د کمانډ په کارولو سره رامینځته شوي BPF_OBJ_PIN د BPF سیسټم زنګ. د روښانه کولو لپاره، راځئ چې یو برنامه واخلو، تالیف یې کړو، پورته یې کړو، او هغه یې پین کړئ bpffs. زموږ برنامه هیڅ ګټور کار نه کوي ، موږ یوازې کوډ وړاندې کوو نو تاسو کولی شئ مثال بیا تولید کړئ:

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

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

راځئ چې دا پروګرام تالیف کړو او د فایل سیسټم یوه محلي کاپي جوړه کړو bpffs:

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

اوس راځئ چې د یوټیلیټ په کارولو سره زموږ برنامه ډاونلوډ کړو bpftool او د اړوند سیسټم تلیفونونو ته وګورئ bpf(2) (ځینې غیر اړونده کرښې د سټریس محصول څخه لرې شوي):

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

دلته موږ برنامه په کارولو سره پورته کړې BPF_PROG_LOAD، د کرنل څخه د فایل توضیح کونکی ترلاسه کړ 3 او د کمانډ په کارولو سره BPF_OBJ_PIN د دې فایل تشریح کونکی د فایل په توګه پین ​​کړی "bpf-mountpoint/test". له دې وروسته د بوټلوډر برنامه bpftool کار یې پای ته ورساوه، مګر زموږ برنامه په کرنل کې پاتې شوه، که څه هم موږ دا د کومې شبکې انٹرفیس سره نه و نښلول:

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

موږ کولی شو د فایل څیز په نورمال ډول حذف کړو unlink(2) او له هغې وروسته به اړونده برنامه حذف شي:

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

د شیانو حذف کول

د شیانو د حذف کولو په اړه خبرې کول ، دا اړینه ده چې روښانه کړو چې وروسته له دې چې موږ برنامه له هک (ایونټ جنریټر) څخه منقطع کړو ، یوه نوې پیښه به د هغې لانچ نه پیل کړي ، په هرصورت ، د برنامه ټول اوسني مثالونه به په نورمال ترتیب کې بشپړ شي. .

د BPF ځینې ډولونه تاسو ته اجازه درکوي چې برنامه په الوتنه کې ځای په ځای کړئ، د بیلګې په توګه. د اتومیکیت ترتیب چمتو کړئ 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

وروسته له دې چې موږ پریکړه وکړه چې موږ به اپلوډ کړو، موږ کولی شو تاسو ته ووایو چې موږ به دا څنګه وکړو:

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

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

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

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

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

    for ( ;; )
        pause();
}

په برنامه کې په زړه پورې پیښې د صف تعریف سره پیل کیږي insns - زموږ د BPF برنامه په ماشین کوډ کې. په دې حالت کې، د BPF پروګرام هره لارښوونه په جوړښت کې بسته شوې ده bpf_insn. لومړی عنصر insns د لارښوونو سره سمون لري r0 = 2، دوهم - exit.

اعتکاف. کرنل د ماشین کوډونو لیکلو او د کرنل سرلیک فایل کارولو لپاره خورا اسانه میکرو تعریفوي tools/include/linux/filter.h موږ لیکلی شوای

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

مګر له هغه ځایه چې په اصلي کوډ کې د BPF برنامو لیکل یوازې په دانا کې د ازموینو لیکلو او د BPF په اړه مقالو لپاره اړین دي ، د دې میکرو نشتوالی واقعیا د پراختیا کونکي ژوند پیچلی نه کوي.

د BPF برنامه تعریف کولو وروسته ، موږ دا په کرنل کې بارولو ته ځو. زموږ د پیرامیټونو لږترلږه سیټ attr د پروګرام ډول، ترتیب او د لارښوونو شمیر، اړین جواز، او نوم شامل دي "woo"، کوم چې موږ د ډاونلوډ کولو وروسته په سیسټم کې زموږ برنامه موندلو لپاره کاروو. برنامه ، لکه څنګه چې ژمنه شوې ، د سیسټم کال په کارولو سره سیسټم کې بار شوی bpf.

د برنامه په پای کې موږ په یو لامحدود لوپ کې پای ته ورسیږو چې د تادیې سمولو کوي. له دې پرته، برنامه به د کرنل لخوا ووژل شي کله چې د فایل ډیسکریټر چې سیسټم یې موږ ته بیرته راګرځوي بند شي bpf، او موږ به دا په سیسټم کې ونه ګورو.

ښه، موږ د ازموینې لپاره چمتو یو. راځئ چې لاندې پروګرام راټول او پرمخ بوځو straceد دې لپاره چې وګورئ چې هرڅه کار کوي لکه څنګه چې باید وي:

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

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

هر څه سم دي، bpf(2) لاستی 3 موږ ته راستانه شو او موږ د لامحدود لوپ سره لاړو pause(). راځئ هڅه وکړو چې خپل برنامه په سیسټم کې ومومئ. د دې کولو لپاره موږ به بل ټرمینل ته لاړ شو او یوټیلیټ وکاروو bpftool:

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

موږ ګورو چې په سیسټم کې یو بار شوی برنامه شتون لري woo د چا نړیوال ID 390 دی او دا مهال د پرمختګ په حال کې دی simple-prog دلته یو خلاص فایل تشریح کونکی دی چې برنامه ته اشاره کوي (او که simple-prog بیا به کار پای ته ورسوي woo ورک به شي). لکه څنګه چې تمه کیده، برنامه woo د BPF په جوړښت کې د بائنری کوډونو - دوه لارښوونې - 16 بایټونه اخلي، مګر په خپل اصلي بڼه (x86_64) کې دا دمخه 40 بایټ دی. راځئ چې زموږ پروګرام په اصلي بڼه وګورو:

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

هیڅ حیرانتیا نشته. اوس راځئ چې د JIT کمپیلر لخوا رامینځته شوي کوډ ته وګورو:

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

لپاره ډیر اغیزمن ندي exit(2)، مګر په عادلانه توګه ، زموږ برنامه خورا ساده ده ، او د غیر معمولي برنامو لپاره د JIT تالیف کونکي لخوا اضافه شوي پرولوګ او ایپیلوګ ، البته ، اړین دي.

نقشه

د BPF برنامې کولی شي د حافظې جوړښت لرونکي ساحې وکاروي چې د BPF نورو برنامو او د کارونکي ځای کې برنامو ته دواړه د لاسرسي وړ دي. دا توکي د نقشې په نوم یادیږي او پدې برخه کې به موږ وښیو چې څنګه د سیسټم کال په کارولو سره دوی مینځل کیږي bpf.

راځئ چې سمدلاسه ووایو چې د نقشو وړتیاوې یوازې شریکې حافظې ته لاسرسي پورې محدود ندي. دلته د ځانګړو موخو نقشې شتون لري، د بیلګې په توګه، د BPF پروګرامونو ته اشاره یا د شبکې انٹرفیسونو ته اشاره، د پرف پیښو سره د کار کولو نقشې، او داسې نور. موږ به دلته د دوی په اړه خبرې ونه کړو، ترڅو لوستونکی مغشوش نه شي. د دې تر څنګ، موږ د همغږي کولو مسلې له پامه غورځوو، ځکه چې دا زموږ د مثالونو لپاره مهم ندي. د شته نقشو ډولونو بشپړ لیست په کې موندل کیدی شي <linux/bpf.h>، او پدې برخه کې به موږ د مثال په توګه په تاریخي ډول لومړی ډول ، د هش جدول واخلو BPF_MAP_TYPE_HASH.

که تاسو د هش میز جوړ کړئ، ووایاست، C++، تاسو به ووایاست unordered_map<int,long> woo، چې په روسیه کې معنی لري "زه میز ته اړتیا لرم woo لامحدود اندازه، چې کیلي یې ډول دي int، او ارزښتونه ډول دي long" د BPF هش میز جوړولو لپاره، موږ باید ورته کار وکړو، پرته له دې چې موږ باید د میز اعظمي اندازه مشخص کړو، او د کیلي او ارزښتونو ډولونو مشخص کولو پرځای، موږ اړتیا لرو چې د دوی اندازه په بایټ کې مشخص کړو. . د نقشې جوړولو لپاره کمانډ وکاروئ BPF_MAP_CREATE سیسټم زنګ bpf. راځئ چې لږ یا لږ لږ برنامه وګورو چې نقشه رامینځته کوي. د تیر پروګرام وروسته چې د BPF پروګرامونه باروي، دا باید تاسو ته ساده ښکاري:

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

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

    for ( ;; )
        pause();
}

دلته موږ د پیرامیټونو یوه ټولګه تعریف کوو attr، په کوم کې چې موږ وایو "زه د کیلي او اندازې ارزښتونو سره د هش میز ته اړتیا لرم sizeof(int)، په کوم کې چې زه کولی شم اعظمي څلور عناصر واچوم." کله چې د BPF نقشې رامینځته کړئ ، تاسو کولی شئ نور پیرامیټونه مشخص کړئ ، د مثال په توګه ، په ورته ډول لکه څنګه چې د برنامې سره مثال کې ، موږ د څیز نوم په توګه مشخص کړ. "woo".

راځئ چې برنامه تالیف او چل کړو:

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

دلته د سیسټم غږ دی bpf(2) موږ ته د توضیحي نقشې شمیره راکړه 3 او بیا برنامه ، لکه څنګه چې تمه کیږي ، د سیسټم کال کې نورو لارښوونو ته انتظار باسي pause(2).

اوس راځئ چې خپل برنامه شالید ته ولیږو یا بل ټرمینل خلاص کړو او د یوټیلیټ په کارولو سره زموږ څیز وګورو bpftool (موږ کولی شو خپله نقشه له نورو څخه د دې نوم په واسطه توپیر کړو):

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

114 شمیره زموږ د اعتراض نړیوال ID دی. په سیسټم کې هر برنامه کولی شي دا ID د کمانډ په کارولو سره د موجوده نقشې خلاصولو لپاره وکاروي BPF_MAP_GET_FD_BY_ID سیسټم زنګ bpf.

اوس موږ کولی شو زموږ د هش میز سره لوبې وکړو. راځئ چې محتوا یې وګورو:

$ sudo bpftool map dump id 114
Found 0 elements

خالي. راځئ چې په هغې کې ارزښت واچوو hash[1] = 1:

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

راځئ چې بیا میز ته وګورو:

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

هورې! موږ وکولی شو یو عنصر اضافه کړو. په یاد ولرئ چې موږ باید د دې کولو لپاره د بایټ په کچه کار وکړو، ځکه چې bptftool نه پوهیږي چې په هش جدول کې کوم ډول ارزښتونه دي. (دا پوهه د BTF په کارولو سره هغې ته رسول کیدی شي، مګر اوس په دې اړه نور.)

bpftool څنګه په حقیقت کې عناصر لوستل او اضافه کوي؟ راځئ چې د هود لاندې یو نظر وګورو:

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

لومړی موږ د کمانډ په کارولو سره نقشه د هغې نړیوال ID لخوا خلاص کړه BPF_MAP_GET_FD_BY_ID и bpf(2) موږ ته تشریح کوونکی 3 بیرته راستانه شو. نور د کمانډ په کارولو سره BPF_MAP_GET_NEXT_KEY موږ په تیریدو سره په جدول کې لومړۍ کیلي وموندله NULL د "مخکیني" کیلي ته د اشارې په توګه. که موږ کلیدي لرو موږ یې کولی شو BPF_MAP_LOOKUP_ELEMکوم چې یو پوائنټر ته ارزښت بیرته راګرځوي value. بل ګام دا دی چې موږ اوسني کیلي ته د پوائنټر په لیږدولو سره راتلونکی عنصر ومومئ، مګر زموږ جدول یوازې یو عنصر او کمانډ لري BPF_MAP_GET_NEXT_KEY بیرته راګرځي ENOENT.

ښه، راځئ چې ارزښت د کلیدي 1 لخوا بدل کړو، راځئ چې ووایو زموږ د سوداګرۍ منطق راجستر کولو ته اړتیا لري hash[1] = 2:

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

لکه څنګه چې تمه کیده، دا خورا ساده دی: کمانډ BPF_MAP_GET_FD_BY_ID زموږ نقشه د ID او کمانډ له لارې خلاصوي BPF_MAP_UPDATE_ELEM عنصر بیا لیکي.

نو، د یو پروګرام څخه د هش میز جوړولو وروسته، موږ کولی شو د بل پروګرام څخه د هغې محتويات ولولو او ولولو. په یاد ولرئ چې که موږ دا د کمانډ لاین څخه ترسره کولی شو، نو بیا په سیسټم کې کوم بل پروګرام کولی شي دا ترسره کړي. د پورته بیان شوي حکمونو سربیره، د کاروونکي ځای څخه د نقشو سره کار کولو لپاره، لاندې:

  • BPF_MAP_LOOKUP_ELEM: د کیلي په واسطه ارزښت ومومئ
  • BPF_MAP_UPDATE_ELEM: ارزښت تازه کړئ / جوړ کړئ
  • BPF_MAP_DELETE_ELEM: کیلي لرې کړئ
  • BPF_MAP_GET_NEXT_KEY: راتلونکی (یا لومړی) کیلي ومومئ
  • BPF_MAP_GET_NEXT_ID: تاسو ته اجازه درکوي د ټولو موجوده نقشو له لارې لاړ شئ، دا څنګه کار کوي bpftool map
  • BPF_MAP_GET_FD_BY_ID: د خپل نړیوال ID لخوا موجوده نقشه پرانیزئ
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: په اټومي ډول د یو څیز ارزښت تازه کړئ او زوړ بیرته راوباسئ
  • BPF_MAP_FREEZE: نقشه د کاروونکي ځای څخه بدله وړ کړئ (دا عملیات نشي رد کیدی)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: ډله ایز عملیات د مثال په ډول، BPF_MAP_LOOKUP_AND_DELETE_BATCH - دا د نقشې څخه د ټولو ارزښتونو لوستلو او بیا تنظیم کولو یوازینۍ معتبره لار ده

دا ټول حکمونه د ټولو نقشو ډولونو لپاره کار نه کوي، مګر په عموم کې د کاروونکي ځای څخه د نورو ډولونو نقشو سره کار کول د هش میزونو سره کار کولو په څیر ورته ښکاري.

د نظم لپاره، راځئ چې زموږ د هش میز تجربې پای ته ورسوو. په یاد ولرئ چې موږ یو جدول جوړ کړی چې کولی شي تر څلورو کیلي ولري؟ راځئ چې یو څو نور عناصر اضافه کړو:

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

تر دې دمه ښه:

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

راځئ چې یو بل اضافه کولو هڅه وکړو:

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

لکه څنګه چې تمه کیده، موږ بریالي نه شو. راځئ چې تېروتنه په تفصیل سره وګورو:

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

هرڅه سم دي: لکه څنګه چې تمه کیده، ټیم BPF_MAP_UPDATE_ELEM هڅه کوي یو نوی، پنځم، کیلي رامینځته کړي، مګر ټکر کوي E2BIG.

نو، موږ کولی شو د BPF پروګرامونه جوړ او پورته کړو، په بیله بیا د کارن ځای څخه نقشه جوړ او اداره کړو. اوس دا منطقي ده چې وګورو چې څنګه موږ کولی شو پخپله د BPF برنامو څخه نقشې وکاروو. موږ د دې په اړه د ماشین میکرو کوډونو کې د لوستلو سختو برنامو په ژبه خبرې کولی شو ، مګر په حقیقت کې د دې وخت رارسیدلی چې وښیو چې د BPF برنامې واقعیا څنګه لیکل کیږي او ساتل کیږي - کارول. libbpf.

(د هغو لوستونکو لپاره چې د ټیټې کچې مثال نشتوالي څخه ناخوښه دي: موږ به په تفصیل سره پروګرامونه تحلیل کړو چې نقشې او مرستندویه فعالیتونه کاروي libbpf او تاسو ته ووایاست چې د لارښوونې په کچه څه پیښیږي. د هغو لوستونکو لپاره چې ناخوښه دي ډېر، موږ زیاته کړه مثال په مقاله کې په مناسب ځای کې.)

د libbpf په کارولو سره د BPF برنامې لیکل

د ماشین کوډونو په کارولو سره د BPF برنامو لیکل یوازې د لومړي ځل لپاره په زړه پوري کیدی شي ، او بیا اطمینان په کې راځي. په دې وخت کې تاسو باید خپل پام واړوئ llvm، کوم چې د BPF معمارۍ لپاره د کوډ رامینځته کولو لپاره شالید لري ، او همدارنګه یو کتابتون libbpf، کوم چې تاسو ته اجازه درکوي د BPF غوښتنلیکونو کارونکي اړخ ولیکئ او د BPF برنامو کوډ پورته کړئ چې په کارولو سره رامینځته شوي. llvm/clang.

په حقیقت کې، لکه څنګه چې موږ به په دې او راتلونکو مقالو کې وګورو، libbpf پرته له دې ډیر کار کوي (یا ورته وسیلې - iproute2, libbcc, libbpf-goاو داسې نور) ژوند کول ناشوني دي. د پروژې د وژونکو ځانګړتیاوو څخه یو libbpf د BPF CO-RE دی (یوځل تالیف کړئ ، هرچیرې چل کړئ) - یوه پروژه چې تاسو ته اجازه درکوي د BPF برنامې ولیکئ چې له یو کرنل څخه بل ته د پورټ وړ وي ، په مختلف APIs کې د چلولو وړتیا سره (د مثال په توګه ، کله چې د کرنل جوړښت له نسخې څخه بدلیږي) نسخه ته). د دې لپاره چې د CO-RE سره کار کولو وړتیا ولرئ، ستاسو دانه باید د BTF ملاتړ سره جوړه شي (موږ په دې برخه کې تشریح کوو چې دا څنګه ترسره کړو د پراختیا وسیلې. تاسو کولی شئ وګورئ چې ایا ستاسو دانه د BTF سره جوړه شوې ده یا نه په ساده ډول - د لاندې فایل په شتون سره:

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

دا فایل د ټولو ډیټا ډولونو په اړه معلومات ذخیره کوي چې په کرنل کې کارول کیږي او زموږ په ټولو مثالونو کې کارول کیږي libbpf. موږ به په راتلونکي مقاله کې د CO-RE په اړه په تفصیل سره وغږیږو ، مګر پدې کې - یوازې خپل ځان سره دانه جوړ کړئ CONFIG_DEBUG_INFO_BTF.

کړی libbpf په لارښود کې سم ژوند کوي tools/lib/bpf کرنل او د هغې پراختیا د بریښنالیک لیست له لارې ترسره کیږي [email protected]. په هرصورت، د کرنل څخه بهر ژوند کولو غوښتنلیکونو اړتیاو لپاره جلا ذخیره ساتل کیږي https://github.com/libbpf/libbpf په کوم کې چې د کرنل کتابتون د لوستلو لاسرسي لپاره منعکس شوی یا لږ څه لکه څنګه چې دی.

پدې برخه کې به موږ وګورو چې تاسو څنګه کولی شئ یوه پروژه جوړه کړئ چې کاروي libbpf، راځئ چې ډیری (ډیر یا لږ بې معنی) ازموینې برنامې ولیکئ او په تفصیل سره تحلیل کړئ چې دا ټول څنګه کار کوي. دا به موږ ته اجازه راکړو چې په لاندې برخو کې په اسانۍ سره تشریح کړو چې څنګه د BPF پروګرامونه د نقشو، د کرنل مرستندویانو، BTF، او نورو سره اړیکه لري.

معمولا پروژې کاروي libbpf د git submodule په توګه د GitHub ذخیره اضافه کړئ، موږ به ورته کار وکړو:

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

ورتګ ... ورته libbpf خورا ساده:

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

پدې برخه کې زموږ راتلونکی پلان په لاندې ډول دی: موږ به د BPF برنامه لیکو BPF_PROG_TYPE_XDP، د تیر مثال په څیر ، مګر په C کې ، موږ یې په کارولو سره تالیف کوو clang، او یو مرستندویه پروګرام ولیکئ چې دا به په کرنل کې بار کړي. په لاندې برخو کې به موږ د BPF پروګرام او معاون پروګرام دواړه وړتیاوې پراخې کړو.

بېلګه: د libbpf په کارولو سره د بشپړ غوښتنلیک رامینځته کول

د پیل کولو لپاره، موږ فایل کاروو /sys/kernel/btf/vmlinux، کوم چې پورته یادونه وشوه، او د سرلیک فایل په بڼه یې برابر جوړ کړئ:

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

دا فایل به زموږ په کرنل کې موجود ټول ډیټا جوړښتونه ذخیره کړي، د بیلګې په توګه، دا څنګه د IPv4 سرلیک په کرنل کې تعریف شوی:

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

اوس موږ به زموږ د BPF برنامه په C کې ولیکو:

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

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

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

که څه هم زموږ برنامه خورا ساده وه ، موږ لاهم اړتیا لرو ډیری توضیحاتو ته پاملرنه وکړو. لومړی، لومړی سرلیک فایل چې موږ پکې شامل یو vmlinux.h، کوم چې موږ یوازې په کارولو سره رامینځته کړی bpftool btf dump - اوس موږ اړتیا نلرو چې د کرنل سرلیک بسته نصب کړو ترڅو ومومئ چې د کرنل جوړښت څه ډول ښکاري. لاندې سرلیک فایل له کتابتون څخه موږ ته راځي libbpf. اوس موږ یوازې دې ته اړتیا لرو چې میکرو تعریف کړو SEC، کوم چې کرکټر د ELF اعتراض فایل مناسب برخې ته لیږي. زموږ پروګرام په دې برخه کې شتون لري xdp/simple، چیرې چې د سلیش دمخه موږ د برنامه ډول BPF تعریف کوو - دا هغه کنوانسیون دی چې په کې کارول کیږي libbpf، د برخې نوم پراساس دا به په پیل کې سم ډول ځای په ځای کړي bpf(2). د BPF برنامه پخپله ده C - خورا ساده او د یوې کرښې څخه جوړ دی return XDP_PASS. په پای کې، یوه جلا برخه "license" د جواز نوم لري.

موږ کولی شو خپل پروګرام د llvm/clang، نسخه>= 10.0.0 په کارولو سره تالیف کړو، یا تر دې هم ښه، لوی (برخه وګورئ د پراختیا وسیلې):

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

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

په زړه پورې ځانګړتیاوو کې: موږ د هدف جوړښت ته اشاره کوو -target bpf او سرلیکونو ته لاره libbpf، کوم چې موږ پدې وروستیو کې نصب کړی. همدارنګه، په اړه مه هېروئ -O2، د دې اختیار پرته تاسو ممکن په راتلونکي کې حیرانتیا ولرئ. راځئ چې زموږ کوډ وګورو، ایا موږ د هغه پروګرام لیکلو اداره کړې چې موږ یې غوښتل؟

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

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

Disassembly of section xdp/simple:

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

هو، دا کار وکړ! اوس، موږ د پروګرام سره بائنری فایل لرو، او موږ غواړو یو غوښتنلیک جوړ کړو چې دا به په کرنل کې بار کړي. د دې هدف لپاره کتابتون libbpf موږ ته دوه اختیارونه وړاندې کوي - د ټیټې کچې API یا د لوړې کچې API وکاروئ. موږ به دویمې لارې ته ځو، ځکه چې موږ غواړو د BPF پروګرامونو لیکلو، بارولو او نښلولو څرنګوالی زده کړو چې د دوی د راتلونکي مطالعې لپاره لږترلږه هڅې سره.

لومړی، موږ اړتیا لرو چې د ورته کارونې په کارولو سره زموږ د برنامه "کنکال" د دې بائنری څخه تولید کړو bpftool - د BPF نړۍ سویس چاقو (کوم چې په لفظي توګه اخیستل کیدی شي، ځکه چې ډینیل بورکمن، د BPF یو جوړونکی او ساتونکی، سویس دی):

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

په دوتنه کې xdp-simple.skel.h زموږ د برنامه بائنری کوډ لري او د مدیریت لپاره دندې - بار کول ، ضمیمه کول ، زموږ څیز حذف کول. زموږ په ساده قضیه کې دا د overkill په څیر ښکاري ، مګر دا په هغه حالت کې هم کار کوي چیرې چې د اعتراض فایل ډیری BPF برنامې او نقشې لري او د دې لوی ELF پورته کولو لپاره موږ یوازې کنکال رامینځته کولو ته اړتیا لرو او د دودیز غوښتنلیک څخه یو یا دوه فنکشن ته زنګ وهو. لیکلی دی اوس پرمخ ځو.

په کلکه خبرې کول ، زموږ د لوډر برنامه کوچنۍ ده:

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

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

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

    pause();

    xdp_simple_bpf__destroy(obj);
}

دا struct xdp_simple_bpf په فایل کې تعریف شوی xdp-simple.skel.h او زموږ د اعتراض فایل تشریح کوي:

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

موږ دلته د ټیټې کچې API نښې لیدلی شو: جوړښت struct bpf_program *simple и struct bpf_link *simple. لومړی جوړښت په ځانګړې توګه زموږ برنامه تشریح کوي، په برخه کې لیکل شوي xdp/simple، او دوهم دا تشریح کوي چې څنګه برنامه د پیښې سرچینې سره وصل کیږي.

دنده xdp_simple_bpf__open_and_load، د ELF څیز خلاصوي ، تحلیل کوي ، ټول جوړښتونه او فرعي جوړښتونه رامینځته کوي (د برنامه سربیره ، ELF نورې برخې هم لري - ډیټا ، یوازې د لوستلو ډاټا ، د ډیبګ کولو معلومات ، جواز ، او نور) ، او بیا یې د سیسټم په کارولو سره کرنل کې باروي. زنګ bpf، کوم چې موږ کولی شو د برنامه په تالیف او چلولو سره چیک کړو:

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

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

راځئ چې اوس زموږ د پروګرام په کارولو سره وګورو bpftool. راځئ چې د هغې هویت پیدا کړو:

# 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_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

د BPF مرستندویه فنکشن تعریفونه د لینکس سیسټم کال تعریفونو سره ورته دي. دلته، د مثال په توګه، یو فنکشن تعریف شوی چې هیڅ دلیل نلري. (یو فنکشن چې اخلي، ووایه، درې دلیلونه د میکرو په کارولو سره تعریف شوي BPF_CALL_3. د دلیلونو اعظمي شمیره پنځه ده.) په هرصورت، دا یوازې د تعریف لومړۍ برخه ده. دویمه برخه د جوړښت ډول تعریف کول دي struct bpf_func_proto، کوم چې د مرستندویه فعالیت توضیحات لري چې تصدیق کونکی پوهیږي:

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

د مرستندویه دندو راجستر کول

د دې فنکشن کارولو لپاره د یو ځانګړي ډول BPF پروګرامونو لپاره، دوی باید دا ثبت کړي، د بیلګې په توګه د ډول لپاره BPF_PROG_TYPE_XDP یو فنکشن په کرنل کې تعریف شوی xdp_func_proto، کوم چې د مرستندویه فنکشن ID څخه ټاکي چې ایا XDP د دې فنکشن ملاتړ کوي یا نه. زموږ دنده ده ملاتړ کوي:

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

د نوي BPF پروګرام ډولونه په فایل کې "تعریف شوي" دي include/linux/bpf_types.h د میکرو کارول BPF_PROG_TYPE. په اقتباساتو کې تعریف شوی ځکه چې دا یو منطقي تعریف دی، او د C ژبې په شرایطو کې د کانکریټ جوړښتونو ټول تعریف په نورو ځایونو کې واقع کیږي. په ځانګړې توګه، په فایل کې kernel/bpf/verifier.c د فایل څخه ټول تعریفونه bpf_types.h د جوړښتونو د لړۍ د جوړولو لپاره کارول کیږي bpf_verifier_ops[]:

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

دا، د هر ډول BPF پروګرام لپاره، د ډول ډیټا جوړښت ته یو اشاره تعریف شوی struct bpf_verifier_ops، کوم چې د ارزښت سره پیل کیږي _name ## _verifier_opsیعني xdp_verifier_ops لپاره xdp... جوړښت xdp_verifier_ops ټاکل شوی په دوتنه کې net/core/filter.c په لاندې ډول:

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

دلته موږ خپل پیژندل شوی فعالیت ګورو xdp_func_proto، کوم چې به هرکله چې له ننګونې سره مخ شي تصدیق کونکی پرمخ وړي یو څه مهربان د BPF پروګرام دننه دندې، وګورئ verifier.c.

راځئ وګورو چې څنګه د فرضي BPF برنامه فعالیت کاروي bpf_get_smp_processor_id. د دې کولو لپاره، موږ د خپلې پخوانۍ برخې څخه پروګرام په لاندې ډول بیا لیکو:

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

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

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

نښه bpf_get_smp_processor_id ټاکل شوی в <bpf/bpf_helper_defs.h> کتابتونونه libbpf څنګه

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

هغه دی، bpf_get_smp_processor_id د فنکشن پوائنټر دی چې ارزښت یې 8 دی، چیرته چې 8 ارزښت دی BPF_FUNC_get_smp_processor_id ډول enum bpf_fun_id، کوم چې زموږ لپاره په فایل کې تعریف شوی vmlinux.h (دوتنه bpf_helper_defs.h په دانا کې د سکریپټ لخوا رامینځته شوی ، نو د "جادو" شمیرې سم دي). دا فنکشن هیڅ دلیل نه اخلي او د ډول ارزښت بیرته راولي __u32. کله چې موږ دا زموږ په برنامه کې چلوو، clang لارښوونه رامنځته کوي BPF_CALL "سمه ډول" راځئ چې برنامه جوړه کړو او برخه یې وګورو xdp/simple:

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

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

Disassembly of section xdp/simple:

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

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

په لومړۍ کرښه کې موږ لارښوونې ګورو call, پیرامیټر IMM کوم چې د 8 سره برابر دی، او SRC_REG - صفر. د ABI تړون له مخې چې د تصدیق کونکي لخوا کارول کیږي، دا د مرستندویه فعالیت اتم نمبر ته زنګ دی. یوځل چې دا پیل شي، منطق ساده دی. د راجستر څخه بیرته راستنیدنه r0 ته کاپي شوی r1 او په 2,3 کرښو کې دا ډول ته بدلیږي u32 - پورتنۍ 32 بټونه پاک شوي. په 4,5,6,7 کرښو کې موږ 2 بیرته راستنیږو (XDP_PASS) یا 1 (XDP_DROP) په دې پورې اړه لري چې ایا د 0 لاین څخه مرستندویه فعالیت صفر یا غیر صفر ارزښت بیرته راګرځوي.

راځئ چې خپل ځان ازموینه وکړو: برنامه پورته کړئ او محصول وګورئ bpftool prog dump xlated:

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

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

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

سمه ده، تصدیق کوونکی سم کرنل-مرستندوی وموند.

مثال: د دلیلونو تیریدل او په پای کې د برنامه چلول!

ټول د چلولو په کچه مرستندویه دندې یو پروټوټایپ لري

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

د مرستندویه کارونو پیرامیټونه په راجسترونو کې لیږدول کیږي r1-r5، او ارزښت په راجستر کې بیرته راځي r0. هیڅ داسې دندې شتون نلري چې له پنځو څخه ډیر دلیلونه واخلي، او د دوی لپاره ملاتړ تمه نه کیږي چې په راتلونکي کې اضافه شي.

راځئ چې د کرنل نوي مرسته کونکي ته یو نظر وګورو او څنګه BPF پیرامیټونه تیریږي. راځئ چې بیا لیکو xdp-simple.bpf.c په لاندې ډول (پاتې کرښې نه دي بدل شوي):

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

زموږ برنامه د CPU شمیره چاپ کوي په کوم کې چې دا چلیږي. راځئ چې دا راټول کړو او کوډ ته وګورو:

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

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

په 0-7 کرښو کې موږ تار لیکو running on CPU%un، او بیا په 8 کرښه کې موږ پیژندل شوی چلوو bpf_get_smp_processor_id. په 9-12 کرښو کې موږ مرستندویه دلیلونه چمتو کوو bpf_printk - ثبتونه r1, r2, r3. ولې په دوی کې درې شتون لري او دوه نه دي؟ ځکه bpf_printkدا یو میکرو ریپر دی د ریښتیني مرسته کونکي شاوخوا bpf_trace_printk، کوم چې د فارمیټ تار اندازې ته اړتیا لري.

راځئ چې اوس یو څو کرښې اضافه کړو xdp-simple.cترڅو زموږ برنامه له انٹرفیس سره وصل شي lo او واقعیا پیل شو!

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

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

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

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

cleanup:
    xdp_simple_bpf__destroy(obj);
}

دلته موږ فنکشن کاروو bpf_set_link_xdp_fd، کوم چې د XDP ډول BPF برنامې د شبکې انٹرفیسونو سره وصل کوي. موږ د انٹرفیس شمیره هارډ کوډ کړې lo، کوم چې تل 1 وي. موږ دوه ځله فنکشن چلوو ترڅو لومړی زوړ برنامه جلا کړو که چیرې دا ضمیمه وي. په یاد ولرئ چې اوس موږ ننګونې ته اړتیا نلرو pause یا لامحدود لوپ: زموږ د لوډر برنامه به وځي ، مګر د BPF برنامه به ونه وژل شي ځکه چې دا د پیښې سرچینې سره وصل دی. د بریالي ډاونلوډ او پیوستون وروسته ، برنامه به د هرې شبکې پاکټ لپاره پیل شي چې رارسیږي lo.

راځئ چې برنامه ډاونلوډ کړو او انٹرفیس ته وګورو lo:

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

هغه برنامه چې موږ ډاونلوډ کړې ID 669 لري او موږ ورته ID په انٹرفیس کې ګورو lo. موږ به یو څو کڅوړې ولیږو 127.0.0.1 (غوښتنه + ځواب):

$ ping -c1 localhost

او اوس راځئ چې د ډیبګ مجازی فایل مینځپانګې وګورو /sys/kernel/debug/tracing/trace_pipeپه کوم کې bpf_printk خپل پیغامونه لیکي:

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

دوه کڅوړې په نښه شوي lo او په CPU0 کې پروسس شوی - زموږ لومړی بشپړ بې معنی BPF برنامه کار وکړ!

دا د یادولو وړ ده bpf_printk دا د هیڅ شی لپاره ندي چې دا د ډیبګ فایل ته لیکي: دا په تولید کې د کارولو لپاره خورا بریالي مرسته کونکی ندی ، مګر زموږ هدف دا و چې یو څه ساده وښیو.

د BPF برنامو څخه نقشو ته لاسرسی

بېلګه: د BPF پروګرام څخه د نقشې کارول

په تیرو برخو کې موږ زده کړل چې څنګه د کارن ځای څخه نقشه جوړ او کارول، او اوس راځئ چې د کرنل برخه وګورو. راځئ چې د معمول په څیر، د مثال سره پیل وکړو. راځئ چې خپل پروګرام بیا لیکو xdp-simple.bpf.c په لاندې ډول:

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

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

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

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

    *val += 1;

    return XDP_PASS;
}

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

د پروګرام په پیل کې موږ د نقشې تعریف اضافه کړ woo: دا د 8-عنصر صف دی چې ارزښتونه لکه ذخیره کوي u64 (په C کې موږ به داسې یو صف تعریف کړو لکه u64 woo[8]). په یوه پروګرام کې "xdp/simple" موږ د اوسني پروسیسر شمیره په متغیر کې ترلاسه کوو key او بیا د مرستندویه فنکشن په کارولو سره bpf_map_lookup_element موږ په صف کې اړونده ننوتلو ته یو پوائنټر ترلاسه کوو، کوم چې موږ یو له خوا زیاتوو. په روسی کې ژباړل شوی: موږ احصایې محاسبه کوو چې CPU د راتلونکو کڅوړو پروسس کوي. راځئ هڅه وکړو چې پروګرام پرمخ بوځو:

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

راځئ وګورو چې هغه ورسره تړلې ده lo او ځینې کڅوړې ولېږئ:

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

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

اوس راځئ چې د صف محتوا وګورو:

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

نږدې ټولې پروسې په CPU7 کې پروسس شوي. دا زموږ لپاره مهم ندي، اصلي شی دا دی چې برنامه کار کوي او موږ پوهیږو چې څنګه د BPF برنامو څخه نقشو ته لاسرسی ومومئ - کارول хелперов bpf_mp_*.

صوفیانه شاخص

نو ، موږ کولی شو د تلیفونونو په کارولو سره د BPF برنامې څخه نقشې ته لاسرسی ومومئ

val = bpf_map_lookup_elem(&woo, &key);

چیرې چې د مرستندویه فعالیت داسې ښکاري

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

مګر موږ یو پوائنټر تیر کوو &woo یو نامعلوم جوړښت ته struct { ... }...

که موږ د برنامه راټولونکي ته وګورو ، موږ ګورو چې ارزښت &woo په حقیقت کې تعریف شوی نه دی (4 کرښه):

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

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

Disassembly of section xdp/simple:

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

او په لیږد کې شامل دي:

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

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

مګر که موږ دمخه بار شوي برنامه ته وګورو ، موږ سمې نقشې ته اشاره ګورو (4 کرښه):

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

پدې توګه ، موږ کولی شو دې پایلې ته ورسیږو چې زموږ د لوډر برنامه پیل کولو په وخت کې ، لینک &woo د کتابتون سره یو څه بدل شوی libbpf. لومړی به موږ محصول وګورو strace:

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

موږ دا ګورو libbpf یوه نقشه جوړه کړه woo او بیا زموږ پروګرام ډاونلوډ کړئ simple. راځئ چې نږدې وګورو چې څنګه موږ برنامه پورته کوو:

  • زنګ وهل xdp_simple_bpf__open_and_load له فایل څخه xdp-simple.skel.h
  • کوم چې لامل کیږي xdp_simple_bpf__load له فایل څخه xdp-simple.skel.h
  • کوم چې لامل کیږي bpf_object__load_skeleton له فایل څخه libbpf/src/libbpf.c
  • کوم چې لامل کیږي bpf_object__load_xattr د libbpf/src/libbpf.c

وروستی فعالیت، د نورو شیانو په منځ کې، به غږ وکړي bpf_object__create_maps، کوم چې موجوده نقشې رامینځته کوي یا خلاصوي ، دوی د فایل توضیح کونکو ته بدلوي. (دا هغه ځای دی چې موږ یې ګورو BPF_MAP_CREATE په تولید کې strace.) ورپسې فنکشن ویل کیږي bpf_object__relocate او دا هغه ده چې زموږ سره علاقه لري، ځکه چې موږ هغه څه په یاد لرو چې موږ ولیدل woo د ځای په ځای کولو میز کې. د دې سپړنه، موږ په پای کې خپل ځان په فعالیت کې پیدا کوو bpf_program__relocate، کوم چې د نقشې له لیږد سره معامله کوي:

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

نو موږ خپل لارښوونې اخلو

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

او په دې کې د سرچینې راجستر بدل کړئ BPF_PSEUDO_MAP_FD، او زموږ د نقشې د فایل توضیح کونکي ته لومړی IMM او که دا مساوي وي ، د مثال په توګه ، 0xdeadbeef، بیا په پایله کې به موږ لارښوونه ترلاسه کړو

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

دا څنګه د نقشې معلومات یو ځانګړي بار شوي BPF پروګرام ته لیږدول کیږي. په دې حالت کې، نقشه په کارولو سره رامینځته کیدی شي BPF_MAP_CREATE، او د ID په کارولو سره پرانستل شو BPF_MAP_GET_FD_BY_ID.

ټول، کله چې کارول libbpf الګوریتم په لاندې ډول دی:

  • د تالیف په جریان کې، ریکارډونه د نقشې سره د اړیکو لپاره د ځای په ځای کولو جدول کې رامینځته کیږي
  • libbpf د ELF څیز کتاب خلاصوي، ټولې کارول شوې نقشې لټوي او د دوی لپاره د فایل توضیحات رامینځته کوي
  • د فایل تشریح کونکي د لارښوونې برخې په توګه په کرنل کې بار شوي LD64

لکه څنګه چې تاسو تصور کولی شئ، نور ډیر څه شتون لري او موږ باید اصلي ته وګورو. خوشبختانه، موږ یوه نښه لرو - موږ معنی لیکلې ده BPF_PSEUDO_MAP_FD د سرچینې راجستر کې او موږ کولی شو دا ښخ کړو، کوم چې به موږ د ټولو مقدساتو سپیڅلي ته ورسوي - kernel/bpf/verifier.c، چیرې چې یو فنکشن د ځانګړي نوم سره د فایل توضیح کونکي ځای د ډول جوړښت پته سره بدلوي struct bpf_map:

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

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

(بشپړ کوډ موندل کیدی شي مخونه). نو موږ کولی شو خپل الګوریتم پراخ کړو:

  • د برنامه پورته کولو پرمهال ، تصدیق کونکی د نقشې سمه کارول ګوري او د اړوند جوړښت پته لیکي struct bpf_map

کله چې د ELF بائنری په کارولو سره ډاونلوډ کړئ libbpf دلته ډیر څه روان دي، مګر موږ به یې په نورو مقالو کې بحث وکړو.

د libbpf پرته د برنامو او نقشو بارول

لکه څنګه چې ژمنه شوې، دلته د لوستونکو لپاره یو مثال دی چې غواړي پوه شي چې څنګه یو پروګرام جوړ او پورته کړي چې نقشه کاروي، پرته له مرستې libbpf. دا ګټور کیدی شي کله چې تاسو په داسې چاپیریال کې کار کوئ چې تاسو نشئ کولی انحصار رامینځته کړئ ، یا هر څه خوندي کړئ ، یا د برنامه لیکلو په څیر ply، کوم چې په الوتنه کې د BPF بائنری کوډ رامینځته کوي.

د دې لپاره چې د منطق تعقیب اسانه کړي، موږ به د دې موخو لپاره خپل مثال بیا لیکو xdp-simple. په دې مثال کې د بحث شوي پروګرام بشپړ او لږ پراخ شوی کوډ پدې کې موندل کیدی شي خلاص.

زموږ د غوښتنلیک منطق په لاندې ډول دی:

  • یو ډول نقشه جوړه کړئ BPF_MAP_TYPE_ARRAY د کمانډ په کارولو سره BPF_MAP_CREATE,
  • یو پروګرام جوړ کړئ چې دا نقشه کاروي،
  • برنامه له انٹرفیس سره وصل کړئ lo,

کوم چې د انسان په توګه ژباړل کیږي

int main(void)
{
    int map_fd, prog_fd;

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

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

    xdp_attach(1, prog_fd);
}

دا map_create نقشه په ورته ډول رامینځته کوي لکه څنګه چې موږ د سیسټم کال په اړه په لومړي مثال کې ترسره کړې bpf - "دانه، مهرباني وکړئ ما ته د 8 عناصرو په څیر یوه نوې نقشه جوړه کړئ __u64 او ماته د دوتنې توضیحات بیرته راکړئ":

static int map_create()
{
    union bpf_attr attr;

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

برنامه د بارولو لپاره هم اسانه ده:

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

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

ستونزمنه برخه prog_load زموږ د BPF پروګرام تعریف د جوړښتونو د لړۍ په توګه دی struct bpf_insn insns[]. مګر له هغه ځایه چې موږ یو برنامه کاروو چې موږ یې په C کې لرو، موږ یو څه غلا کولی شو:

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

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

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

په مجموع کې، موږ اړتیا لرو چې د جوړښتونو په څیر 14 لارښوونې ولیکئ struct bpf_insn (مشوره: له پورته څخه ډمپ واخلئ، د لارښوونو برخه بیا ولولئ، خلاص کړئ linux/bpf.h и linux/bpf_common.h او د معلومولو هڅه وکړئ struct bpf_insn insns[] په خپله):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

د هغو کسانو لپاره تمرین چې دا پخپله نه دی لیکلی - ومومئ map_fd.

زموږ په پروګرام کې یوه بله ناڅرګنده برخه پاتې ده - xdp_attach. له بده مرغه، د XDP په څیر پروګرامونه نشي کولی د سیسټم کال په کارولو سره وصل شي bpf. هغه خلک چې BPF او XDP یې رامینځته کړي د آنلاین لینکس ټولنې څخه وو ، پدې معنی چې دوی هغه یو کارولی چې دوی ته خورا پیژندل شوي (مګر نه نورمال خلک) د کرنل سره د تعامل لپاره انٹرفیس: د netlink ساکټونه، هم وګوره RFC3549. د پلي کولو ترټولو ساده لاره xdp_attach څخه کوډ کاپي کوي libbpf، یعنی د فایل څخه netlink.c، دا هغه څه دي چې موږ یې وکړل، دا یو څه لنډوي:

د netlink ساکټونو نړۍ ته ښه راغلاست

د netlink ساکټ ډول خلاص کړئ NETLINK_ROUTE:

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

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

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

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

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

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

    *nl_pid = sa.nl_pid;
    return sock;
}

موږ د دې ساکټ څخه لوستل:

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

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

        if (len == 0)
            break;

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

په نهایت کې ، دلته زموږ فعالیت دی چې ساکټ خلاصوي او دې ته یو ځانګړی پیغام لیږي چې د فایل توضیح کونکي لري:

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

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

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

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

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

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

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

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

cleanup:
    close(sock);
    return ret;
}

نو، هرڅه د ازموینې لپاره چمتو دي:

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

راځئ وګورو چې ایا زموږ برنامه ورسره تړلې ده lo:

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

راځئ چې پینګونه واستوو او نقشه وګورو:

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

هوری، هرڅه کار کوي. په یاد ولرئ، په لاره کې، زموږ نقشه بیا د بایټ په بڼه ښودل کیږي. دا د دې حقیقت له امله دی چې برعکس libbpf موږ د ډول ډول معلومات (BTF) نه دي پورته کړي. مګر موږ به په راتلونکي کې پدې اړه نور خبرې وکړو.

د پراختیا وسیلې

پدې برخه کې، موږ به لږترلږه د BPF پراختیا کونکي وسیلې وګورو.

په عمومي ډول، تاسو د BPF پروګرامونو پراختیا لپاره کوم ځانګړي ته اړتیا نلرئ - BPF په هر ښه توزیع دانه کې پرمخ ځي، او پروګرامونه د کارولو څخه جوړ شوي clang، کوم چې د کڅوړې څخه چمتو کیدی شي. په هرصورت، د دې حقیقت له امله چې BPF د پراختیا په حال کې دی، دانه او وسیلې په دوامداره توګه بدلیږي، که تاسو نه غواړئ د BPF پروګرامونه د 2019 څخه د زاړه طریقې په کارولو سره ولیکئ، نو تاسو باید تالیف کړئ.

  • llvm/clang
  • pahole
  • اصلي
  • bpftool

(د حوالې لپاره، دا برخه او په مقاله کې ټول مثالونه په Debian 10 کې چلول شوي.)

llvm/clang

BPF د LLVM سره دوستانه دی او که څه هم په دې وروستیو کې د BPF لپاره پروګرامونه د gcc په کارولو سره ترتیب کیدی شي، ټول اوسني پرمختګ د LLVM لپاره ترسره کیږي. له همدې امله، لومړی به موږ اوسنی نسخه جوړه کړو clang له git څخه:

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

اوس موږ کولی شو وګورو چې ایا هرڅه سم سره یوځای شوي:

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

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

(د مجلس لارښوونې clang زما لخوا اخیستل شوی bpf_devel_QA.)

موږ به هغه پروګرامونه نصب نه کړو چې موږ یې جوړ کړي، مګر پرځای یې یوازې دوی ته اضافه کوو PATHد بیلګې په توګه:

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

(دا کې اضافه کیدی شي .bashrc یا جلا فایل ته. په شخصي توګه، زه د دې په څیر شیان اضافه کوم ~/bin/activate-llvm.sh او کله چې اړتیا وي زه یې کوم . activate-llvm.sh.)

Pahole او 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 سره تجربه کولو لپاره موږ کولی شو معمول وکاروو ونیلا kernel یا یو له پرمختیایي کرنلونو څخه. په تاریخي توګه، د BPF پراختیا د لینکس شبکې په ټولنه کې ترسره کیږي او له همدې امله ټول بدلونونه ژر یا وروسته د ډیویډ میلر له لارې ځي، د لینکس شبکې ساتونکي. د دوی په ماهیت پورې اړه لري - ایډیټونه یا نوي ځانګړتیاوې - د شبکې بدلونونه په دوو برخو کې راځي - net او یا net-next. د BPF لپاره بدلونونه په ورته ډول ویشل شوي دي bpf и bpf-next، کوم چې بیا په ترتیب سره په net او net-Next کې راټول شوي. د نورو جزیاتو لپاره، وګورئ bpf_devel_QA и netdev-FAQ. نو د خپل خوند او د هغه سیسټم ثبات اړتیاو پراساس یو دانه غوره کړئ چې تاسو یې ازموینه کوئ (*-next دانه د هغو لیست شویو څخه خورا بې ثباته دي).

دا د دې مقالې له دائرې څخه بهر ده چې د کرنل ترتیب کولو فایلونو اداره کولو څرنګوالي په اړه وغږیږئ - داسې انګیرل کیږي چې تاسو یا دمخه پوهیږئ چې دا څنګه ترسره کړئ ، یا د زده کړې لپاره چمتو دی په خپله په هرصورت، لاندې لارښوونې باید ډیر یا لږ وي چې تاسو ته د BPF فعال سیسټم درکوي.

د پورتنیو کرنلونو څخه یو ډاونلوډ کړئ:

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

لږ تر لږه کاري کرنل تشکیل جوړ کړئ:

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

په فایل کې د BPF اختیارونه فعال کړئ .config ستاسو د خپل انتخاب (ډیری احتمال CONFIG_BPF لا دمخه به فعال شي ځکه چې سیسټم دا کاروي). دلته د دې مقالې لپاره کارول شوي کرنل څخه د اختیارونو لیست دی:

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

بیا موږ کولی شو ماډلونه او کرنل په اسانۍ سره راټول او نصب کړو (په لاره کې، تاسو کولی شئ د نوي راټول شوي په کارولو سره دانه راټول کړئ clangپه اضافه کولو سره CC=clang):

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

او د نوي کرنل سره ریبوټ کړئ (زه د دې لپاره کاروم kexec له کڅوړې څخه kexec-tools):

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

bpftool

په مقاله کې ترټولو عام کارول شوي افادیت به وي bpftool، د لینکس کرنل د یوې برخې په توګه چمتو شوی. دا د BPF پراختیا کونکو لخوا د BPF پراختیا کونکو لخوا لیکل شوی او ساتل کیږي او د BPF هر ډول توکو اداره کولو لپاره کارول کیدی شي - برنامه بار کړئ ، نقشې رامینځته کړئ او ایډیټ کړئ ، د BPF ایکوسیستم ژوند وپلټئ ، او داسې نور. د مین پاڼو لپاره د سرچینې کوډونو په بڼه اسناد موندل کیدی شي په اصلي یا، دمخه تالیف شوی، آنلاین.

د دې لیکلو په وخت کې bpftool یوازې د RHEL، Fedora او Ubuntu لپاره چمتو شوي (وګورئ، د بیلګې په توګه، دا تار، کوم چې د بسته بندۍ نیمګړې کیسه بیانوي bpftool په دیبیان کې). مګر که تاسو دمخه خپل دانا جوړ کړی وي نو بیا جوړ کړئ bpftool د پائی په څیر اسانه:

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

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

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

$

(دلته ${linux} دا ستاسو د کرنل ډایرکټر دی.) د دې امرونو اجرا کولو وروسته bpftool په لارښود کې به راټول شي ${linux}/tools/bpf/bpftool او دا په لاره کې اضافه کیدی شي (لومړی د کارونکي ته root) یا یوازې کاپي کړئ /usr/local/sbin.

راټولول bpftool دا غوره ده چې وروستی وکاروئ clang، لکه څنګه چې پورته بیان شوي راټول شوي ، او وګورئ چې ایا دا په سمه توګه راټول شوي - د مثال په توګه کمانډ په کارولو سره

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

کوم چې به وښیې چې د BPF کوم ځانګړتیاوې ستاسو په کرنل کې فعال شوي.

د لارې په توګه، پخوانۍ کمانډ په توګه پرمخ وړل کیدی شي

# bpftool f p k

دا د بسته بندۍ څخه د اسانتیاوو سره د انډول په واسطه ترسره کیږي iproute2، چیرې چې موږ کولی شو د مثال په توګه ووایو ip a s eth0 د ځای پرځای ip addr show dev eth0.

پایلې

BPF تاسو ته اجازه درکوي د مچیو بوټان په مؤثره توګه اندازه کړي او په الوتنه کې د اصلي فعالیت بدل کړي. سیسټم خورا بریالی وګرځید، د UNIX په غوره دودونو کې: یو ساده میکانیزم چې تاسو ته اجازه درکوي د کرنل (بیا) پروګرام کولو لپاره د ډیرو خلکو او سازمانونو تجربه کولو ته اجازه ورکړي. او، که څه هم تجربې، او همدارنګه د BPF زیربنا پراختیا پخپله پای ته رسیدلې، سیسټم لا دمخه یو باثباته ABI لري چې تاسو ته اجازه درکوي د باور وړ، او تر ټولو مهم، اغیزمن سوداګریز منطق رامینځته کړي.

زه غواړم یادونه وکړم چې زما په نظر، ټیکنالوژي خورا مشهوره شوې ځکه چې له یوې خوا، دا کولی شي لوبې کول (د ماشین جوړښت په یوه ماښام کې ډیر یا لږ درک کیدی شي) او له بلې خوا ، د هغه ستونزو حل کول چې د هغې له ظهور دمخه (ښکلا) حل نشي. دا دوه برخې یوځای خلک د تجربې او خوب لیدلو ته اړوي، کوم چې د ډیرو او نوښت حلونو رامینځته کیدو لامل کیږي.

دا مقاله، که څه هم په ځانګړې توګه لنډه نه ده، یوازې د BPF نړۍ ته پیژندنه ده او "پرمختللي" ځانګړتیاوې او د معمارۍ مهمې برخې نه بیانوي. پلان چې مخ په وړاندې روان دی یو څه داسې دی: راتلونکې مقاله به د BPF برنامې ډولونو ته یوه کتنه وي (د 5.8 کرنل کې د 30 برنامې ډولونه ملاتړ شوي) ، بیا به موږ په پای کې وګورو چې څنګه د کرنل تعقیب کولو برنامو په کارولو سره د ریښتیني BPF غوښتنلیکونه ولیکئ. د مثال په توګه، نو دا د BPF جوړښت په اړه د ډیر ژور کورس لپاره وخت دی، وروسته د BPF شبکې او امنیتي غوښتنلیکونو مثالونه.

په دې لړۍ کې پخوانۍ مقالې

  1. د کوچنيانو لپاره BPF، برخه صفر: کلاسیک BPF

لینکونه

  1. د BPF او XDP حوالې لارښود - د سیلیم څخه د BPF په اړه اسناد، یا په دقیق ډول د ډینیل بورکمن څخه، د BPF جوړونکي او ساتونکي څخه. دا یو له لومړي جدي توضیحاتو څخه دی ، کوم چې د نورو څخه توپیر لري پدې کې ډینیل په ریښتیا پوهیږي چې هغه د څه په اړه لیکي او هلته هیڅ غلطی شتون نلري. په ځانګړې توګه، دا سند تشریح کوي چې څنګه د XDP او TC ډولونو BPF پروګرامونو سره د ښه پیژندل شوي افادیت په کارولو سره کار وکړي. ip له کڅوړې څخه iproute2.

  2. Documentation/networking/filter.txt - اصلي فایل د کلاسیک او بیا د BPF لپاره د اسنادو سره. یو ښه لوستل که تاسو غواړئ د مجلس ژبې او تخنیکي معماري توضیحاتو ته لاړ شئ.

  3. د فیسبوک څخه د BPF په اړه بلاګ. دا په ندرت سره تازه کیږي ، مګر په مناسب ډول ، لکه څنګه چې الیکسي سټاروویتوف (د eBPF لیکوال) او اندري نیکرییکو - (ساتونکی) هلته لیکي libbpf).

  4. د bpftool رازونه. د کوینټین مونیټ څخه په زړه پوري ټویټر تار د bpftool کارولو مثالونو او رازونو سره.

  5. په BPF کې ډوب کړئ: د لوستلو موادو لیست. د کوینټین مونیټ څخه د BPF اسنادو ته د لینکونو لوی (او لاهم ساتل شوی) لیست.

سرچینه: www.habr.com

Add a comment