چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

شروع میں ایک ٹیکنالوجی تھی اور اسے BPF کہا جاتا تھا۔ ہم نے اس کی طرف دیکھا پچھلااس سلسلے کا پرانا عہد نامہ مضمون۔ 2013 میں، Alexei Starovoitov اور Daniel Borkman کی کوششوں سے، اس کا ایک بہتر ورژن، جو جدید 64-bit مشینوں کے لیے موزوں ہے، تیار کیا گیا اور اسے Linux کرنل میں شامل کیا گیا۔ اس نئی ٹکنالوجی کو مختصراً انٹرنل بی پی ایف کہا گیا، پھر اس کا نام بدل کر توسیعی بی پی ایف رکھ دیا گیا، اور اب، کئی سالوں کے بعد، ہر کوئی اسے صرف بی پی ایف کہتا ہے۔

موٹے الفاظ میں، بی پی ایف آپ کو لینکس کرنل اسپیس میں صوابدیدی صارف کے فراہم کردہ کوڈ کو چلانے کی اجازت دیتا ہے، اور نیا فن تعمیر اتنا کامیاب ثابت ہوا کہ ہمیں اس کی تمام ایپلی کیشنز کو بیان کرنے کے لیے ایک درجن مزید مضامین کی ضرورت ہوگی۔ (صرف ایک چیز جو ڈویلپرز نے اچھی طرح سے نہیں کی، جیسا کہ آپ نیچے پرفارمنس کوڈ میں دیکھ سکتے ہیں، ایک مہذب لوگو بنانا تھا۔)

یہ مضمون بی پی ایف ورچوئل مشین کی ساخت، بی پی ایف کے ساتھ کام کرنے کے لیے کرنل انٹرفیس، ڈویلپمنٹ ٹولز کے ساتھ ساتھ موجودہ صلاحیتوں کا ایک مختصر، انتہائی مختصر جائزہ، یعنی۔ ہر وہ چیز جس کی ہمیں مستقبل میں بی پی ایف کے عملی استعمال کے گہرے مطالعہ کے لیے ضرورت ہوگی۔
چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

مضمون کا خلاصہ

بی پی ایف فن تعمیر کا تعارف۔ سب سے پہلے، ہم BPF فن تعمیر کا برڈ آئی ویو لیں گے اور اہم اجزاء کا خاکہ بنائیں گے۔

BPF ورچوئل مشین کا رجسٹر اور کمانڈ سسٹم۔ پہلے سے ہی مجموعی طور پر فن تعمیر کا خیال رکھتے ہوئے، ہم BPF ورچوئل مشین کی ساخت کو بیان کریں گے۔

بی پی ایف آبجیکٹ کا لائف سائیکل، بی پی ایف ایس فائل سسٹم۔ اس حصے میں، ہم BPF اشیاء کے لائف سائیکل - پروگراموں اور نقشوں پر گہری نظر ڈالیں گے۔

بی پی ایف سسٹم کال کا استعمال کرتے ہوئے اشیاء کا انتظام کرنا۔ پہلے سے موجود سسٹم کے بارے میں کچھ سمجھ بوجھ کے ساتھ، ہم آخر میں دیکھیں گے کہ کس طرح ایک خصوصی سسٹم کال کا استعمال کرتے ہوئے یوزر اسپیس سے اشیاء کی تخلیق اور ہیرا پھیری کی جاتی ہے۔ bpf(2).

Пишем программы BPF с помощью libbpf. یقینا، آپ سسٹم کال کا استعمال کرتے ہوئے پروگرام لکھ سکتے ہیں۔ لیکن یہ مشکل ہے۔ زیادہ حقیقت پسندانہ منظر نامے کے لیے، جوہری پروگرامرز نے ایک لائبریری تیار کی۔ libbpf. ہم ایک بنیادی BPF ایپلیکیشن سکیلٹن بنائیں گے جسے ہم بعد کی مثالوں میں استعمال کریں گے۔

دانا مددگار۔ یہاں ہم سیکھیں گے کہ کس طرح BPF پروگرام کرنل ہیلپر فنکشنز تک رسائی حاصل کر سکتے ہیں - ایک ایسا ٹول جو نقشوں کے ساتھ، کلاسک کے مقابلے میں نئے BPF کی صلاحیتوں کو بنیادی طور پر بڑھاتا ہے۔

بی پی ایف پروگراموں سے نقشوں تک رسائی۔ اس وقت تک، ہم یہ سمجھنے کے لیے کافی جان جائیں گے کہ ہم نقشے استعمال کرنے والے پروگرام کیسے بنا سکتے ہیں۔ اور آئیے عظیم اور طاقتور تصدیق کنندہ میں بھی ایک سرسری جھانکیں۔

ترقی کے اوزار۔ تجربات کے لیے مطلوبہ یوٹیلیٹیز اور دانا کو جمع کرنے کے طریقے کے بارے میں مدد کا سیکشن۔

اختتام. مضمون کے آخر میں، اس دور کو پڑھنے والوں کو حوصلہ افزا الفاظ ملیں گے اور اگلے مضامین میں کیا ہوگا اس کی مختصر تفصیل۔ ہم ان لوگوں کے لیے خود مطالعہ کے لیے کئی لنکس بھی درج کریں گے جن میں تسلسل کا انتظار کرنے کی خواہش یا صلاحیت نہیں ہے۔

بی پی ایف آرکیٹیکچر کا تعارف

اس سے پہلے کہ ہم BPF فن تعمیر پر غور شروع کریں، ہم ایک آخری بار (اوہ) کا حوالہ دیں گے۔ کلاسک بی پی ایف، جو RISC مشینوں کی آمد کے جواب کے طور پر تیار کیا گیا تھا اور موثر پیکٹ فلٹرنگ کا مسئلہ حل کیا گیا تھا۔ فن تعمیر اس قدر کامیاب نکلا کہ، برکلے UNIX میں نوے کی دہائی میں پیدا ہونے کے بعد، اسے زیادہ تر موجودہ آپریٹنگ سسٹمز پر پورٹ کر دیا گیا، بیسیوں کی دہائی میں بچ گیا اور اب بھی نئی ایپلی کیشنز تلاش کر رہا ہے۔

نیا بی پی ایف 64 بٹ مشینوں، کلاؤڈ سروسز اور SDN بنانے کے لیے ٹولز کی بڑھتی ہوئی ضرورت کے جواب کے طور پر تیار کیا گیا تھا۔Sسامان-defined nکام کرنا)۔ کرنل نیٹ ورک انجینئرز کے ذریعہ کلاسک بی پی ایف کے بہتر متبادل کے طور پر تیار کیا گیا، نئے بی پی ایف نے لفظی طور پر چھ ماہ بعد لینکس سسٹمز کو ٹریس کرنے کے مشکل کام میں ایپلی کیشنز تلاش کیں، اور اب، اس کے ظاہر ہونے کے چھ سال بعد، ہمیں اگلے مضمون کی ضرورت ہوگی۔ مختلف قسم کے پروگراموں کی فہرست بنائیں۔

مزاحیہ تصاویر

اس کے بنیادی طور پر، BPF ایک سینڈ باکس ورچوئل مشین ہے جو آپ کو حفاظتی سمجھوتہ کیے بغیر کرنل اسپیس میں "صوابدیدی" کوڈ چلانے کی اجازت دیتی ہے۔ بی پی ایف پروگرام یوزر اسپیس میں بنائے جاتے ہیں، کرنل میں لوڈ ہوتے ہیں، اور کچھ ایونٹ سورس سے منسلک ہوتے ہیں۔ ایک واقعہ ہو سکتا ہے، مثال کے طور پر، نیٹ ورک انٹرفیس پر پیکٹ کی ترسیل، کچھ کرنل فنکشن کا آغاز، وغیرہ۔ پیکیج کی صورت میں، بی پی ایف پروگرام کو پیکیج کے ڈیٹا اور میٹا ڈیٹا تک رسائی حاصل ہوگی (پڑھنے اور ممکنہ طور پر لکھنے کے لیے، پروگرام کی قسم پر منحصر ہے)؛ کرنل فنکشن چلانے کی صورت میں، دلائل فنکشن، بشمول کرنل میموری پر پوائنٹرز وغیرہ۔

آئیے اس عمل کو قریب سے دیکھیں۔ شروع کرنے کے لیے، آئیے کلاسک بی پی ایف سے پہلے فرق کے بارے میں بات کرتے ہیں، ایسے پروگرام جن کے لیے اسمبلر میں لکھا گیا تھا۔ نئے ورژن میں، فن تعمیر کو وسعت دی گئی تھی تاکہ پروگراموں کو اعلیٰ سطح کی زبانوں میں لکھا جا سکے، بنیادی طور پر، یقیناً، C میں۔ اس کے لیے، llvm کے لیے ایک بیک اینڈ تیار کیا گیا تھا، جو آپ کو BPF فن تعمیر کے لیے بائیک کوڈ بنانے کی اجازت دیتا ہے۔

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

BPF فن تعمیر کو جزوی طور پر جدید مشینوں پر موثر انداز میں چلانے کے لیے ڈیزائن کیا گیا تھا۔ اس کام کو عملی طور پر کرنے کے لیے، بی پی ایف بائیک کوڈ، ایک بار کرنل میں لوڈ ہونے کے بعد، جے آئی ٹی کمپائلر نامی جزو کا استعمال کرتے ہوئے مقامی کوڈ میں ترجمہ کیا جاتا ہے۔Jاست In Time)۔ اگلا، اگر آپ کو یاد ہو، کلاسک BPF میں پروگرام کو کرنل میں لوڈ کیا گیا تھا اور ایٹم طریقے سے ایونٹ کے ماخذ سے منسلک کیا گیا تھا - سنگل سسٹم کال کے تناظر میں۔ نئے فن تعمیر میں، یہ دو مراحل میں ہوتا ہے - پہلے، کوڈ کو سسٹم کال کا استعمال کرتے ہوئے کرنل میں لوڈ کیا جاتا ہے۔ bpf(2)اور پھر، بعد میں، دوسرے میکانزم کے ذریعے جو پروگرام کی قسم کے لحاظ سے مختلف ہوتے ہیں، پروگرام ایونٹ کے ماخذ سے منسلک ہوتا ہے۔

یہاں قاری کے ذہن میں ایک سوال ہو سکتا ہے کہ کیا یہ ممکن تھا؟ ایسے کوڈ کے نفاذ کی حفاظت کی ضمانت کیسے دی جاتی ہے؟ بی پی ایف پروگراموں کو لوڈ کرنے کے مرحلے سے عملدرآمد کی حفاظت کی ضمانت دی جاتی ہے جسے verifier کہتے ہیں (انگریزی میں اس مرحلے کو verifier کہتے ہیں اور میں انگریزی لفظ کا استعمال جاری رکھوں گا):

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

تصدیق کنندہ ایک جامد تجزیہ کار ہے جو اس بات کو یقینی بناتا ہے کہ کوئی پروگرام کرنل کے معمول کے عمل میں خلل نہ ڈالے۔ ویسے، اس کا مطلب یہ نہیں ہے کہ پروگرام سسٹم کے آپریشن میں مداخلت نہیں کر سکتا - BPF پروگرام، قسم کے لحاظ سے، کرنل میموری کے حصوں کو پڑھ اور دوبارہ لکھ سکتے ہیں، افعال کی قدریں واپس کر سکتے ہیں، ٹرم، اپنڈ، دوبارہ لکھ سکتے ہیں۔ اور یہاں تک کہ نیٹ ورک پیکٹ کو آگے بھیجیں۔ تصدیق کنندہ اس بات کی ضمانت دیتا ہے کہ BPF پروگرام چلانے سے کرنل کریش نہیں ہو گا اور یہ کہ ایک پروگرام جس میں، قواعد کے مطابق، تحریری رسائی ہے، مثال کے طور پر، باہر جانے والے پیکٹ کا ڈیٹا، پیکٹ کے باہر کرنل میموری کو اوور رائٹ نہیں کر سکے گا۔ بی پی ایف کے دیگر تمام اجزاء سے واقفیت کے بعد ہم متعلقہ سیکشن میں تصدیق کنندہ کو تھوڑی مزید تفصیل سے دیکھیں گے۔

تو ہم نے اب تک کیا سیکھا ہے؟ صارف C میں ایک پروگرام لکھتا ہے، اسے سسٹم کال کے ذریعے کرنل میں لوڈ کرتا ہے۔ bpf(2)، جہاں اسے ایک تصدیق کنندہ کے ذریعہ چیک کیا جاتا ہے اور مقامی بائی کوڈ میں ترجمہ کیا جاتا ہے۔ پھر وہی یا دوسرا صارف پروگرام کو ایونٹ کے سورس سے جوڑتا ہے اور اس پر عمل درآمد شروع ہوجاتا ہے۔ بوٹ اور کنکشن کو الگ کرنا کئی وجوہات کی بنا پر ضروری ہے۔ سب سے پہلے، تصدیق کنندہ چلانا نسبتاً مہنگا ہے اور ایک ہی پروگرام کو کئی بار ڈاؤن لوڈ کرنے سے ہم کمپیوٹر کا وقت ضائع کرتے ہیں۔ دوسری بات یہ کہ پروگرام کس طرح جڑا ہوا ہے اس کا انحصار اس کی قسم پر ہے، اور ایک سال پہلے تیار کیا گیا ایک "یونیورسل" انٹرفیس نئی قسم کے پروگراموں کے لیے موزوں نہیں ہو سکتا۔ (اگرچہ اب جب کہ فن تعمیر زیادہ پختہ ہو رہا ہے، اس انٹرفیس کو سطح پر متحد کرنے کا ایک خیال ہے libbpf.)

ہوشیار قاری یہ محسوس کر سکتا ہے کہ ہم نے ابھی تک تصویریں ختم نہیں کی ہیں۔ درحقیقت، مندرجہ بالا سبھی اس بات کی وضاحت نہیں کرتے ہیں کہ BPF بنیادی طور پر کلاسک BPF کے مقابلے میں تصویر کیوں بدلتا ہے۔ دو ایجادات جو قابل اطلاق کے دائرہ کار کو نمایاں طور پر بڑھاتی ہیں وہ ہیں مشترکہ میموری اور کرنل ہیلپر فنکشنز کو استعمال کرنے کی صلاحیت۔ BPF میں، مشترکہ میموری کو ایک مخصوص API کے ساتھ نام نہاد نقشے - مشترکہ ڈیٹا ڈھانچے کا استعمال کرتے ہوئے لاگو کیا جاتا ہے۔ انہیں شاید یہ نام اس لیے ملا کیونکہ ظاہر ہونے والا پہلا نقشہ ہیش ٹیبل تھا۔ پھر صفیں نمودار ہوئیں، مقامی (فی-سی پی یو) ہیش ٹیبلز اور لوکل اریز، سرچ ٹریز، بی پی ایف پروگراموں کی طرف اشارہ کرنے والے نقشے اور بہت کچھ۔ اب ہمارے لیے جو چیز دلچسپ ہے وہ یہ ہے کہ BPF پروگراموں میں اب یہ صلاحیت موجود ہے کہ وہ کالوں کے درمیان حالت کو برقرار رکھ سکیں اور اسے دوسرے پروگراموں اور صارف کی جگہ کے ساتھ شیئر کریں۔

سسٹم کال کا استعمال کرتے ہوئے صارف کے عمل سے نقشہ جات تک رسائی حاصل کی جاتی ہے۔ bpf(2)، اور مددگار افعال کا استعمال کرتے ہوئے دانا میں چلنے والے بی پی ایف پروگراموں سے۔ مزید برآں، مددگار نہ صرف نقشوں کے ساتھ کام کرنے کے لیے، بلکہ دیگر دانا کی صلاحیتوں تک رسائی کے لیے بھی موجود ہیں۔ مثال کے طور پر، بی پی ایف پروگرام پیکٹ کو دوسرے انٹرفیس پر فارورڈ کرنے، پرف ایونٹس بنانے، کرنل ڈھانچے تک رسائی، وغیرہ کے لیے مددگار فنکشنز کا استعمال کر سکتے ہیں۔

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

خلاصہ طور پر، BPF صوابدیدی طور پر لوڈ کرنے کی صلاحیت فراہم کرتا ہے، یعنی تصدیق کنندہ کے ٹیسٹ شدہ، صارف کوڈ کو کرنل اسپیس میں۔ یہ کوڈ کالز کے درمیان حالت کو بچا سکتا ہے اور صارف کی جگہ کے ساتھ ڈیٹا کا تبادلہ کر سکتا ہے، اور اس قسم کے پروگرام کے ذریعے اجازت شدہ کرنل سب سسٹم تک بھی رسائی حاصل کر سکتا ہے۔

یہ پہلے سے ہی کرنل ماڈیولز کی فراہم کردہ صلاحیتوں سے ملتا جلتا ہے، جس کے مقابلے میں BPF کے کچھ فوائد ہیں (یقیناً، آپ صرف اسی طرح کی ایپلی کیشنز کا موازنہ کر سکتے ہیں، مثال کے طور پر، سسٹم ٹریسنگ - آپ BPF کے ساتھ صوابدیدی ڈرائیور نہیں لکھ سکتے)۔ آپ اندراج کی ایک نچلی حد نوٹ کر سکتے ہیں (کچھ افادیتیں جو BPF استعمال کرتی ہیں صارف کو کرنل پروگرامنگ کی مہارت، یا عام طور پر پروگرامنگ کی مہارتوں کی ضرورت نہیں ہے)، رن ٹائم سیفٹی (ان لوگوں کے لیے کمنٹس میں اپنا ہاتھ اٹھائیں جنہوں نے لکھتے وقت سسٹم کو نہیں توڑا ہے۔ یا ٹیسٹنگ ماڈیولز)، اٹومیسیٹی - ماڈیولز کو دوبارہ لوڈ کرنے کے وقت بند ہوتا ہے، اور BPF سب سسٹم اس بات کو یقینی بناتا ہے کہ کوئی واقعہ یاد نہ آئے (منصفانہ طور پر، یہ تمام قسم کے BPF پروگراموں کے لیے درست نہیں ہے)۔

اس طرح کی صلاحیتوں کی موجودگی بی پی ایف کو دانا کو پھیلانے کے لیے ایک عالمی ٹول بناتی ہے، جس کی عملی طور پر تصدیق ہوتی ہے: بی پی ایف میں زیادہ سے زیادہ نئے قسم کے پروگرام شامل کیے جاتے ہیں، زیادہ سے زیادہ بڑی کمپنیاں بی پی ایف کو جنگی سرورز 24×7 پر استعمال کرتی ہیں، زیادہ سے زیادہ اسٹارٹ اپ اپنے کاروبار کو ایسے حل پر بناتے ہیں جن کی بنیاد BPF پر ہوتی ہے۔ BPF ہر جگہ استعمال ہوتا ہے: DDoS حملوں سے تحفظ میں، SDN بنانے میں (مثال کے طور پر، kubernetes کے لیے نیٹ ورکس کو لاگو کرنا)، نظام کا سراغ لگانے کے مرکزی آلے اور اعداد و شمار جمع کرنے والے کے طور پر، مداخلت کا پتہ لگانے کے نظام اور سینڈ باکس سسٹم وغیرہ میں۔

آئیے مضمون کا جائزہ حصہ یہاں ختم کرتے ہیں اور ورچوئل مشین اور BPF ماحولیاتی نظام کو مزید تفصیل سے دیکھتے ہیں۔

ڈگریشن: افادیت

مندرجہ ذیل حصوں میں مثالوں کو چلانے کے قابل ہونے کے لیے، آپ کو کم از کم متعدد افادیت کی ضرورت ہو سکتی ہے۔ llvm/clang بی پی ایف سپورٹ کے ساتھ اور bpftool. سیکشن میں ڈویلپمنٹ ٹولز آپ افادیت کو جمع کرنے کے ساتھ ساتھ اپنے دانا کو بھی پڑھ سکتے ہیں۔ یہ حصہ ذیل میں رکھا گیا ہے تاکہ ہماری پیشکش کی ہم آہنگی میں خلل نہ پڑے۔

بی پی ایف ورچوئل مشین رجسٹر اور انسٹرکشن سسٹم

بی پی ایف کا فن تعمیر اور کمانڈ سسٹم اس حقیقت کو مدنظر رکھتے ہوئے تیار کیا گیا تھا کہ پروگرام سی زبان میں لکھے جائیں گے اور کرنل میں لوڈ ہونے کے بعد مقامی کوڈ میں ترجمہ کیا جائے گا۔ لہذا، رجسٹروں کی تعداد اور کمانڈز کے سیٹ کا انتخاب ریاضی کے لحاظ سے، جدید مشینوں کی صلاحیتوں کو دیکھتے ہوئے کیا گیا۔ اس کے علاوہ، پروگراموں پر مختلف پابندیاں عائد کی گئیں، مثال کے طور پر، کچھ عرصہ پہلے تک لوپس اور سب روٹین لکھنا ممکن نہیں تھا، اور ہدایات کی تعداد 4096 تک محدود تھی (اب مراعات یافتہ پروگرام ایک ملین تک ہدایات لوڈ کر سکتے ہیں)۔

بی پی ایف کے پاس گیارہ صارف کے قابل رسائی 64 بٹ رجسٹر ہیں۔ r0-r10 اور ایک پروگرام کاؤنٹر۔ رجسٹر کریں۔ r10 ایک فریم پوائنٹر پر مشتمل ہے اور صرف پڑھنے کے لیے ہے۔ پروگراموں کو رن ٹائم پر 512 بائٹ اسٹیک تک رسائی حاصل ہوتی ہے اور نقشوں کی شکل میں مشترکہ میموری کی لامحدود مقدار ہوتی ہے۔

BPF پروگراموں کو پروگرام کی قسم کے کرنل مددگاروں کا ایک مخصوص سیٹ چلانے کی اجازت ہے اور، حال ہی میں، باقاعدہ افعال۔ ہر کہلانے والا فنکشن پانچ تک دلائل لے سکتا ہے، جو رجسٹر میں پاس کیا گیا ہے۔ r1-r5، اور واپسی کی قیمت کو منتقل کیا جاتا ہے۔ r0. اس بات کی ضمانت دی جاتی ہے کہ فنکشن سے واپسی کے بعد، رجسٹر کے مندرجات r6-r9 نہیں بدلے گا۔

پروگرام کے موثر ترجمہ کے لیے، رجسٹر r0-r11 موجودہ فن تعمیر کی ABI خصوصیات کو مدنظر رکھتے ہوئے تمام تعاون یافتہ فن تعمیر کو حقیقی رجسٹروں میں منفرد طور پر نقشہ بنایا گیا ہے۔ مثال کے طور پر، کے لیے x86_64 رجسٹر r1-r5, فنکشن پیرامیٹرز کو منتقل کرنے کے لیے استعمال کیا جاتا ہے، پر دکھایا جاتا ہے۔ rdi, rsi, rdx, rcx, r8، جو پیرامیٹرز کو فنکشنز میں منتقل کرنے کے لیے استعمال ہوتے ہیں۔ x86_64. مثال کے طور پر، بائیں طرف کا کوڈ دائیں طرف کے کوڈ کا اس طرح ترجمہ کرتا ہے:

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

رجسٹر کریں r0 پروگرام پر عمل درآمد کا نتیجہ واپس کرنے کے لیے بھی استعمال کیا جاتا ہے، اور رجسٹر میں r1 پروگرام کو سیاق و سباق کی طرف اشارہ کیا جاتا ہے - پروگرام کی قسم پر منحصر ہے، یہ مثال کے طور پر، ایک ڈھانچہ ہو سکتا ہے struct xdp_md (XDP کے لیے) یا ڈھانچہ struct __sk_buff (مختلف نیٹ ورک پروگراموں کے لیے) یا ڈھانچہ struct pt_regs (ٹریسنگ پروگراموں کی مختلف اقسام کے لیے)، وغیرہ۔

لہذا، ہمارے پاس رجسٹروں کا ایک سیٹ، کرنل مددگار، ایک اسٹیک، ایک سیاق و سباق کے پوائنٹر اور نقشوں کی شکل میں مشترکہ میموری تھی۔ ایسا نہیں ہے کہ یہ سب سفر میں بالکل ضروری ہے، لیکن...

آئیے تفصیل جاری رکھیں اور ان اشیاء کے ساتھ کام کرنے کے لیے کمانڈ سسٹم کے بارے میں بات کریں۔ تمام (تقریبا تمام) BPF ہدایات میں ایک مقررہ 64 بٹ سائز ہوتا ہے۔ اگر آپ 64 بٹ بگ اینڈین مشین پر ایک ہدایات دیکھیں گے تو آپ دیکھیں گے۔

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

یہاں Code - یہ ہدایات کی انکوڈنگ ہے، Dst/Src وصول کنندہ اور ماخذ کی انکوڈنگز ہیں، بالترتیب، Off - 16 بٹ پر دستخط شدہ انڈینٹیشن، اور Imm ایک 32 بٹ دستخط شدہ عدد ہے جو کچھ ہدایات میں استعمال ہوتا ہے (cBPF مستقل K کی طرح)۔ انکوڈنگ Code دو اقسام میں سے ایک ہے:

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

انسٹرکشن کلاسز 0، 1، 2، 3 میموری کے ساتھ کام کرنے کے لیے کمانڈز کی وضاحت کرتی ہیں۔ وہ کہا جاتا ہے, BPF_LD, BPF_LDX, BPF_ST, BPF_STXبالترتیب کلاس 4, 7 (BPF_ALU, BPF_ALU64) ALU ہدایات کا ایک سیٹ تشکیل دیں۔ کلاس 5, 6 (BPF_JMP, BPF_JMP32) چھلانگ کی ہدایات پر مشتمل ہے۔

بی پی ایف انسٹرکشن سسٹم کا مطالعہ کرنے کے لیے مستقبل کا منصوبہ درج ذیل ہے: تمام ہدایات اور ان کے پیرامیٹرز کو احتیاط سے درج کرنے کے بجائے، ہم اس سیکشن میں چند مثالیں دیکھیں گے اور ان سے یہ واضح ہو جائے گا کہ ہدایات دراصل کیسے کام کرتی ہیں اور کیسے۔ BPF کے لیے کسی بھی بائنری فائل کو دستی طور پر الگ کریں۔ مضمون میں بعد میں مواد کو مضبوط کرنے کے لیے، ہم تصدیق کنندہ، جے آئی ٹی کمپائلر، کلاسک بی پی ایف کے ترجمہ کے ساتھ ساتھ نقشوں کا مطالعہ کرتے وقت، کالنگ فنکشنز وغیرہ کے بارے میں سیکشنز میں انفرادی ہدایات سے بھی ملیں گے۔

جب ہم انفرادی ہدایات کے بارے میں بات کرتے ہیں، تو ہم بنیادی فائلوں کا حوالہ دیں گے۔ bpf.h и bpf_common.h، جو BPF ہدایات کے عددی کوڈز کی وضاحت کرتا ہے۔ اپنے طور پر فن تعمیر کا مطالعہ کرتے وقت اور/یا بائنریز کو پارس کرتے وقت، آپ کو پیچیدگی کے لحاظ سے ترتیب دیئے گئے مندرجہ ذیل ذرائع میں سیمنٹکس مل سکتے ہیں: غیر سرکاری eBPF تفصیلات, BPF اور XDP حوالہ گائیڈ، انسٹرکشن سیٹ, Documentation/networking/filter.txt اور، یقیناً، لینکس سورس کوڈ میں - تصدیق کنندہ، جے آئی ٹی، بی پی ایف ترجمان۔

مثال: اپنے سر میں بی پی ایف کو الگ کرنا

آئیے ایک مثال دیکھتے ہیں جس میں ہم ایک پروگرام مرتب کرتے ہیں۔ readelf-example.c اور نتیجے میں بائنری کو دیکھیں۔ ہم اصل مواد کو ظاہر کریں گے۔ readelf-example.c ذیل میں، بائنری کوڈز سے اس کی منطق کو بحال کرنے کے بعد:

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

آؤٹ پٹ میں پہلا کالم readelf ایک انڈینٹیشن ہے اور اس طرح ہمارا پروگرام چار کمانڈز پر مشتمل ہے:

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

کمانڈ کوڈز برابر ہیں۔ b7, 15, b7 и 95. یاد رکھیں کہ کم از کم اہم تین بٹس انسٹرکشن کلاس ہیں۔ ہمارے معاملے میں، تمام ہدایات کا چوتھا حصہ خالی ہے، لہذا ہدایات کی کلاسیں بالترتیب 7، 5، 7، 5 ہیں۔ کلاس 7 ہے BPF_ALU64، اور 5 ہے BPF_JMP. دونوں کلاسوں کے لیے، ہدایات کی شکل ایک جیسی ہے (اوپر دیکھیں) اور ہم اپنے پروگرام کو اس طرح دوبارہ لکھ سکتے ہیں (اسی وقت ہم باقی کالموں کو انسانی شکل میں دوبارہ لکھیں گے):

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

آپریشن b کلاس ALU64 ہے - BPF_MOV. یہ منزل کے رجسٹر کو ایک قدر تفویض کرتا ہے۔ اگر بٹ سیٹ ہے۔ s (ذریعہ)، پھر قدر ماخذ رجسٹر سے لی جاتی ہے، اور اگر، جیسا کہ ہمارے معاملے میں، یہ سیٹ نہیں ہے، تو قیمت فیلڈ سے لی جاتی ہے۔ Imm. چنانچہ پہلی اور تیسری ہدایات میں ہم آپریشن کرتے ہیں۔ r0 = Imm. مزید، JMP کلاس 1 آپریشن ہے۔ BPF_JEQ (اگر برابر ہو تو چھلانگ لگائیں)۔ ہمارے معاملے میں، بٹ کے بعد سے S صفر ہے، یہ فیلڈ کے ساتھ سورس رجسٹر کی قدر کا موازنہ کرتا ہے۔ Imm. اگر اقدار ایک دوسرے سے ملتی ہیں، تو منتقلی ہوتی ہے PC + Offجہاں PC, ہمیشہ کی طرح، اگلی ہدایات کے ایڈریس پر مشتمل ہے۔ آخر میں، JMP کلاس 9 آپریشن ہے BPF_EXIT. یہ ہدایت پروگرام کو ختم کرتی ہے، دانا پر واپس آتی ہے۔ r0. آئیے اپنے ٹیبل میں ایک نیا کالم شامل کریں:

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

ہم اسے زیادہ آسان شکل میں دوبارہ لکھ سکتے ہیں:

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

اگر ہمیں یاد ہے کہ رجسٹر میں کیا ہے۔ r1 پروگرام کو کرنل سے سیاق و سباق کی طرف اشارہ کیا جاتا ہے، اور رجسٹر میں r0 قدر دانا کو واپس کر دی جاتی ہے، پھر ہم دیکھ سکتے ہیں کہ اگر سیاق و سباق کی طرف اشارہ کرنے والا صفر ہے، تو ہم 1 واپس کرتے ہیں، اور دوسری صورت میں - 2۔ آئیے ماخذ کو دیکھ کر چیک کریں کہ ہم درست ہیں:

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

جی ہاں، یہ ایک بے معنی پروگرام ہے، لیکن یہ صرف چار سادہ ہدایات میں ترجمہ کرتا ہے۔

استثناء کی مثال: 16 بائٹ ہدایت

ہم نے پہلے ذکر کیا ہے کہ کچھ ہدایات 64 بٹس سے زیادہ لیتی ہیں۔ اس کا اطلاق، مثال کے طور پر، ہدایات پر ہوتا ہے۔ lddw (کوڈ = 0x18 = BPF_LD | BPF_DW | BPF_IMM) — کھیتوں سے ایک دوہرا لفظ رجسٹر میں لوڈ کریں۔ Imm... حقیقت یہ ہے کہ۔ Imm اس کا سائز 32 ہے، اور ایک دوہرا لفظ 64 بٹس ہے، لہذا ایک 64 بٹ انسٹرکشن میں 64 بٹ فوری ویلیو کو رجسٹر میں لوڈ کرنا کام نہیں کرے گا۔ ایسا کرنے کے لیے، فیلڈ میں 64 بٹ ویلیو کے دوسرے حصے کو اسٹور کرنے کے لیے دو ملحقہ ہدایات استعمال کی جاتی ہیں۔ Imm. ایک مثال:

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

بائنری پروگرام میں صرف دو ہدایات ہیں:

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

ہم ہدایات کے ساتھ دوبارہ ملیں گے۔ lddw، جب ہم نقل مکانی اور نقشوں کے ساتھ کام کرنے کے بارے میں بات کرتے ہیں۔

مثال: معیاری ٹولز کا استعمال کرتے ہوئے BPF کو الگ کرنا

لہذا، ہم نے BPF بائنری کوڈز کو پڑھنا سیکھ لیا ہے اور اگر ضروری ہو تو کسی بھی ہدایات کو پارس کرنے کے لیے تیار ہیں۔ تاہم، یہ کہنا ضروری ہے کہ عملی طور پر معیاری ٹولز کا استعمال کرتے ہوئے پروگراموں کو الگ کرنا زیادہ آسان اور تیز تر ہے، مثال کے طور پر:

$ llvm-objdump -d x64.o

Disassembly of section .text:

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

بی پی ایف آبجیکٹ کا لائف سائیکل، بی پی ایف ایس فائل سسٹم

(میں نے سب سے پہلے اس ذیلی حصے میں بیان کردہ کچھ تفصیلات سیکھیں۔ روزہ رکھنا Alexei Starovoitov میں بی پی ایف بلاگ.)

BPF اشیاء - پروگرام اور نقشے - کمانڈز کا استعمال کرتے ہوئے صارف کی جگہ سے بنائے جاتے ہیں۔ BPF_PROG_LOAD и BPF_MAP_CREATE سسٹم کال bpf(2)، ہم اگلے حصے میں اس کے بارے میں بات کریں گے کہ یہ کیسے ہوتا ہے۔ یہ کرنل ڈیٹا ڈھانچے اور ان میں سے ہر ایک کے لیے تخلیق کرتا ہے۔ refcount (حوالہ شمار) ایک پر سیٹ کیا جاتا ہے، اور اعتراض کی طرف اشارہ کرنے والا ایک فائل ڈسکرپٹر صارف کو واپس کر دیا جاتا ہے۔ ہینڈل بند ہونے کے بعد refcount آبجیکٹ ایک سے کم ہو جاتا ہے، اور جب یہ صفر تک پہنچ جاتا ہے، تو چیز تباہ ہو جاتی ہے۔

اگر پروگرام نقشے کا استعمال کرتا ہے، تو refcount پروگرام لوڈ کرنے کے بعد ان نقشوں میں ایک ایک اضافہ کیا جاتا ہے، یعنی ان کے فائل ڈسکرپٹرز کو صارف کے عمل سے بند کیا جا سکتا ہے اور پھر بھی refcount صفر نہیں ہو گا:

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

کسی پروگرام کو کامیابی کے ساتھ لوڈ کرنے کے بعد، ہم اسے عام طور پر کسی قسم کے ایونٹ جنریٹر سے منسلک کرتے ہیں۔ مثال کے طور پر، ہم آنے والے پیکٹوں کو پروسیس کرنے کے لیے اسے نیٹ ورک انٹرفیس پر رکھ سکتے ہیں یا اسے کچھ سے جوڑ سکتے ہیں۔ tracepoint کور میں اس مقام پر، ریفرنس کاؤنٹر بھی ایک سے بڑھ جائے گا اور ہم لوڈر پروگرام میں فائل ڈسکرپٹر کو بند کرنے کے قابل ہو جائیں گے۔

اگر اب ہم بوٹ لوڈر کو بند کردیں تو کیا ہوگا؟ یہ ایونٹ جنریٹر (ہک) کی قسم پر منحصر ہے۔ لوڈر کے مکمل ہونے کے بعد تمام نیٹ ورک ہکس موجود ہوں گے، یہ نام نہاد عالمی ہکس ہیں۔ اور، مثال کے طور پر، ٹریس پروگراموں کو اس عمل کے ختم ہونے کے بعد جاری کیا جائے گا جس نے انہیں تخلیق کیا ہے (اور اس وجہ سے انہیں مقامی کہا جاتا ہے، "مقامی سے عمل تک")۔ تکنیکی طور پر، مقامی ہکس میں ہمیشہ صارف کی جگہ میں ایک متعلقہ فائل ڈسکرپٹر ہوتا ہے اور اس وجہ سے جب یہ عمل بند ہوتا ہے تو بند ہوجاتا ہے، لیکن عالمی ہکس ایسا نہیں کرتے۔ مندرجہ ذیل تصویر میں، ریڈ کراس کا استعمال کرتے ہوئے، میں یہ دکھانے کی کوشش کرتا ہوں کہ لوڈر پروگرام کا خاتمہ مقامی اور عالمی ہکس کے معاملے میں اشیاء کی زندگی کو کیسے متاثر کرتا ہے۔

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

مقامی اور عالمی ہکس کے درمیان فرق کیوں ہے؟ کچھ قسم کے نیٹ ورک پروگرام چلانا یوزر اسپیس کے بغیر معنی رکھتا ہے، مثال کے طور پر، DDoS تحفظ کا تصور کریں - بوٹ لوڈر قواعد لکھتا ہے اور BPF پروگرام کو نیٹ ورک انٹرفیس سے جوڑتا ہے، جس کے بعد بوٹ لوڈر جا کر خود کو مار سکتا ہے۔ دوسری طرف، ایک ڈیبگنگ ٹریس پروگرام کا تصور کریں جو آپ نے اپنے گھٹنوں پر دس منٹ میں لکھا ہے - جب یہ ختم ہو جائے گا، آپ چاہیں گے کہ سسٹم میں کوئی کوڑا باقی نہ رہے، اور مقامی ہکس اس بات کو یقینی بنائیں گے۔

دوسری طرف، تصور کریں کہ آپ کرنل میں کسی ٹریس پوائنٹ سے جڑنا چاہتے ہیں اور کئی سالوں کے اعداد و شمار جمع کرنا چاہتے ہیں۔ اس صورت میں، آپ صارف کا حصہ مکمل کرنا چاہیں گے اور وقتاً فوقتاً اعدادوشمار پر واپس آنا چاہیں گے۔ بی پی ایف فائل سسٹم یہ موقع فراہم کرتا ہے۔ یہ ایک ان میموری سیوڈو فائل سسٹم ہے جو ایسی فائلوں کو بنانے کی اجازت دیتا ہے جو BPF اشیاء کا حوالہ دیتے ہیں اور اس طرح اس میں اضافہ ہوتا ہے۔ refcount اشیاء اس کے بعد، لوڈر باہر نکل سکتا ہے، اور اس کی تخلیق کردہ اشیاء زندہ رہیں گی۔

چھوٹے بچوں کے لیے بی پی ایف، پہلا حصہ: توسیع شدہ بی پی ایف

bpffs میں فائلیں بنانا جو BPF اشیاء کا حوالہ دیتے ہیں اسے "پننگ" کہا جاتا ہے (جیسا کہ درج ذیل فقرے میں ہے: "عمل ایک BPF پروگرام یا نقشہ کو پن کر سکتا ہے")۔ BPF اشیاء کے لیے فائل آبجیکٹ بنانا نہ صرف مقامی اشیاء کی زندگی کو بڑھانے کے لیے، بلکہ عالمی اشیاء کے استعمال کے لیے بھی معنی رکھتا ہے - عالمی DDoS تحفظ پروگرام کے ساتھ مثال پر واپس جانا، ہم چاہتے ہیں کہ آکر اعدادوشمار کو دیکھیں۔ وقت سے وقت تک.

BPF فائل سسٹم عام طور پر نصب ہوتا ہے۔ /sys/fs/bpf، لیکن اسے مقامی طور پر بھی لگایا جاسکتا ہے، مثال کے طور پر، اس طرح:

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

فائل سسٹم کے نام کمانڈ کا استعمال کرتے ہوئے بنائے جاتے ہیں۔ BPF_OBJ_PIN بی پی ایف سسٹم کال۔ مثال کے طور پر، آئیے ایک پروگرام لیں، اسے مرتب کریں، اسے اپ لوڈ کریں، اور اسے پن کریں۔ 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

اشیاء کو حذف کرنا

اشیاء کو حذف کرنے کے بارے میں بات کرتے ہوئے، یہ واضح کرنا ضروری ہے کہ پروگرام کو ہک (ایونٹ جنریٹر) سے منقطع کرنے کے بعد، ایک بھی نیا واقعہ اس کے آغاز کو متحرک نہیں کرے گا، تاہم، پروگرام کے تمام موجودہ واقعات معمول کے مطابق مکمل ہوں گے۔ .

کچھ قسم کے بی پی ایف پروگرام آپ کو پرواز پر پروگرام کو تبدیل کرنے کی اجازت دیتے ہیں، یعنی ترتیب جوہری فراہم کریں replace = detach old program, attach new program. اس صورت میں، پروگرام کے پرانے ورژن کی تمام فعال مثالیں اپنا کام ختم کر دیں گی، اور نئے پروگرام سے نئے ایونٹ ہینڈلرز بنائے جائیں گے، اور یہاں "ایٹمیسٹی" کا مطلب ہے کہ ایک بھی واقعہ یاد نہیں آئے گا۔

پروگراموں کو ایونٹ کے ذرائع سے منسلک کرنا

اس مضمون میں، ہم پروگراموں کو ایونٹ کے ذرائع سے مربوط کرنے کی الگ سے وضاحت نہیں کریں گے، کیونکہ ایک مخصوص قسم کے پروگرام کے تناظر میں اس کا مطالعہ کرنا سمجھ میں آتا ہے۔ سینٹی میٹر. مثال کے طور پر ذیل میں، جس میں ہم دکھاتے ہیں کہ XDP جیسے پروگرام کیسے جڑے ہوئے ہیں۔

بی پی ایف سسٹم کال کا استعمال کرتے ہوئے اشیاء کو جوڑنا

بی پی ایف پروگرام

تمام 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_PROG_TYPE_XDP، جو قدر واپس کرے گا۔ XDP_PASS (تمام پیکجوں کو چھوڑ دیں)۔ بی پی ایف اسمبلر میں یہ بہت آسان لگتا ہے:

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_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()
};

لیکن چونکہ بی پی ایف پروگراموں کو مقامی کوڈ میں لکھنا صرف دانا میں ٹیسٹ لکھنے اور بی پی ایف کے بارے میں مضامین کے لیے ضروری ہے، اس لیے ان میکروز کی عدم موجودگی واقعی ڈویلپر کی زندگی کو پیچیدہ نہیں کرتی ہے۔

بی پی ایف پروگرام کی وضاحت کرنے کے بعد، ہم اسے کرنل میں لوڈ کرنے کے لیے آگے بڑھتے ہیں۔ ہمارے پیرامیٹرز کا کم سے کم سیٹ 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

کوئی تعجب نہیں. اب جے آئی ٹی کمپائلر کے تیار کردہ کوڈ کو دیکھتے ہیں:

# 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 مرتب کرنے والے کی طرف سے شامل کردہ prologue اور epilogue کی ضرورت ہے۔

نقشہ جات

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 پروگراموں سے نقشے کیسے استعمال کر سکتے ہیں۔ ہم اس کے بارے میں مشین میکرو کوڈز میں مشکل سے پڑھے جانے والے پروگراموں کی زبان میں بات کر سکتے ہیں، لیکن درحقیقت یہ بتانے کا وقت آ گیا ہے کہ بی پی ایف پروگرام کس طرح لکھے اور برقرار رکھے جاتے ہیں۔ 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 کرنل اور اس کی ترقی میلنگ لسٹ کے ذریعے کی جاتی ہے۔ [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 - اب ہمیں یہ معلوم کرنے کے لیے کرنل ہیڈرز پیکج انسٹال کرنے کی ضرورت نہیں ہے کہ کرنل کے ڈھانچے کس طرح کے نظر آتے ہیں۔ مندرجہ ذیل ہیڈر فائل ہمارے پاس لائبریری سے آتی ہے۔ libbpf. اب ہمیں صرف میکرو کی وضاحت کرنے کی ضرورت ہے۔ SEC، جو کردار کو ELF آبجیکٹ فائل کے مناسب حصے میں بھیجتا ہے۔ ہمارا پروگرام سیکشن میں موجود ہے۔ xdp/simple، جہاں سلیش سے پہلے ہم پروگرام کی قسم BPF کی وضاحت کرتے ہیں - یہ وہ کنونشن ہے جس میں استعمال کیا جاتا ہے۔ libbpf، سیکشن کے نام کی بنیاد پر یہ اسٹارٹ اپ میں صحیح قسم کو بدل دے گا۔ bpf(2). بی پی ایف پروگرام خود ہے۔ 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 ہمارے پروگرام کے بائنری کوڈ پر مشتمل ہے اور ہمارے آبجیکٹ کو لوڈ کرنا، اٹیچ کرنا، ڈیلیٹ کرنا۔ ہمارے سادہ معاملے میں یہ اوور کِل کی طرح لگتا ہے، لیکن یہ اس صورت میں بھی کام کرتا ہے جہاں آبجیکٹ فائل میں بہت سے بی پی ایف پروگرامز اور نقشے ہوتے ہیں اور اس دیوہیکل ای ایل ایف کو لوڈ کرنے کے لیے ہمیں صرف کنکال بنانے کی ضرورت ہوتی ہے اور اپنی مرضی کی ایپلی کیشن سے ایک یا دو فنکشنز کو کال کرنا ہوتا ہے۔ لکھ رہے ہیں چلو اب آگے بڑھتے ہیں۔

سخت الفاظ میں، ہمارا لوڈر پروگرام معمولی ہے:

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

یعنی، ہر قسم کے بی پی ایف پروگرام کے لیے، اس قسم کے ڈیٹا ڈھانچے کی طرف اشارہ کیا جاتا ہے۔ 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_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_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 ڈھانچے کی ایک صف کے طور پر ہمارے بی پی ایف پروگرام کی تعریف ہے۔ 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 تخلیق کیا وہ آن لائن لینکس کمیونٹی سے تھے، جس کا مطلب ہے کہ انہوں نے اپنے سب سے زیادہ مانوس کو استعمال کیا (لیکن نہیں عام لوگ) دانا کے ساتھ بات چیت کے لیے انٹرفیس: نیٹ لنک ساکٹ، بھی دیکھو آر ایف سی 3549. لاگو کرنے کا آسان ترین طریقہ xdp_attach سے کوڈ کاپی کر رہا ہے۔ libbpf، یعنی فائل سے netlink.c، جو ہم نے کیا، اسے تھوڑا سا مختصر کرتے ہوئے:

نیٹ لنک ساکٹ کی دنیا میں خوش آمدید

نیٹ لنک ساکٹ کی قسم کھولیں۔ 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، جو پیکیج سے فراہم کی جاسکتی ہے۔ تاہم، اس حقیقت کی وجہ سے کہ بی پی ایف ترقی کے مراحل میں ہے، کرنل اور ٹولز مسلسل تبدیل ہو رہے ہیں، اگر آپ 2019 سے پرانے زمانے کے طریقے استعمال کرتے ہوئے بی پی ایف پروگرام نہیں لکھنا چاہتے تو آپ کو مرتب کرنا پڑے گا۔

  • llvm/clang
  • pahole
  • اس کا بنیادی
  • bpftool

(حوالہ کے لیے، یہ سیکشن اور مضمون کی تمام مثالیں Debian 10 پر چلائی گئیں۔)

llvm/clang

BPF LLVM کے ساتھ دوستانہ ہے اور، اگرچہ حال ہی میں BPF کے پروگراموں کو gcc کا استعمال کرتے ہوئے مرتب کیا جا سکتا ہے، تمام موجودہ ترقی LLVM کے لیے کی جاتی ہے۔ لہذا، سب سے پہلے، ہم موجودہ ورژن بنائیں گے clang گٹ سے:

$ 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 کی ترقی لینکس نیٹ ورکنگ کمیونٹی کے اندر ہوتی ہے اور اس لیے تمام تبدیلیاں جلد یا بدیر ڈیوڈ ملر، لینکس نیٹ ورکنگ مینٹینر کے ذریعے ہوتی ہیں۔ ان کی نوعیت پر منحصر ہے - ترمیم یا نئی خصوصیات - نیٹ ورک کی تبدیلیاں دو کوروں میں سے ایک میں آتی ہیں - net یا net-next. BPF کے لیے تبدیلیاں اسی طرح تقسیم کی جاتی ہیں۔ bpf и bpf-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

فائل میں بی پی ایف کے اختیارات کو فعال کریں۔ .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. چھوٹے بچوں کے لیے بی پی ایف، حصہ صفر: کلاسک بی پی ایف

لنکس

  1. BPF اور XDP حوالہ گائیڈ — BPF پر دستاویزات cilium سے، یا زیادہ واضح طور پر ڈینیل بورکمین سے، جو BPF کے تخلیق کاروں اور دیکھ بھال کرنے والوں میں سے ایک ہیں۔ یہ پہلی سنجیدہ وضاحتوں میں سے ایک ہے، جو دوسروں سے مختلف ہے کہ ڈینیئل بالکل جانتا ہے کہ وہ کیا لکھ رہا ہے اور اس میں کوئی غلطی نہیں ہے۔ خاص طور پر، یہ دستاویز بیان کرتی ہے کہ معروف یوٹیلیٹی کا استعمال کرتے ہوئے XDP اور TC اقسام کے BPF پروگراموں کے ساتھ کیسے کام کیا جائے۔ ip پیکج سے iproute2.

  2. Documentation/networking/filter.txt - کلاسک اور پھر توسیع شدہ BPF کے لئے دستاویزات کے ساتھ اصل فائل۔ اگر آپ اسمبلی کی زبان اور فن تعمیراتی تفصیلات کو جاننا چاہتے ہیں تو ایک اچھا مطالعہ۔

  3. فیس بک سے بی پی ایف کے بارے میں بلاگ. اسے شاذ و نادر ہی اپ ڈیٹ کیا جاتا ہے، لیکن مناسب طور پر، جیسا کہ الیکسی اسٹاروویٹوف (ای بی پی ایف کے مصنف) اور اندری نیکریکو - (مینٹینر) وہاں لکھتے ہیں۔ libbpf).

  4. bpftool کے راز. Bpftool استعمال کرنے کی مثالوں اور رازوں کے ساتھ کوئنٹن مونیٹ کا ایک تفریحی ٹویٹر تھریڈ۔

  5. BPF میں غوطہ: پڑھنے کے مواد کی فہرست. Quentin Monnet سے BPF دستاویزات کے لنکس کی ایک بڑی (اور اب بھی برقرار) فہرست۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں