ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

شروعات ۾ اتي هڪ ٽيڪنالاجي هئي ۽ ان کي BPF سڏيو ويندو هو. اسان هن ڏانهن ڏٺو پوئين، هن سيريز جي پراڻي عهد نامي آرٽيڪل. 2013 ۾، Alexei Starovoitov ۽ Daniel Borkman جي ڪوششن سان، ان جو هڪ بهتر نسخو، جديد 64-bit مشينن لاءِ بهتر ڪيو ويو، ترقي ڪئي وئي ۽ لينڪس ڪنيل ۾ شامل ڪئي وئي. هن نئين ٽيڪنالاجي کي مختصر طور تي اندروني BPF سڏيو ويو، پوء ان جو نالو وڌايو ويو BPF، ۽ هاڻي، ڪيترن سالن کان پوء، هرڪو صرف ان کي BPF سڏيندو آهي.

عام طور تي ڳالهائڻ، بي پي ايف توهان کي اجازت ڏئي ٿو ته لينڪس ڪنييل اسپيس ۾ صوابديدي صارف جي فراهم ڪيل ڪوڊ کي هلائڻ، ۽ نئين فن تعمير ايترو ڪامياب ٿي ويو ته اسان کي ان جي سڀني ايپليڪيشنن کي بيان ڪرڻ لاء هڪ درجن کان وڌيڪ مضمونن جي ضرورت پوندي. (صرف شيء جيڪا ڊولپرز سٺو نه ڪيو، جيئن توهان هيٺ ڏنل ڪارڪردگي ڪوڊ ۾ ڏسي سگهو ٿا، هڪ مهذب لوگو ٺاهي رهيو هو.)

هي آرٽيڪل بيان ڪري ٿو BPF ورچوئل مشين جي جوڙجڪ، BPF سان ڪم ڪرڻ لاءِ ڪنيل انٽرفيس، ڊولپمينٽ ٽولز، گڏوگڏ موجوده صلاحيتن جو مختصر، تمام مختصر جائزو، يعني. هر شيء جيڪا اسان کي مستقبل ۾ بي پي ايف جي عملي ايپليڪيشنن جي گهڻي مطالعي جي ضرورت پوندي.
ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

مضمون جو خلاصو

BPF فن تعمير جو تعارف. پهرين، اسان BPF فن تعمير جي پکيء جي اکين جو ڏيک وٺنداسين ۽ مکيه اجزاء کي بيان ڪنداسين.

BPF ورچوئل مشين جو رجسٽر ۽ ڪمانڊ سسٽم. اڳ ۾ ئي مڪمل طور تي فن تعمير جو خيال آهي، اسان BPF مجازي مشين جي جوڙجڪ کي بيان ڪنداسين.

BPF شين جي زندگي چڪر، bpffs فائل سسٽم. هن حصي ۾، اسان BPF شين جي زندگي جي چڪر تي هڪ ويجهي نظر ڪنداسين - پروگرام ۽ نقشا.

بي پي ايف سسٽم ڪال استعمال ڪندي شيون منظم ڪرڻ. اڳ ۾ ئي موجود سسٽم جي ڪجهه سمجھڻ سان، اسان آخر ۾ ڏسنداسين ته ڪيئن ٺاهي ۽ استعمال ڪرڻ واري شيون يوزر اسپيس مان هڪ خاص سسٽم ڪال استعمال ڪندي - bpf(2).

Пишем программы BPF с помощью libbpf. يقينا، توهان هڪ سسٽم ڪال استعمال ڪندي پروگرام لکي سگهو ٿا. پر اهو ڏکيو آهي. وڌيڪ حقيقي منظر لاء، ايٽمي پروگرامرز هڪ لائبريري ٺاهي libbpf. اسان هڪ بنيادي BPF ايپليڪيشن اسڪيليٽ ٺاهينداسين جيڪو اسان ايندڙ مثالن ۾ استعمال ڪنداسين.

ڪرنل مددگار. هتي اسين سکنداسين ته ڪيئن BPF پروگرامن تائين رسائي حاصل ڪري سگهن ٿا ڪنيل مددگار افعال - هڪ اوزار جيڪو نقشن سان گڏ، بنيادي طور تي نئين BPF جي صلاحيتن کي کلاسک جي مقابلي ۾ وڌائي ٿو.

BPF پروگرامن مان نقشن تائين رسائي. هن نقطي تائين، اسان کي اهو سمجهڻ لاء ڪافي ڄاڻ پوندي ته اسان ڪئين پروگرام ٺاهي سگهون ٿا جيڪي نقشا استعمال ڪندا آهن. ۽ اچو ته عظيم ۽ طاقتور تصديق ڪندڙ ۾ تڪڙو تڪڙو وٺو.

ترقي جا اوزار. تجربن لاءِ گهربل يوٽيلٽيز ۽ ڪرنل کي ڪيئن گڏ ڪرڻ تي مدد سيڪشن.

نتيجو. مضمون جي آخر ۾، جيڪي هن پري تائين پڙهيا آهن انهن کي حوصلا افزائي وارا لفظ ملندا ۽ هيٺ ڏنل مضمونن ۾ ڇا ٿيندو ان جو مختصر بيان. اسان انهن لاءِ خود مطالعي لاءِ ڪجهه لنڪس پڻ لسٽ ڪنداسين جن ۾ تسلسل جو انتظار ڪرڻ جي خواهش يا صلاحيت نه آهي.

BPF فن تعمير جو تعارف

ان کان اڳ جو اسان BPF فن تعمير تي غور ڪرڻ شروع ڪيو، اسان ھڪڙو آخري وقت (اوھ) ڏانھن اشارو ڪنداسين کلاسک BPF، جيڪا RISC مشينن جي اچڻ جي جواب جي طور تي ترقي ڪئي وئي ۽ موثر پيڪيٽ فلٽرنگ جو مسئلو حل ڪيو. فن تعمير ايترو ڪامياب ٿي ويو جو، برڪلي UNIX ۾ نائين جي ڏهاڪي ۾ پيدا ٿيڻ کان پوء، اهو اڪثر موجوده آپريٽنگ سسٽم ڏانهن پورٽ ڪيو ويو، پاگل ويهن سالن ۾ بچيو ويو ۽ اڃا تائين نئين ايپليڪيشنون ڳولي رهيو آهي.

نئين بي پي ايف کي 64-bit مشينن، ڪلائوڊ سروسز ۽ SDN ٺاهڻ لاءِ اوزارن جي وڌندڙ ضرورت جي جواب جي طور تي ترقي ڪئي وئي (Sسامان-dصاف ٿيل nڪم ڪرڻ). kernel نيٽ ورڪ انجنيئرن پاران ترقي يافته BPF جي بهتر متبادل جي طور تي، نئين BPF لفظي طور تي ڇهن مهينن بعد لينڪس سسٽم کي ٽريڪ ڪرڻ جي مشڪل ڪم ۾ ايپليڪيشنون مليون، ۽ هاڻي، ان جي ظاهر ٿيڻ کان ڇهه سال پوءِ، اسان کي هڪ مڪمل ايندڙ مضمون جي ضرورت پوندي. پروگرامن جي مختلف قسمن جي فهرست.

مزاحيه تصويرون

ان جي بنيادي طور تي، بي پي ايف هڪ سينڊ باڪس ورچوئل مشين آهي جيڪا توهان کي هلائڻ جي اجازت ڏئي ٿي “منتقلي” ڪوڊ کي ڪنيل اسپيس ۾ بغير حفاظتي سمجهوتو ڪرڻ جي. BPF پروگرام ٺاهيا ويا آهن صارف جي جڳهه ۾، لوڊ ٿيل ڪنييل ۾، ۽ ڳنڍيل آهن ڪجهه واقعي جي ذريعن سان. ھڪڙو واقعو ٿي سگھي ٿو، مثال طور، ھڪڙي نيٽ ورڪ انٽرفيس ڏانھن ھڪڙي پيڪٽ جي ترسيل، ڪجھ ڪنيل فنڪشن جي شروعات، وغيره. هڪ پيڪيج جي صورت ۾، BPF پروگرام کي پيڪيج جي ڊيٽا ۽ ميٽا ڊيٽا تائين رسائي هوندي (پڙهڻ ۽ ممڪن طور تي لکڻ لاءِ، پروگرام جي قسم تي منحصر آهي)؛ ڪرنل فنڪشن کي هلائڻ جي صورت ۾، جا دليل فنڪشن، بشمول ڪنيل ميموري ڏانهن اشارو، وغيره.

اچو ته هن عمل تي هڪ ويجهي نظر رکون. شروع ڪرڻ سان، اچو ته کلاسک BPF کان پهرين فرق جي باري ۾ ڳالهايون، پروگرامن لاء جن لاء جمع ڪندڙ ۾ لکيل هئا. نئين ورزن ۾، فن تعمير کي وڌايو ويو ته جيئن پروگرامن کي اعلي سطحي ٻولين ۾ لکيو وڃي، بنيادي طور تي، يقينا، C ۾. ان مقصد لاء، llvm لاء هڪ پس منظر تيار ڪيو ويو، جيڪو BPF فن تعمير لاء بائيٽ ڪوڊ پيدا ڪرڻ جي اجازت ڏئي ٿو.

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

BPF فن تعمير ٺهيل هئي، جزوي طور تي، جديد مشينن تي موثر طريقي سان هلائڻ لاء. هن ڪم کي عملي طور تي ڪرڻ لاءِ، BPF بائيٽ ڪوڊ، هڪ ڀيرو ڪرنل ۾ لوڊ ڪيو ويندو آهي، هڪ جزو استعمال ڪندي اصلي ڪوڊ ۾ ترجمو ڪيو ويندو آهي جنهن کي JIT compiler (Jيوٽ In Time). اڳيون، جيڪڏهن توهان کي ياد آهي، کلاسک BPF ۾ پروگرام ڪرنل ۾ لوڊ ڪيو ويو ۽ ايونٽ جي ماخذ سان ايٽمي طور تي ڳنڍيل آهي - هڪ واحد سسٽم ڪال جي حوالي سان. نئين فن تعمير ۾، اهو ٻن مرحلن ۾ ٿئي ٿو - پهريون، ڪوڊ سسٽم ڪال استعمال ڪندي ڪرنل ۾ لوڊ ڪيو ويندو آهي. bpf(2)۽ پوءِ، بعد ۾، ٻين ميکانيزم ذريعي جيڪي مختلف ٿين ٿا پروگرام جي قسم جي لحاظ کان، پروگرام واقعي جي ماخذ سان ڳنڍي ٿو.

هتي پڙهندڙ هڪ سوال ٿي سگهي ٿو: ڇا اهو ممڪن آهي؟ اهڙي ڪوڊ جي عملدرآمد جي حفاظت جي ضمانت ڪيئن آهي؟ بي پي ايف پروگرامن جي لوڊشيڊنگ جي اسٽيج تي عملدرآمد جي حفاظت جي ضمانت ڏني وئي آهي جنهن کي ويريفير (انگريزي ۾ هن اسٽيج کي ويريفير چيو ويندو آهي ۽ مان انگريزي لفظ استعمال ڪرڻ جاري رکندس):

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

تصديق ڪندڙ هڪ جامد تجزيه ڪندڙ آهي جيڪو يقيني بڻائي ٿو ته هڪ پروگرام ڪرنل جي عام آپريشن ۾ خلل نٿو پوي. انهي جو مطلب اهو ناهي ته پروگرام سسٽم جي آپريشن ۾ مداخلت نٿو ڪري سگهي - بي پي ايف پروگرامن، قسم تي منحصر ڪري ٿو، پڙهي ۽ ٻيهر لکي سگھي ٿو ڪنيل ميموري جا حصا، واپسي جا قدر، ٽرم، ضميمه، ٻيهر لکڻ. ۽ اڃا اڳتي نيٽ ورڪ پيڪٽس. تصديق ڪندڙ ضمانت ڏئي ٿو ته BPF پروگرام هلائڻ سان ڪرنل کي حادثو نه ٿيندو ۽ اهو پروگرام جنهن کي، ضابطن جي مطابق، لکڻ جي رسائي آهي، مثال طور، ٻاهر نڪرندڙ پيڪٽ جي ڊيٽا، پيڪٽ جي ٻاهران ڪنيل ميموري کي اوور رائٽ ڪرڻ جي قابل نه هوندو. اسان BPF جي ٻين سڀني اجزاء کان واقف ٿيڻ کان پوء، لاڳاپيل سيڪشن ۾ ٿورو وڌيڪ تفصيل سان تصديق ڪندڙ کي ڏسنداسين.

پوءِ اسان هينئر تائين ڇا سکيو آهي؟ صارف سي ۾ هڪ پروگرام لکي ٿو، ان کي سسٽم ڪال استعمال ڪندي ڪرنل ۾ لوڊ ڪري ٿو bpf(2)، جتي ان کي تصديق ڪندڙ طرفان جانچيو ويندو آهي ۽ اصلي بائيٽ ڪوڊ ۾ ترجمو ڪيو ويندو آهي. پوءِ ساڳيو يا ٻيو صارف پروگرام کي واقعي جي ماخذ سان ڳنڍي ٿو ۽ اهو عمل ڪرڻ شروع ٿئي ٿو. بوٽ ۽ ڪنيڪشن کي الڳ ڪرڻ ڪيترن ئي سببن لاء ضروري آهي. پهرين، هڪ تصديق ڪندڙ هلائڻ نسبتا مهانگو آهي ۽ ساڳئي پروگرام کي ڪيترائي ڀيرا ڊائون لوڊ ڪرڻ سان اسان ڪمپيوٽر جو وقت ضايع ڪندا آهيون. ٻيو، بلڪل ڪيئن هڪ پروگرام ڳنڍيل آهي ان جي قسم تي منحصر آهي، ۽ هڪ "آفاقي" انٽرفيس هڪ سال اڳ ترقي يافته ٿي سگهي ٿو نئين قسم جي پروگرامن لاء مناسب ناهي. (جيتوڻيڪ هاڻي ته فن تعمير وڌيڪ پختو ٿي رهيو آهي، اتي هڪ خيال آهي ته هن انٽرفيس کي سطح تي متحد ڪرڻ لاء. libbpf.)

ڌيان ڏيڻ وارو پڙهندڙ شايد نوٽيس ڪري سگھي ٿو ته اسان اڃا تائين تصويرن سان ختم نه ڪيو آهي. درحقيقت، مٿين سڀني بيانن جي وضاحت نه ڪئي وئي آهي ڇو ته BPF بنيادي طور تي تصوير کي تبديل ڪري ٿو کلاسک BPF جي مقابلي ۾. ٻه جدت جيڪي خاص طور تي قابل اطلاق جي دائري کي وڌائين ٿيون، حصيداري ياداشت ۽ ڪنيل مددگار افعال استعمال ڪرڻ جي صلاحيت آهن. BPF ۾، شيئر ڪيل ميموري لاڳو ڪئي وئي آهي نام نهاد نقشا استعمال ڪندي - هڪ مخصوص API سان حصيداري ڪيل ڊيٽا جي جوڙجڪ. انهن کي شايد اهو نالو مليو آهي ڇاڪاڻ ته پهريون قسم جو نقشو ظاهر ٿيڻ لاءِ هيش ٽيبل هو. پوءِ صفا ظاهر ٿيا، مقامي (في-سي پي يو) هيش ٽيبل ۽ مقامي صفون، وڻن جي ڳولا، نقشا جن ۾ BPF پروگرامن ڏانهن اشارو ۽ گهڻو ڪجهه. هاڻي اسان لاءِ دلچسپ ڳالهه اها آهي ته BPF پروگرامن ۾ هاڻي ڪالن جي وچ ۾ رياست کي برقرار رکڻ جي صلاحيت آهي ۽ ان کي ٻين پروگرامن سان ۽ صارف جي جڳهه سان حصيداري ڪرڻ جي صلاحيت آهي.

سسٽم ڪال استعمال ڪندي صارف جي عملن مان نقشن تائين رسائي حاصل ڪئي وئي آهي bpf(2)، ۽ بي پي ايف پروگرامن مان جيڪي ڪنيل ۾ هلن ٿا مددگار افعال استعمال ڪندي. ان کان علاوه، مددگار موجود آهن نه رڳو نقشن سان ڪم ڪرڻ، پر ٻين ڪنييل صلاحيتن تائين رسائي لاء پڻ. مثال طور، BPF پروگرام مددگار افعال استعمال ڪري سگھن ٿا پيڪيٽ کي ٻين انٽرفيس ڏانهن اڳتي وڌائڻ لاءِ، پرف واقعا پيدا ڪرڻ، ڪنييل ڍانچي تائين رسائي، وغيره.

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

تت ۾، BPF صوابديدي لوڊ ڪرڻ جي صلاحيت مهيا ڪري ٿي، يعني، تصديق ڪندڙ-ٽيسٽ ٿيل، يوزر ڪوڊ ڪرنل اسپيس ۾. هي ڪوڊ ڪالن جي وچ ۾ رياست کي بچائي سگهي ٿو ۽ صارف جي اسپيس سان ڊيٽا مٽائي سگهي ٿو، ۽ هن قسم جي پروگرام پاران اجازت ڏنل ڪنيل سب سسٽم تائين رسائي پڻ آهي.

اهو اڳ ۾ ئي ڪنيل ماڊلز پاران مهيا ڪيل صلاحيتن سان ملندڙ جلندڙ آهي، جنهن جي مقابلي ۾ BPF جا ڪجهه فائدا آهن (يقيناً، توهان صرف ساڳئي ايپليڪيشنن جو مقابلو ڪري سگهو ٿا، مثال طور، سسٽم ٽريڪنگ - توهان بي پي ايف سان هڪ صوابديدي ڊرائيور نه ٿا لکي سگهو). توهان نوٽ ڪري سگهو ٿا هڪ هيٺين داخل ٿيڻ واري حد (ڪجهه افاديت جيڪي BPF استعمال ڪن ٿيون صارف کي ڪننل پروگرامنگ مهارتن جي ضرورت ناهي، يا عام طور تي پروگرامنگ مهارتون)، رن ٽائم حفاظت (توهان جو هٿ انهن ماڻهن لاءِ تبصرن ۾ وڌايو جن لکڻ دوران سسٽم کي نه ٽوڙيو. يا ٽيسٽنگ ماڊلز)، ايٽميٽي - ماڊلز کي ٻيهر لوڊ ڪرڻ وقت وقت ختم ٿئي ٿو، ۽ BPF سبسسٽم يقيني بڻائي ٿو ته ڪو به واقعو ياد نه ڪيو وڃي (منصفانه هجڻ لاء، اهو BPF پروگرامن جي سڀني قسمن لاء صحيح ناهي).

اهڙين صلاحيتن جي موجودگي BPF کي ڪنيل کي وڌائڻ لاءِ هڪ آفاقي اوزار بڻائي ٿي، جنهن جي عملي طور تي تصديق ٿئي ٿي: BPF ۾ وڌيڪ کان وڌيڪ نوان قسم جا پروگرام شامل ڪيا ويا آهن، وڌيڪ کان وڌيڪ وڏيون ڪمپنيون BPF کي جنگي سرورز تي استعمال ڪن ٿيون 24×7، وڌيڪ ۽ وڌيڪ. startups پنهنجو ڪاروبار ٺاهي انهن حلن تي جيڪي بي پي ايف تي ٻڌل آهن. BPF هر جڳهه استعمال ڪيو ويندو آهي: DDoS حملن جي خلاف حفاظت ۾، SDN ٺاهڻ (مثال طور، ڪبرنيٽس لاء نيٽ ورڪ لاڳو ڪرڻ)، مکيه سسٽم ٽريڪنگ ٽول ۽ شماريات ڪليڪٽر جي طور تي، مداخلت ڳولڻ واري نظام ۽ سينڊ باڪس سسٽم ۾، وغيره.

اچو ته مضمون جو جائزو حصو هتي ختم ڪريون ۽ ڏسو مجازي مشين ۽ BPF ماحولياتي نظام کي وڌيڪ تفصيل سان.

Digression: افاديت

هيٺين حصن ۾ مثالن کي هلائڻ جي قابل ٿيڻ لاء، توهان کي گهٽ ۾ گهٽ ڪيترن ئي افاديت جي ضرورت پوندي. llvm/clang بي پي ايف جي مدد سان ۽ bpftool. ڀا Inي ۾ ترقي جا اوزار توھان پڙھي سگھوٿا ھدايتن کي گڏ ڪرڻ لاءِ يوٽيلٽيز، گڏو گڏ توھان جو ڪنيل. هي سيڪشن هيٺ رکيو ويو آهي ته جيئن اسان جي پيشڪش جي هم آهنگي کي خراب نه ٿئي.

BPF ورچوئل مشين رجسٽر ۽ هدايتون سسٽم

BPF جو آرڪيٽيڪچر ۽ ڪمانڊ سسٽم ٺاهيا ويا آهن انهي حقيقت کي نظر ۾ رکندي ته پروگرام سي ٻوليءَ ۾ لکيا ويندا ۽ ڪرنل ۾ لوڊ ٿيڻ کان پوءِ، اصلي ڪوڊ ۾ ترجمو ڪيو ويندو. تنهن ڪري، رجسٽرن جو تعداد ۽ حڪمن جي سيٽ کي چونڪ جي نظر سان چونڊيو ويو، رياضياتي معنى ۾، جديد مشينن جي صلاحيتن جي. ان کان علاوه، پروگرامن تي مختلف پابنديون لاڳو ڪيون ويون آهن، مثال طور، تازو جيستائين اهو ممڪن نه هو ته لوپ ۽ سبروٽين لکڻ لاء، ۽ هدايتن جو تعداد 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 بگ اينڊين مشين تي هڪ هدايتون ڏسو ته توهان ڏسندا

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

اهو آهي Code - ھيءَ ھدايت جي انڪوڊنگ آھي، Dst/Src وصول ڪندڙ ۽ ماخذ جا انڪوڊنگ آهن، ترتيب وار، Off - 16-bit دستخط ٿيل انڊينٽيشن، ۽ Imm ھڪڙو 32-bit دستخط ٿيل عدد آھي جيڪو ڪجھ ھدايتن ۾ استعمال ڪيو ويو آھي (جيئن ته cBPF مسلسل K). انڪوڊنگ Code ٻن قسمن مان ھڪڙو آھي:

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

هدايتون ڪلاس 0، 1، 2، 3 ميموري سان ڪم ڪرڻ لاء حڪم بيان ڪن ٿا. اهي سڏيو وڃي ٿو, BPF_LD, BPF_LDX, BPF_ST, BPF_STX، ترتيب سان. ڪلاس 4, 7 (BPF_ALU, BPF_ALU64) ALU هدايتن جو هڪ سيٽ ٺاهيو. ڪلاس 5، 6 (BPF_JMP, BPF_JMP32) جمپ جي هدايتن تي مشتمل آهي.

BPF ھدايت واري نظام جي مطالعي لاءِ وڌيڪ منصوبو ھيٺ ڏنل آھي: سڀني ھدايتن ۽ انھن جي ماپن کي احتياط سان لسٽ ڪرڻ بجاءِ، اسين ھن حصي ۾ ڪجھ مثالن تي نظر وجهنداسين ۽ انھن مان اھو واضح ٿي ويندو ته ھدايتون اصل ۾ ڪھڙي ريت ڪم ڪن ٿيون. BPF لاءِ ڪنهن به بائنري فائل کي دستي طور تي الڳ ڪريو. مضمون ۾ بعد ۾ مواد کي مضبوط ڪرڻ لاءِ، اسين پڻ سيڪشنز ۾ انفرادي هدايتن سان ملنداسين Verifier بابت، JIT مرتب ڪندڙ، کلاسک BPF جو ترجمو، ۽ گڏوگڏ نقشن جو مطالعو ڪرڻ، ڪالنگ افعال وغيره.

جڏهن اسان انفرادي هدايتن بابت ڳالهائينداسين، اسان بنيادي فائلن جو حوالو ڏينداسين bpf.h и bpf_common.h، جيڪو BPF هدايتن جي عددي ڪوڊ جي وضاحت ڪري ٿو. جڏهن پنهنجو پاڻ تي آرڪيٽيڪچر جو مطالعو ڪيو ۽/يا بائنري کي پارس ڪيو، توهان هيٺين ذريعن ۾ سيمينٽڪس ڳولي سگهو ٿا، پيچيدگي جي ترتيب سان ترتيب ڏنل: غير رسمي eBPF اسپيڪس, BPF ۽ XDP ريفرنس گائيڊ، هدايتون سيٽ, Documentation/networking/filter.txt ۽، يقينا، لينڪس سورس ڪوڊ ۾ - تصديق ڪندڙ، JIT، 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 هڪ indentation آهي ۽ اسان جو پروگرام اهڙيء طرح چار حڪمن تي مشتمل آهي:

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

ڪمانڊ ڪوڊ برابر آهن b7, 15, b7 и 95. ياد رهي ته گهٽ ۾ گهٽ اهم ٽي بٽ هدايت ڪلاس آهن. اسان جي حالت ۾، سڀني هدايتن جو چوٿون سا خالي آهي، تنهنڪري هدايتون ڪلاس 7، 5، 7، 5، ترتيب ڏنل آهن. ڪلاس 7 آهي BPF_ALU64، ۽ 5 آهي BPF_JMP. ٻنهي طبقن لاء، هدايت جي شڪل ساڳي آهي (مٿي ڏسو) ۽ اسان پنهنجي پروگرام کي هن طرح ٻيهر لکي سگهون ٿا (ساڳئي وقت اسين انساني شڪل ۾ باقي ڪالمن کي ٻيهر لکنداسين):

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

آپريشن b класса ALU64 - هي آهي BPF_MOV. اهو منزل جي رجسٽر کي هڪ قدر تفويض ڪري ٿو. جيڪڏهن بٽ مقرر ٿيل آهي s (ذريعو)، پوء قيمت ماخذ رجسٽر مان ورتو وڃي ٿو، ۽ جيڪڏهن، اسان جي صورت ۾، اهو مقرر نه ڪيو ويو آهي، پوء قيمت فيلڊ مان ورتو وڃي ٿو Imm. تنهنڪري پهرين ۽ ٽئين هدايتن ۾ اسان آپريشن کي انجام ڏيو ٿا r0 = Imm. وڌيڪ، JMP ڪلاس 1 آپريشن آهي BPF_JEQ (جپ جيڪڏھن برابر). اسان جي حالت ۾، سا کان وٺي S صفر آهي، اهو ماخذ رجسٽر جي قيمت کي فيلڊ سان موازنہ ڪري ٿو Imm. جيڪڏهن قدر گڏ ٿين ٿا، ته پوءِ منتقلي ٿيندي PC + Offڪٿي PC, معمول وانگر، ايندڙ هدايتن جي ايڊريس تي مشتمل آهي. آخرڪار، جي ايم پي ڪلاس 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-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                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

اسان هدايتن سان ٻيهر ملنداسين lddw، جڏهن اسان نقل و حمل ۽ نقشن سان ڪم ڪرڻ بابت ڳالهايون ٿا.

مثال: معياري اوزار استعمال ڪندي بي پي ايف کي ڌار ڪرڻ

تنهن ڪري، اسان 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 فائيل سسٽم

(مون پهريون ڀيرو هن سبجيڪٽ ۾ بيان ڪيل ڪجهه تفصيلن مان سکيو پوسٽ Alexei Starovoitov ۾ BPF بلاگ.)

BPF شيون - پروگرام ۽ نقشا - ڪمانڊ استعمال ڪندي صارف جي جڳھ مان ٺاھيا ويا آھن BPF_PROG_LOAD и BPF_MAP_CREATE سسٽم ڪال bpf(2)، اسان انهي بابت ڳالهائينداسين ته اهو ڪيئن ٿئي ٿو ايندڙ حصي ۾. هي ٺاهي ٿو ڪنيل ڊيٽا جي جوڙجڪ ۽ انهن مان هر هڪ لاء refcount (حوالن جي ڳڻپ) ھڪڙي تي مقرر ڪئي وئي آھي، ۽ ھڪڙي فائل بيان ڪندڙ اعتراض ڏانھن اشارو ڪندي صارف ڏانھن موٽايو ويندو آھي. هينڊل بند ٿيڻ کان پوء refcount اعتراض هڪ طرف گھٽجي ويندو آهي، ۽ جڏهن اهو صفر تائين پهچي ويندو آهي، اعتراض تباهه ٿي ويندو آهي.

جيڪڏهن پروگرام نقشا استعمال ڪري ٿو، پوء refcount اهي نقشا پروگرام کي لوڊ ڪرڻ کان پوءِ هڪ طرف وڌي ويا آهن، يعني. انهن جي فائل وضاحت ڪندڙ صارف جي عمل کان بند ٿي سگهي ٿو ۽ اڃا به refcount صفر نه ٿيندو:

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

ڪاميابيءَ سان هڪ پروگرام لوڊ ڪرڻ کان پوءِ، اسين عام طور تي ان کي ڪنهن قسم جي ايونٽ جنريٽر سان ڳنڍيون ٿا. مثال طور، اسان ان کي نيٽ ورڪ انٽرفيس تي رکي سگھون ٿا ايندڙ پيڪيٽ کي پروسيس ڪرڻ لاءِ يا ان کي ڪجهه سان ڳنڍڻ لاءِ tracepoint بنيادي ۾. هن نقطي تي، ريفرنس ڪائونٽر پڻ هڪ طرف وڌندو ۽ اسان لوڊر پروگرام ۾ فائل بيان ڪندڙ کي بند ڪري سگهنداسين.

ڇا ٿيندو جيڪڏهن اسان هاڻي بوٽ لوڊر کي بند ڪريون؟ اهو ايونٽ جنريٽر جي قسم تي منحصر آهي (هڪ). سڀ نيٽ ورڪ ٿلها موجود هوندا، لوڊر مڪمل ٿيڻ کان پوءِ، اهي نام نهاد عالمي ٿلها آهن. ۽، مثال طور، ٽريس پروگرامن کي جاري ڪيو ويندو پروسيس کان پوءِ جيڪو انهن کي ختم ڪيو ويو آهي (۽ انهي ڪري سڏيو ويندو آهي مقامي، "مقامي کان عمل"). تخنيقي طور تي، مقامي ٿلهو هميشه صارف جي جڳهه ۾ هڪ لاڳاپيل فائل بيان ڪندڙ آهي ۽ تنهن ڪري بند ڪيو ويندو آهي جڏهن اهو عمل بند ڪيو ويندو آهي، پر عالمي ٿلهو نه ڪندا آهن. هيٺ ڏنل شڪل ۾، ريڊ ڪراس استعمال ڪندي، مان اهو ڏيکارڻ جي ڪوشش ڪريان ٿو ته ڪيئن لوڊر پروگرام جي ختم ٿيڻ سان مقامي ۽ عالمي ٿلهو جي صورت ۾ شين جي زندگي تي اثر پوي ٿو.

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

مقامي ۽ عالمي ٿلهن جي وچ ۾ فرق ڇو آهي؟ نيٽ ورڪ پروگرامن جي ڪجهه قسمن کي هلائڻ بغير يوزر اسپيس جي سمجهه ۾ اچي ٿو، مثال طور، تصور ڪريو DDoS تحفظ - بوٽ لوڊر ضابطا لکي ٿو ۽ BPF پروگرام کي نيٽ ورڪ انٽرفيس سان ڳنڍي ٿو، جنهن کان پوءِ بوٽ لوڊر وڃي سگهي ٿو ۽ پاڻ کي ماري سگهي ٿو. ٻئي طرف، تصور ڪريو هڪ ڊيبگنگ ٽريس پروگرام جيڪو توهان پنهنجي گوڏن تي ڏهن منٽن ۾ لکيو آهي - جڏهن اهو ختم ٿي ويندو، توهان چاهيو ٿا ته سسٽم ۾ ڪو به گندو نه بچيو وڃي، ۽ مقامي ٿلهو انهي کي يقيني بڻائي سگهندا.

ٻئي طرف، تصور ڪريو ته توھان چاھيو ٿا ڳنڍڻ چاھيو ٿا ڪني ۾ ھڪڙي ٽريڪ پوائنٽ سان ۽ انگ اکر گڏ ڪريو ڪيترن سالن کان. انهي حالت ۾، توهان چاهيو ٿا ته صارف جو حصو مڪمل ڪريو ۽ انگن اکرن ڏانهن موٽڻ وقت وقت تي. بي پي ايف فائيل سسٽم اهو موقعو فراهم ڪري ٿو. اهو هڪ ان-ميموري-صرف pseudo-فائل سسٽم آهي جيڪو فائلن کي ٺاهڻ جي اجازت ڏئي ٿو جيڪو BPF شين جو حوالو ڏئي ٿو ۽ ان سان وڌائي ٿو. refcount شيون. هن کان پوء، لوڊ ڪندڙ ٻاهر نڪري سگهي ٿو، ۽ شيون جيڪي ٺاهيل آهن اهي زنده رهندا.

ننڍڙن لاءِ بي پي ايف، حصو پهريون: وڌايل بي پي ايف

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, kernel مان هڪ فائل بيان ڪندڙ وصول ڪيو 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هيٺ ڏنل پروٽوٽائپ سان گڏ:

#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 پروگرامن ڏانهن اشارو يا نيٽ ورڪ انٽرفيس ڏانهن اشارو، perf واقعن سان ڪم ڪرڻ لاءِ نقشا، وغيره. اسان هتي انهن بابت نه ڳالهائينداسين، جيئن پڙهندڙ کي پريشان نه ٿئي. ان کان سواء، اسان هم وقت سازي جي مسئلن کي نظر انداز ڪندا آهيون، ڇاڪاڻ ته اهو اسان جي مثالن لاء اهم ناهي. دستياب نقشي جي قسمن جي مڪمل فهرست ملي سگھي ٿي <linux/bpf.h>، ۽ هن حصي ۾ اسين مثال طور وٺنداسين تاريخي طور تي پهريون قسم، هيش ٽيبل BPF_MAP_TYPE_HASH.

جيڪڏهن توهان هڪ هيش ٽيبل ٺاهي، چئو، C ++، توهان چوندا unordered_map<int,long> woo، جنهن جو روسي ۾ مطلب آهي "مون کي ٽيبل جي ضرورت آهي woo لامحدود سائيز، جن جون ڪنجيون قسم جون آھن int، ۽ قدر آھن قسم long" BPF هيش ٽيبل ٺاهڻ لاءِ، اسان کي گهڻو ڪجهه ڪرڻو پوندو، سواءِ ان جي ته اسان کي ٽيبل جي وڌ ۾ وڌ سائيز جي وضاحت ڪرڻي پوندي، ۽ ڪي ۽ ويلز جي قسمن کي بيان ڪرڻ بدران، اسان کي انهن جي سائيز بائيٽ ۾ بيان ڪرڻ جي ضرورت آهي. . نقشا ٺاهڻ لاءِ ڪمانڊ استعمال ڪريو BPF_MAP_CREATE سسٽم ڪال bpf. اچو ته هڪ وڌيڪ يا گهٽ ۾ گهٽ پروگرام ڏسو جيڪو نقشو ٺاهي ٿو. پوئين پروگرام کان پوءِ جيڪو BPF پروگرامن کي لوڊ ڪري ٿو، اهو توهان کي سادو لڳي ٿو:

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

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

    for ( ;; )
        pause();
}

هتي اسان پيٽرول جو هڪ سيٽ بيان ڪريون ٿا attr، جنهن ۾ اسان چئون ٿا ”مون کي چاٻين ۽ سائيز جي قيمتن سان هڪ هيش ٽيبل جي ضرورت آهي sizeof(int)، جنهن ۾ آئون وڌ ۾ وڌ چار عنصر رکي سگهان ٿو. جڏهن BPF نقشا ٺاهي رهيا آهيو، توهان ٻين پيٽرولن جي وضاحت ڪري سگهو ٿا، مثال طور، ساڳئي طريقي سان مثال طور پروگرام سان، اسان اعتراض جو نالو بيان ڪيو آهي جيئن "woo".

اچو ته پروگرام گڏ ڪريون ۽ هلائيون:

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

هتي سسٽم ڪال آهي bpf(2) اسان کي وضاحت ڪندڙ نقشي نمبر واپس ڪيو 3 ۽ پوء پروگرام، جيئن توقع ڪئي وئي، سسٽم ڪال ۾ وڌيڪ هدايتن جو انتظار ڪري ٿو pause(2).

ھاڻي اچو ته اسان جي پروگرام کي پس منظر ڏانھن موڪليو يا ٻيو ٽرمينل کوليو ۽ يوٽيلٽي استعمال ڪندي پنھنجي اعتراض کي ڏسو bpftool (اسان پنهنجي نقشي کي ٻين کان ان جي نالي سان الڳ ڪري سگهون ٿا):

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

نمبر 114 اسان جي اعتراض جي عالمي ID آهي. سسٽم تي ڪو به پروگرام هن ID کي استعمال ڪري سگھي ٿو موجوده نقشي کي کولڻ لاءِ ڪمانڊ استعمال ڪندي BPF_MAP_GET_FD_BY_ID سسٽم ڪال bpf.

ھاڻي اسان پنھنجي ھيش ٽيبل سان راند ڪري سگھون ٿا. اچو ته ان جي مواد کي ڏسو:

$ sudo bpftool map dump id 114
Found 0 elements

خالي. اچو ته ان ۾ هڪ قدر وجهي hash[1] = 1:

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

اچو ته ٽيبل تي ٻيهر ڏسو:

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

هوري! اسان ھڪڙو عنصر شامل ڪرڻ ۾ مدد ڪئي. نوٽ ڪريو ته اسان کي بائيٽ جي سطح تي ڪم ڪرڻو پوندو ته جيئن bptftool خبر ناهي ته هيش ٽيبل ۾ ڪهڙي قسم جا قدر آهن. (هي علم هن کي BTF استعمال ڪندي منتقل ڪري سگهجي ٿو، پر هاڻي ان تي وڌيڪ.)

ڪيئن صحيح طور تي bpftool پڙهڻ ۽ عناصر شامل ڪندو؟ اچو ته هيٺان هڪ نظر وٺون:

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

پهرين اسان نقشي کي کوليو ان جي عالمي ID ذريعي ڪمانڊ استعمال ڪندي BPF_MAP_GET_FD_BY_ID и bpf(2) اسان کي وضاحت ڪندڙ 3 واپس ڪيو. وڌيڪ حڪم استعمال ڪندي BPF_MAP_GET_NEXT_KEY اسان پاس ڪندي ٽيبل ۾ پهرين چاٻي ملي NULL "اڳوڻي" ڪيچ ڏانهن اشارو طور. جيڪڏهن اسان وٽ ڪنجي آهي ته اسان ڪري سگهون ٿا BPF_MAP_LOOKUP_ELEMجيڪو پوائنٽر ڏانهن قدر واپس ڪري ٿو value. اڳيون قدم اهو آهي ته اسان ايندڙ عنصر ڳولڻ جي ڪوشش ڪريون ٿا هڪ پوائنٽر کي موجوده ڪيئي ڏانهن منتقل ڪندي، پر اسان جي ٽيبل ۾ صرف هڪ عنصر ۽ حڪم شامل آهي. BPF_MAP_GET_NEXT_KEY واپسي ENOENT.

ٺيڪ آهي، اچو ته قدر تبديل ڪريون ڪي 1 سان، اچو ته چئون ته اسان جي ڪاروباري منطق کي رجسٽر ڪرڻ جي ضرورت آهي hash[1] = 2:

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

جيئن توقع ڪئي وئي، اهو تمام سادو آهي: حڪم BPF_MAP_GET_FD_BY_ID اسان جي نقشي کي ID، ۽ حڪم سان کوليو BPF_MAP_UPDATE_ELEM عنصر کي ختم ڪري ٿو.

تنهن ڪري، هڪ پروگرام مان هيش ٽيبل ٺاهڻ کان پوء، اسان ان جي مواد کي ٻئي کان پڙهي ۽ لکي سگهون ٿا. نوٽ ڪريو ته جيڪڏهن اسان اهو ڪمانڊ لائن مان ڪري سگهون ٿا، ته پوءِ سسٽم تي ڪو ٻيو پروگرام اهو ڪري سگهي ٿو. مٿي بيان ڪيل حڪمن کان علاوه، يوزر اسپيس مان نقشن سان ڪم ڪرڻ لاءِ، هيٺ ڏنل:

  • BPF_MAP_LOOKUP_ELEM: قدر ڳولهي چاٻي ذريعي
  • BPF_MAP_UPDATE_ELEM: تازه ڪاري / قدر ٺاهيو
  • BPF_MAP_DELETE_ELEM: چاٻي هٽائڻ
  • BPF_MAP_GET_NEXT_KEY: ايندڙ (يا پھريون) چاٻي ڳولھيو
  • BPF_MAP_GET_NEXT_ID: توهان کي سڀني موجوده نقشن ذريعي وڃڻ جي اجازت ڏئي ٿي، اهو ڪيئن ڪم ڪري ٿو bpftool map
  • BPF_MAP_GET_FD_BY_ID: موجوده نقشي کي ان جي عالمي ID ذريعي کوليو
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: ايٽمي طور تي هڪ اعتراض جي قيمت کي اپڊيٽ ڪريو ۽ پراڻي هڪ واپس آڻيو
  • BPF_MAP_FREEZE: نقشي کي يوزر اسپيس مان تبديل ڪرڻ لائق بڻايو (ھي آپريشن رد نه ٿو ڪري سگھجي)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: ڪاميٽي آپريشن. مثال طور، BPF_MAP_LOOKUP_AND_DELETE_BATCH - نقشي مان سڀني قدرن کي پڙھڻ ۽ ري سيٽ ڪرڻ جو اھو واحد قابل اعتماد طريقو آھي

اهي سڀئي ڪمانڊ سڀني نقشن جي قسمن لاءِ ڪم نه ٿا ڪن، پر عام طور تي ٻين قسمن جي نقشن سان ڪم ڪرڻ سان يوزر اسپيس مان بلڪل ائين ئي نظر اچن ٿا جيئن هيش ٽيبل سان ڪم ڪرڻ.

آرڊر جي خاطر، اچو ته اسان جي هيش ٽيبل تجربن کي ختم ڪريون. ياد رکو ته اسان هڪ ٽيبل ٺاهيو جنهن ۾ چار چاٻيون شامل هجن؟ اچو ته ڪجھ وڌيڪ عناصر شامل ڪريو:

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

هينئر تائين تمام سٺو:

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

اچو ته هڪ وڌيڪ شامل ڪرڻ جي ڪوشش ڪريو:

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

جيئن توقع ڪئي وئي، اسان ڪامياب نه ٿيا. اچو ته وڌيڪ تفصيل سان غلطي کي ڏسو:

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

هر شي ٺيڪ آهي: جيئن توقع ڪئي وئي، ٽيم BPF_MAP_UPDATE_ELEM هڪ نئون، پنجون، اهم ٺاهڻ جي ڪوشش ڪري ٿو، پر حادثو E2BIG.

تنهن ڪري، اسان BPF پروگرام ٺاهي ۽ لوڊ ڪري سگهون ٿا، انهي سان گڏ يوزر اسپيس مان نقشا ٺاهي ۽ منظم ڪري سگهون ٿا. هاڻي اهو ڏسڻ لاء منطقي آهي ته اسان BPF پروگرامن مان نقشا ڪيئن استعمال ڪري سگهون ٿا. اسان ان بابت ڳالهائي سگهون ٿا مشين ميڪرو ڪوڊس ۾ سخت پڙهڻ وارا پروگرام جي ٻولي ۾، پر حقيقت ۾ اهو وقت اچي ويو آهي ته اهو ڏيکاريو ته BPF پروگرام اصل ۾ ڪيئن لکيل ۽ برقرار رکيا ويندا آهن - استعمال ڪندي. libbpf.

(پڙهندڙن لاءِ جيڪي گهٽ سطحي مثال جي کوٽ کان مطمئن نه آهن: اسان تفصيلي پروگرامن جو تجزيو ڪنداسين جيڪي نقشا ۽ مددگار افعال استعمال ڪندا آهن libbpf ۽ توهان کي ٻڌايو ته هدايت جي سطح تي ڇا ٿئي. پڙهندڙن لاء جيڪي غير مطمئن آهن تمام گهڻو، اسان شامل ڪيو مثال مضمون ۾ مناسب جڳهه تي.)

Libbpf استعمال ڪندي BPF پروگرام لکڻ

مشين ڪوڊ استعمال ڪندي BPF پروگرامن کي لکڻ صرف پهريون ڀيرو دلچسپ ٿي سگهي ٿو، ۽ پوء اطمينان سيٽ ۾. هن وقت توهان کي ڌيان ڏيڻ جي ضرورت آهي llvm، جنهن ۾ BPF آرڪيٽيڪچر لاءِ ڪوڊ ٺاهڻ لاءِ هڪ پس منظر آهي، انهي سان گڏ هڪ لائبريري libbpf، جيڪو توهان کي BPF ايپليڪيشنن جي صارف طرف لکڻ جي اجازت ڏئي ٿو ۽ استعمال ڪندي ٺاهيل BPF پروگرامن جو ڪوڊ لوڊ ڪري ٿو. llvm/clang.

حقيقت ۾، جيئن اسين هن ۽ ايندڙ مضمونن ۾ ڏسندا سين، libbpf ان کان سواءِ ڪافي ڪم ڪري ٿو (يا ساڳيا اوزار - iproute2, libbcc, libbpf-goوغيره) اهو رهڻ ناممڪن آهي. منصوبي جي قاتل خاصيتن مان هڪ آهي libbpf آهي BPF CO-RE (Compile One, Run Everywhere) - هڪ پروجيڪٽ جيڪو توهان کي 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 kernel ۽ ان جي ترقي ميلنگ لسٽ ذريعي ڪئي وئي آهي [email protected]. بهرحال، هڪ الڳ مخزن کي برقرار رکيو ويندو آهي ايپليڪيشنن جي ضرورتن لاءِ جيڪي ڪرنل کان ٻاهر رهن ٿا https://github.com/libbpf/libbpf جنهن ۾ ڪنيل لائبريري کي وڌيڪ يا گهٽ پڙهڻ جي رسائي لاءِ آئيني طور تي ظاهر ڪيو ويو آهي.

هن حصي ۾ اسين ڏسنداسين ته توهان هڪ پروجيڪٽ ڪيئن ٺاهي سگهو ٿا جيڪو استعمال ڪري ٿو libbpf، اچو ته لکون ڪيترائي (وڌيڪ يا گهٽ بي معنيٰ) ٽيسٽ پروگرام ۽ تفصيل سان تجزيو ڪريون ته اهو سڀ ڪيئن ڪم ڪري ٿو. اهو اسان کي وڌيڪ آسانيءَ سان وضاحت ڪرڻ جي اجازت ڏيندو ته هيٺين حصن ۾ بلڪل ڪيئن BPF پروگرامن نقشن، ڪنيل مددگار، BTF وغيره سان لهه وچڙ ۾ اچن ٿا.

عام طور تي منصوبا استعمال ڪندي libbpf شامل ڪريو 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 - ھاڻي اسان کي kernel-headers پيڪيج کي انسٽال ڪرڻ جي ضرورت نه آھي اھو معلوم ڪرڻ لاءِ ته ڪھڙين جي جوڙجڪ جھڙا نظر اچن ٿا. هيٺ ڏنل هيڊر فائل اسان وٽ لائبريري مان اچي ٿي libbpf. هاڻي اسان کي صرف ميڪرو جي وضاحت ڪرڻ جي ضرورت آهي SEC، جيڪو ڪردار کي ELF اعتراض فائل جي مناسب حصي ڏانهن موڪلي ٿو. اسان جو پروگرام سيڪشن ۾ موجود آهي xdp/simple، جتي سليش کان اڳ اسان پروگرام جي قسم جي وضاحت ڪريون ٿا BPF - هي اهو ڪنوينشن آهي جنهن ۾ استعمال ڪيو ويو آهي libbpf، سيڪشن جي نالي جي بنياد تي اهو شروع ڪرڻ تي صحيح قسم کي متبادل ڪندو bpf(2). BPF پروگرام پاڻ آهي C - بلڪل سادو ۽ ھڪڙي لائن تي مشتمل آھي return XDP_PASS. آخرڪار، هڪ الڳ سيڪشن "license" لائسنس جي نالي تي مشتمل آهي.

اسان پنهنجي پروگرام کي llvm/clang، ورجن >= 10.0.0 استعمال ڪندي گڏ ڪري سگھون ٿا، يا اڃا بهتر، وڏو (ڏسو سيڪشن ترقي جا اوزار):

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

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

دلچسپ خاصيتن مان: اسان ھدف فن تعمير جي نشاندهي ڪريون ٿا -target bpf ۽ سرن ڏانهن رستو libbpfجنهن کي اسان تازو انسٽال ڪيو آهي. پڻ، جي باري ۾ نه وساريو -O2، هن اختيار کان سواءِ توهان مستقبل ۾ حيران ٿي سگهو ٿا. اچو ته اسان جي ڪوڊ کي ڏسو، ڇا اسان پروگرام لکڻ جو انتظام ڪيو جيڪو اسان چاهيون ٿا؟

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

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

Disassembly of section xdp/simple:

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

ها، اهو ڪم ڪيو! هاڻي، اسان وٽ پروگرام سان گڏ هڪ بائنري فائل آهي، ۽ اسان هڪ ايپليڪيشن ٺاهڻ چاهيون ٿا جيڪا ان کي ڪرنل ۾ لوڊ ڪندي. هن مقصد لاء لائبريري libbpf اسان کي ٻه اختيار پيش ڪري ٿو - هڪ هيٺين-سطح API يا اعلي-سطح API استعمال ڪريو. اسان ٻئي طريقي سان هلنداسين، ڇو ته اسان سکڻ چاهيون ٿا ته ڪيئن لکڻ، لوڊ ڪرڻ ۽ ڳنڍجي BPF پروگرامن کي گهٽ ۾ گهٽ ڪوشش سان انهن جي ايندڙ مطالعي لاءِ.

پهرين، اسان کي پنهنجي پروگرام جي "سڪيلٽن" پيدا ڪرڻ جي ضرورت آهي ان جي بائنري مان ساڳئي يوٽيلٽي استعمال ڪندي. bpftool - BPF دنيا جو سوئس چاقو (جنهن کي لفظي طور وٺي سگهجي ٿو، ڇاڪاڻ ته ڊينيئل بورڪمن، BPF جي تخليق ۽ سنڀاليندڙن مان هڪ، سوئس آهي):

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

فائل ۾ xdp-simple.skel.h اسان جي پروگرام جي بائنري ڪوڊ تي مشتمل آهي ۽ انتظام ڪرڻ لاءِ افعال - لوڊ ڪرڻ، ڳنڍڻ، اسان جي اعتراض کي ختم ڪرڻ. اسان جي سادي صورت ۾ اهو نظر اچي ٿو اوور ڪل، پر اهو ان صورت ۾ پڻ ڪم ڪري ٿو جتي اعتراض فائل ۾ ڪيترائي BPF پروگرام ۽ نقشا شامل آهن ۽ هن وڏي ELF کي لوڊ ڪرڻ لاءِ اسان کي صرف اسڪيٽ ٺاهڻ جي ضرورت آهي ۽ ڪسٽم ايپليڪيشن مان هڪ يا ٻه فنڪشن کي ڪال ڪرڻ جي ضرورت آهي. لکي رهيا آهيون اچو ته اڳتي هلون.

سختي سان ڳالهائڻ، اسان جو لوڊ ڪندڙ پروگرام ننڍڙو آهي:

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

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

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

    pause();

    xdp_simple_bpf__destroy(obj);
}

اهو آهي struct xdp_simple_bpf فائل ۾ وضاحت ڪئي وئي آهي xdp-simple.skel.h ۽ اسان جي اعتراض فائل کي بيان ڪري ٿو:

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

اسان هتي گهٽ-سطح API جا نشان ڏسي سگهون ٿا: ساخت struct bpf_program *simple и struct bpf_link *simple. پهرين جوڙجڪ خاص طور تي اسان جي پروگرام کي بيان ڪري ٿو، سيڪشن ۾ لکيل آهي xdp/simple، ۽ ٻيو بيان ڪري ٿو ته پروگرام ڪيئن ڳنڍي ٿو واقعي جي ماخذ سان.

فعل xdp_simple_bpf__open_and_load, هڪ ELF اعتراض کوليندو آهي، ان کي پارس ڪري ٿو، سڀني جوڙجڪ ۽ ذيلي جوڙجڪ ٺاهي ٿو (پروگرام کان علاوه، ELF ۾ ٻيا حصا پڻ شامل آهن - ڊيٽا، صرف پڙهڻ واري ڊيٽا، ڊيبگنگ معلومات، لائسنس، وغيره)، ۽ پوء ان کي سسٽم استعمال ڪندي ڪرنل ۾ لوڊ ڪري ٿو. سڏ bpf، جيڪو اسان پروگرام کي گڏ ڪرڻ ۽ هلائڻ سان چيڪ ڪري سگھون ٿا:

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

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

اچو ته هاڻي اسان جي پروگرام کي استعمال ڪندي ڏسو bpftool. اچو ته هن جي سڃاڻپ ڳوليون:

# 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

ڪجهه نئون! پروگرام اسان جي سي سورس فائل جا حصا ڇپايا، اهو لائبريري طرفان ڪيو ويو 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 هڪ فنڪشن kernel ۾ بيان ڪيو ويو آهي 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;
    ...
    }
}

نئين بي پي ايف پروگرام جا قسم فائل ۾ "وضاحت ٿيل" آهن 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
};

اهو آهي، هر قسم جي بي پي ايف پروگرام لاء، قسم جي ڊيٽا جي جوڙجڪ ڏانهن اشارو بيان ڪيو ويو آهي 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

ٺيڪ آهي، تصديق ڪندڙ کي صحيح kernel-helper مليو.

مثال: دليلن کي پاس ڪرڻ ۽ آخرڪار پروگرام کي هلائڻ!

سڀ رن-سطح مددگار افعال هڪ پروٽوٽائپ آهي

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;
}

اسان جو پروگرام سي پي يو جو نمبر پرنٽ ڪري ٿو جنهن تي اهو هلي رهيو آهي. اچو ته ان کي گڏ ڪريون ۽ ڪوڊ ڏسو:

$ 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 تي عمل ڪيو ويو - اسان جو پهريون مڪمل بي معني بي پي ايف پروگرام ڪم ڪيو!

اها ingالهه نوٽ ڪرڻ جي قابل آهي bpf_printk اهو ڪجهه به ناهي ته اهو ڊيبگ فائل ڏانهن لکي ٿو: اهو پيداوار ۾ استعمال لاءِ سڀ کان ڪامياب مددگار ناهي، پر اسان جو مقصد ڪجهه سادو ڏيکارڻ هو.

BPF پروگرامن مان نقشن تائين رسائي

مثال: BPF پروگرام مان نقشو استعمال ڪندي

پوئين سيڪشن ۾ اسان سکيو ته يوزر اسپيس مان نقشا ڪيئن ٺاهيا ۽ استعمال ڪيا وڃن، ۽ هاڻي اچو ته ڪرنل واري حصي کي ڏسو. اچو ته شروع ڪريون، معمول وانگر، هڪ مثال سان. اچو ته اسان جي پروگرام کي ٻيهر لکون xdp-simple.bpf.c جيئن ته:

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

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

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

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

    *val += 1;

    return XDP_PASS;
}

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

پروگرام جي شروعات ۾ اسان نقشي جي تعريف شامل ڪئي woo: هي هڪ 8-عنصر صف آهي جيڪو ذخيرو ڪري ٿو قدرن وانگر u64 (سي ۾ اسان اهڙي صف جي وضاحت ڪنداسين جيئن u64 woo[8]). هڪ پروگرام ۾ "xdp/simple" اسان موجوده پروسيسر نمبر کي متغير ۾ حاصل ڪريون ٿا key ۽ پوء مددگار فنڪشن استعمال ڪندي bpf_map_lookup_element اسان کي صف ۾ لاڳاپيل داخلا ڏانهن اشارو ملي ٿو، جنهن کي اسان هڪ طرف وڌايو. روسي ۾ ترجمو ٿيل: اسان شمار ڪريون ٿا انگ اکر جن تي سي پي يو پروسيسنگ ايندڙ پيڪٽس. اچو ته پروگرام کي هلائڻ جي ڪوشش ڪريو:

$ 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 اسان جي بي پي ايف پروگرام جي تعريف آهي ساخت جي هڪ صف جي طور تي struct bpf_insn insns[]. پر جيئن ته اسان هڪ پروگرام استعمال ڪري رهيا آهيون جيڪو اسان وٽ سي ۾ آهي، اسان ٿورڙو ٺڳي سگهون ٿا:

$ 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 ٺاهيا اهي آن لائين لينڪس ڪميونٽي مان هئا، جنهن جو مطلب آهي ته انهن انهن کي استعمال ڪيو جيڪو انهن کان وڌيڪ واقف آهي (پر نه. عام ماڻهو) ڪنيل سان لهه وچڙ لاءِ انٽرفيس: نيٽ لنڪ ساکٽ، پڻ ڏسو آر ايف سي 3549. عمل ڪرڻ جو آسان طريقو xdp_attach کان ڪوڊ ڪاپي ڪري رهيو آهي libbpf، يعني، فائل مان netlink.c، جيڪو اسان ڪيو آهي، ان کي ٿورڙو مختصر ڪندي:

netlink sockets جي دنيا ۾ ڀليڪار

نيٽ لنڪ ساکٽ جو قسم کوليو NETLINK_ROUTE:

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

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

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

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

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

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

    *nl_pid = sa.nl_pid;
    return sock;
}

اسان هن ساکٽ مان پڙهون ٿا:

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

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

        if (len == 0)
            break;

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

آخرڪار، هتي اسان جو فنڪشن آهي جيڪو هڪ ساکٽ کوليندو آهي ۽ ان ڏانهن هڪ خاص پيغام موڪلي ٿو جنهن ۾ هڪ فائل بيان ڪندڙ آهي:

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

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

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

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

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

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

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

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

cleanup:
    close(sock);
    return ret;
}

تنهن ڪري، هر شي جاچ لاء تيار آهي:

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

اچو ته ڏسون ته اسان جي پروگرام سان ڳنڍيل آهي lo:

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

اچو ته پنگ موڪليون ۽ نقشي کي ڏسو:

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

هيري، سڀ ڪم ڪري ٿو. نوٽ، رستي جي ذريعي، اسان جو نقشو ٻيهر بائيٽ جي صورت ۾ ڏيکاريل آهي. هن حقيقت جي ڪري آهي ته، ان جي ابتڙ libbpf اسان قسم جي معلومات (BTF) لوڊ نه ڪيو. پر اسان ان بابت وڌيڪ ڳالهائينداسين ايندڙ وقت.

ترقي جا اوزار

هن حصي ۾، اسين ڏسنداسين گهٽ ۾ گهٽ BPF ڊولپر ٽول ڪٽ.

عام طور تي ڳالهائڻ، توهان کي BPF پروگرامن کي ترقي ڪرڻ لاء ڪجهه خاص ضرورت نه آهي - BPF ڪنهن به مهذب تقسيم ڪنيل تي هلندو آهي، ۽ پروگرامن کي استعمال ڪندي ٺاهيل آهن. clang، جيڪو پيڪيج مان فراهم ڪري سگهجي ٿو. جڏهن ته، حقيقت اها آهي ته BPF ترقي هيٺ آهي، ڪنيل ۽ اوزار مسلسل تبديل ٿي رهيا آهن، جيڪڏهن توهان 2019 کان پراڻي طريقي سان استعمال ڪندي BPF پروگرامن کي لکڻ نٿا چاهيو، ته پوء توهان کي مرتب ڪرڻو پوندو.

  • llvm/clang
  • pahole
  • ان جو بنيادي
  • bpftool

(ريفرنس لاءِ، هي سيڪشن ۽ مضمون ۾ سڀ مثال ڊيبين 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 ٽيڪنالاجي جي تفصيل بابت تفصيل نه ڪنداسين، حقيقت اها آهي ته اهو آسان آهي ۽ اسان ان کي استعمال ڪرڻ چاهيون ٿا. تنهن ڪري جيڪڏهن توهان پنهنجو ڪنيل تعمير ڪرڻ وارا آهيو، پهرين تعمير ڪريو 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 يا ڊولپمينٽ ڪنلز مان هڪ. تاريخي طور تي، بي پي ايف جي ترقي لينڪس نيٽ ورڪنگ ڪميونٽي جي اندر ٿيندي آهي ۽ تنهن ڪري سڀ تبديليون جلدي يا بعد ۾ ڊيوڊ ملر، لينڪس نيٽ ورڪنگ سنڀاليندڙ ذريعي وڃو. انهن جي نوعيت تي منحصر آهي - تبديليون يا نيون خاصيتون - نيٽ ورڪ تبديليون ٻن حصن مان هڪ ۾ ٿينديون آهن net يا net-next. BPF جي تبديلين جي وچ ۾ ساڳئي طريقي سان ورهايل آهن bpf и bpf-next، جيڪي پوءِ ترتيب ڏنل net ۽ net-next ۾ گڏ ڪيا ويا آهن. وڌيڪ تفصيل لاءِ، ڏسو bpf_devel_QA и netdev-FAQ. تنھنڪري پنھنجي ذائقي ۽ سسٽم جي استحڪام جي ضرورتن جي بنياد تي ھڪڙو دانا چونڊيو جنھن تي توھان جاچ ڪري رھيا آھيو (*-next kernels انهن فهرستن مان سڀ کان وڌيڪ غير مستحڪم آهن).

اهو هن آرٽيڪل جي دائري کان ٻاهر آهي انهي بابت ڳالهائڻ لاءِ ته ڪنيل ڪنفيگريشن فائلن کي ڪيئن منظم ڪجي - اهو فرض ڪيو وڃي ٿو ته توهان يا ته اڳ ۾ ئي ڄاڻو ٿا ته اهو ڪيئن ڪجي، يا سکڻ لاءِ تيار پاڻ تي. جڏهن ته، هيٺ ڏنل هدايتون توهان کي ڪم ڪندڙ BPF-فعال سسٽم ڏيڻ لاءِ ڪافي يا گهٽ هجڻ گهرجن.

مٿي ڏنل ڪنلن مان هڪ ڊائون لوڊ ڪريو:

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

گھٽ ۾ گھٽ ڪم ڪندڙ ڪنييل ترتيب ٺاھيو:

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

فائل ۾ BPF اختيارن کي فعال ڪريو .config توهان جي پنهنجي پسند جي (گهڻو ڪري CONFIG_BPF اڳ ۾ ئي چالو ڪيو ويندو ڇاڪاڻ ته systemd ان کي استعمال ڪري ٿو). ھتي آھي اختيارن جي ھڪڙي فهرست ھن مضمون لاءِ استعمال ٿيل ڪنيل مان:

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

پوءِ اسان آساني سان گڏ ڪري سگھون ٿا ۽ انسٽال ڪري سگھون ٿا ماڊيولز ۽ ڪنيل کي (انهي سان، توهان نئين گڏ ٿيل ڪتن کي استعمال ڪندي ڪنيل کي گڏ ڪري سگهو ٿا. clangشامل ڪرڻ سان CC=clang):

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

۽ نئين ڪرنل سان ريبوٽ ڪريو (آئون ان لاءِ استعمال ڪريان ٿو kexec پيڪيج مان kexec-tools):

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

bpftool

مضمون ۾ سڀ کان وڌيڪ عام استعمال جي افاديت هوندي bpftoolلينڪس ڪنيل جي حصي طور فراهم ڪيو ويو. اهو BPF ڊولپرز پاران BPF ڊولپرز لاءِ لکيو ۽ سنڀاليو ويو آهي ۽ سڀني قسمن جي BPF شين کي منظم ڪرڻ لاءِ استعمال ٿي سگهي ٿو - پروگرام لوڊ ڪريو، نقشا ٺاهي ۽ ايڊٽ ڪريو، BPF ايڪو سسٽم جي زندگي جي ڳولا ڪريو، وغيره. انسان جي صفحن لاء ماخذ ڪوڊ جي صورت ۾ دستاويز ڳولهي سگهجن ٿا بنيادي ۾ يا، اڳ ۾ ئي مرتب ٿيل، خالص تي.

هن لکڻ جي وقت تي bpftool صرف RHEL، Fedora ۽ Ubuntu لاءِ تيار ٿيل آهي (ڏسو، مثال طور، هن سلسلي، جيڪو پيڪنگنگ جي نامڪمل ڪهاڻي ٻڌائي ٿو bpftool ديبين ۾). پر جيڪڏهن توهان اڳ ۾ ئي پنهنجو ڪنيل ٺاهيو آهي، پوء تعمير ڪريو bpftool پائي وانگر آسان:

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

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

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

$

(هتي ${linux} - ھي آھي توھان جي ڪرنل ڊاريڪٽري.) انھن حڪمن تي عمل ڪرڻ کان پوءِ bpftool ڊاريڪٽري ۾ گڏ ڪيو ويندو ${linux}/tools/bpf/bpftool ۽ ان کي رستي ۾ شامل ڪري سگھجي ٿو (سڀ کان پهريان استعمال ڪندڙ کي root) يا صرف نقل ڪريو /usr/local/sbin.

گڏ ڪرڻ bpftool اهو آخري استعمال ڪرڻ لاء بهترين آهي clangگڏ ڪيو ويو جيئن مٿي بيان ڪيو ويو آهي، ۽ چيڪ ڪريو ته اهو صحيح طور تي گڏ ڪيو ويو آهي - استعمال ڪندي، مثال طور، حڪم

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

جيڪو ڏيکاريندو ته BPF خاصيتون توهان جي ڪرنل ۾ فعال آهن.

رستي جي ذريعي، پوئين حڪم هلائي سگهجي ٿو جيئن

# bpftool f p k

اهو قياس سان ڪيو ويندو آهي پيڪيج مان افاديت سان iproute2، جتي اسان ڪري سگهون ٿا، مثال طور، چئو ip a s eth0 بدران بدران ip addr show dev eth0.

ٿڪل

BPF توهان کي هڪ پسو جوتا ڪرڻ جي اجازت ڏئي ٿو مؤثر انداز سان ماپ ڪرڻ لاءِ ۽ اڏامندي ئي بنيادي ڪارڪردگي کي تبديل ڪري ٿو. UNIX جي بهترين روايتن ۾ اهو نظام تمام گهڻو ڪامياب ثابت ٿيو: هڪ سادي ميکانيزم جيڪو توهان کي (ٻيهر) پروگرام ڪرڻ جي اجازت ڏئي ٿو ڪرنل ڪيترن ئي ماڻهن ۽ تنظيمن کي تجربو ڪرڻ جي اجازت ڏئي ٿو. ۽، جيتوڻيڪ تجربا، گڏوگڏ BPF انفراسٹرڪچر جي ترقي خود، ختم ٿيڻ کان پري آهن، سسٽم اڳ ۾ ئي هڪ مستحڪم ABI آهي جيڪو توهان کي قابل اعتماد، ۽ سڀ کان اهم، موثر ڪاروباري منطق ٺاهڻ جي اجازت ڏئي ٿو.

مان اهو نوٽ ڪرڻ چاهيان ٿو ته، منهنجي خيال ۾، ٽيڪنالاجي تمام مقبول ٿي چڪي آهي، ڇاڪاڻ ته، هڪ پاسي، اهو ڪري سگهي ٿو کيڏ (هڪ مشين جي فن تعمير کي هڪ شام ۾ گهٽ يا گهٽ سمجهي سگهجي ٿو)، ۽ ٻئي طرف، انهن مسئلن کي حل ڪرڻ لاء جيڪي ان جي ظاهر ٿيڻ کان اڳ (خوبصورت) حل نه ٿي سگهيا آهن. اهي ٻئي جزا گڏجي ماڻهن کي تجربا ۽ خواب ڏسڻ تي مجبور ڪن ٿا، جنهن جي نتيجي ۾ وڌيڪ ۽ وڌيڪ جديد حل پيدا ٿين ٿا.

هي آرٽيڪل، جيتوڻيڪ خاص طور تي مختصر ناهي، صرف BPF جي دنيا جو هڪ تعارف آهي ۽ "ترقي يافته" خاصيتن ۽ فن تعمير جي اهم حصن کي بيان نٿو ڪري. اڳتي وڌڻ جو منصوبو ڪجهه هن طرح آهي: ايندڙ آرٽيڪل BPF پروگرام جي قسمن جو هڪ جائزو هوندو (5.8 پروگرامن جا قسم آهن 30 ڪرنل ۾ سپورٽ ٿيل)، پوءِ اسان آخر ۾ ڏسنداسين ته ڪيئن لکڻيون حقيقي BPF ايپليڪيشنون استعمال ڪندي ڪنيل ٽريڪنگ پروگرامن کي. مثال طور، پوءِ اھو وقت آھي BPF آرڪيٽيڪچر تي وڌيڪ گہرے ڪورس لاءِ، بعد ۾ BPF نيٽ ورڪنگ ۽ سيڪيورٽي ايپليڪيشنن جا مثال.

اڳيون مضمون هن سلسلي ۾

  1. BPF ننڍڙن لاء، حصو صفر: کلاسک BPF

لنڪس

  1. BPF ۽ XDP ريفرنس گائيڊ - Cilium کان BPF تي دستاويز، يا وڌيڪ واضح طور تي ڊينيئل بورڪمان کان، BPF جي تخليق ۽ سنڀاليندڙن مان هڪ. اها پهرين سنجيده وضاحتن مان هڪ آهي، جيڪا ٻين کان مختلف آهي ته ڊينيئل ڄاڻي ٿو ته هو ڇا لکي رهيو آهي ۽ اتي ڪا غلطي ناهي. خاص طور تي، هي دستاويز بيان ڪري ٿو ته ڪيئن ڪم ڪجي BPF پروگرامن جي XDP ۽ TC قسمن جي معروف افاديت استعمال ڪندي ip پيڪيج مان iproute2.

  2. Documentation/networking/filter.txt - اصل فائل دستاويزن سان گڏ کلاسک ۽ پوءِ وڌايل BPF. سٺو پڙهو جيڪڏهن توهان چاهيو ٿا ته اسيمبليءَ جي ٻولي ۽ فني تعميراتي تفصيلن ۾.

  3. فيسبوڪ کان BPF بابت بلاگ. اهو گهٽ ۾ گهٽ اپڊيٽ ڪيو ويندو آهي، پر مناسب طور تي، جيئن Alexei Starovoitov (اي بي پي ايف جو ليکڪ) ۽ اندري نيڪريڪو - (رڳو رکڻ وارو) اتي لکي ٿو. libbpf).

  4. bpftool جا راز. Quentin Monnet کان هڪ دلچسپ ٽوئيٽر ٿريڊ مثالن ۽ رازن سان گڏ bpftool استعمال ڪرڻ جا راز.

  5. ڊيو ان بي پي ايف: پڙهڻ واري مواد جي هڪ فهرست. Quentin Monnet کان BPF دستاويزن جي لنڪ جي هڪ وڏي (۽ اڃا تائين برقرار رکيل) فهرست.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو