seyed hossein Tafakh
seyed hossein Tafakh
خواندن ۷ دقیقه·۱ ماه پیش

شروع برنامه نویسی در ebpf با یک مثال ساده



با سلام به همه دوستان ، این پست شروعی بسیار ساده بر ebpf میباشد ، و به طور کلی چند موضوع رو پوشِش میده :
یک - معرفی ebpf و bpf
دو - معرفی منابع آموزشی مناسب
سه - یک نرم افزار نوشته شده در ebpf برای نشان دادن ادرس بسته های ورودی از شبکه به کرنل لینوکس



اگر درباره واحد پردازنده مرکزی ( cpu ) تحقیق کرده باشید متوجه میشید که واحد پردازنده در واقع از تعداد بسیار زیادی از ترانزیستور ها ( کانال های برقی که توسط جریان برق کنترل میشوند ) ساخته شده و این ترانزیستور های زیاد محاسبات رو انجام میدن و اطلاعات رو نگه داری میکنند، برای نمونه عکس زیر یک ماشین حساب چهار بیتی رو نشون میده :



بیشتر بدانید

در نگاه اول متوجه میشید که تعداد ترانزیستور ها چقدر سریع زیاد میشه ، طراح های واحد پردازنده برای حل این موضوع از زبانی استفاده میکنند به نام verilog که در واقع یک زبان توصیف سخت افزار هست و منطق کامپیوتر با verilog نوشته میشه ، مثل این که این معماری چند دستور ( instruction ) باید داشته باشه یا اینکه چند بیت داشته باشه ، چقدر حافظه register داشته باشه و ... البته این زبان دوباره در حال معروف شدن هست و دلیل این موضوع استفاده از این زبان برای برنامه نویسی پلاسمید های DNA هست و اضافه کردن اون به درون باکتری و کلی کارهای باحال دیگه که در این مَقال نمیگنجد .


هر کامپیوتر دارنده تعدادی instruction هست ، این instruction ها نوعی دستور هستند که به کامپیوتر داده میشوند تا دستوری را اجرا بکنند ، برای مثال instruction جمع ، یا انتقال داده و ...

معرفی BPF

برنامه bpf یک ماشین مجازی است که در سطح کرنل فعالیت میکند و این موضوع این امکان را میدهد که بدون نگرانی در سطح kernel کد نویسی کرد و اگر کدی که نوشتیم به هر دلیل خراب شد کرنل را خراب نکند .

کرنل لینوکس مسئول ارتباط برنامه ها با سخت افزار است ، نرم افزار ها برای ارتباط با سخت افزار از طریق واحد ارتباطی به نام syscall درخواست خود را به kernel ارسال میکنند ، یک مثال بسیار ساده از syscall استفاده از عملگر read یا write برای خواندن یا نوشتن اطلاعات .

تاریخ BPF

این اسم منتخب از حروف اول سه کلمه میباشد berkeley packet filter و همانطور که از اسم پیداست توسط دانشگاه berkeley ارائه شده است .

دانشگاه زیبای برکلی
دانشگاه زیبای برکلی


در روز های اول این ماشین مجازی فقط دو instruction داشت و فقط بر روی بسته های ورودی شبکه تمرکز داشت که عمل فیلتر را بر روی آنها انجام میداد ، و بعد ها برنامه نویسان kernel متوجه ظرفیت بالای این قطعه نرم افزاری شدند و شروع به بروز رسانی این نرم افزار کردند و تعداد instruction ها را افزایش دادند و نرم افزار های زیادی در کنار این برنامه اضافه شد برای مثال امکان رسد کردن قسمت های مختلف سیستم عامل یا XDP که در مثال ارائه شده در این بلاگ از آن استفاده خواهیم کرد .


معرفی منابع آموزشی

کتاب اول :
Liz Rice - Learning eBPF_ Programming the Linux Kernel for Enhanced Observability, Networking, and Security-O'Reilly Media (2023)

نویسنده این کتاب کتاب هایی بسیار جالب دیگری در زمینه کامپیوتر و امنیت نوشتن که به نظر من خواندنشون بسیار کمک میکنه به دوستان علاقه مند .

کتاب دوم :

David Calavera, Lorenzo Fontana - Linux Observability with BPF_ Advanced Programming for Performance Analysis and Networking-O’Reilly Media (2019)

نویسنده این کتاب در شرکت Docker کار میکرد و پس از مشکلات زیادی که با شبکه روی سیستم عامل داشت توجه ایشون به ebpf جلب میشه و بعد از کلی تجربه این کتاب رو مینویسند ،



مثال:

برای این مثال از زبان C استفاده میکنیم . برای برنامه نویسی در محیط ebpf میتوانید از زبان هایی مثل GO , RUST و اسمبلی استفاده کنید .
دانش محدود من توی این موضوع میگه که بالغ ترین اکو سیستم ها برای این محیط C و اسمبلی هست و GO خیلی خوب داره موضوعات مختلف رو اضافه میکنه و RUST هم در حال جذب علاقه مندان خودش و اضافه کردن موضوعات و آموزش روی اینترنت هست .

برای این مثال پیشنهاد میدم که یک لینوکس را در محیط شبیه سازی نصب بکنید مثل Oracle vm virtualbox بعد از نصب و اطمینان از سلامت VM چند نرم افزار نیاز دارید .
1- LLVM و clang برای کامپایل کد C به بایت کد ebpf و بعد از اون تبدیل به زبان ماشین .
2- bpftool برای اضافه کردن و چسباندن فایل کامپایل شده به کارت شبکه .

کد :

1- #include <linux/bpf.h> 2- #include <bpf/bpf_helpers.h> 3- #include <linux/if_ether.h> 4- SEC(&quotxdp&quot) 5- int macprinter(struct xdp_md *ctx){ 6- void *data = (void *)(long)ctx->data; 7- void *data_end = (void *)(long)ctx->data_end; 8- if ((void *)(eth+1) > data_end) 9- return XDP_PASS; 10- bpf_printk(&quotS_MAC %x:%x:%x &quot ,eth->h_source[0],eth->h_source[1],eth->h_source[2]); 11- bpf_printk(&quotS_MAC %x:%x:%x \n&quot ,eth->h_source[3],eth->h_source[4],eth->h_source[5]); 12- return XDP_PASS; } 13- char LICENSE[] SEC(&quotlicense&quot) = &quotDual BSD/GPL&quot

توضیح هر خط کد :

1 سربرگ (headers) های کد bpf موجود در کرنل
2 سربرگ های کد bpf_helpers - این عملگر ها جهت ساده سازی برنامه نویسی در کرنل قرار داده شده اند و خیلی استفاده های مختلفی خواهند داشت


3 سربرگ های کرنل لینوکس برای ساختار داده سربرگ بسته های شبکه ( ethernet header )
4 نمیدونم
5 شروع عملگر اصلی که سازه اطلاعاتی xdp_md را دریافت میکند، این سازه دارای دو قسمت مهم است که اولی آدرس شروع اطلاعات و دومی آدرس پایان اطلاعات بر روی RAM است .
6 cast اطلاعات ورودی آدرس شروع اطلاعات به سازه data با نوع تهی ( این نوع سازه ها برای کار با Buffer هست ، ساختار اطلاعاتی شناخته شده برای کار کردن با اطلاعات شبکه است )
7 تکرار مرحله 6 برای خانه اطلاعات پایانی
8 این خط برای چیزی به اسم verifier مهم است و اطمینانی به سیستم است که کاربر دسترسی بیش از حد به RAM و اطلاعات نرم افزار های دیگر نداشته باشد . اگر این خط رو اضافه نکنید برای خاندن اطلاعات خیلی به مشکل خواهید خرد .
9 xdp دارای 4 برگرداننده است و در این برنامه از XDP_PASS برای چک کردن طول بسته استفاده کردم ، این خط و خط قبل چک میکنند تا ببینند طول اطلاعات با چیزی که انتظار داشتیم برابر است و اگر نبود برنامه را اتمام میکند و اطلاعات را به کرنل ارسال میکند .
XDP_DROP : از بین بردن اطلاعات ورودی
XDP_PASS : انتقال اطلاعات به کرنل جهت انجام کارهای لازم و استفاده از در ساختار شبکه سیستم عامل
XDP_TX : انتقال اطلاعات به مبدا اطلاعات پس از تغییرات لازم در آن .
XDP_REDIRECT : انتقال اطلاعات به کارت شبکه مورد نظر برنامه نویس .

10 و 11 این دو خط اطلاعات آدرس سخت افزاری کارت شبکه ارسال کننده بسته های شبکه را چاپ میکنند .

12 ارسال اطلاعات شبکه به کرنل و اتمام عملگر .

13 کد اجباری license که در در بعضی از برنامه های bpf باید باشد ، درواقع اگر از بعضی عملگر ها استفاده کرده باشید این شرط تحمیل میشود ، میشود این اجبار نباشد اما کد این عملگر ها را باید خودتان پیاده سازی بکنید .

این کد را در پوشه ای که دوست دارید ذخیره کنید .
بعد برای کامپایل کردن فایل از دستور زیر استفاده بکنید.

clang -g -O2 -target bpf -c tutorial.bpf.c -o tutorial.bpf.o

بعد از کامپایل دو یک فایل جدید به اسم tutorial.bpf.o خواهید داشت .

بعد از ابزار bpftool استفاده خواهید کرد و نرم افزار را به مکان مناسب منتقل خواهید کرد :

bpftool prog load tutorial.bpf.o /sys/fs/bpf/name

بعد از این کار میتوانید از طریق دستور زیر چک بکنید که ایا نرم افزار bpf شما در جای مناسب جهت اجرا هست یا خیر :

bpftool prog list

لاگی از اطلاعات خواهید دید که معمولا در اخر لیست میتوانید اطلاعات برنامه خود را پیدا بکنید.

67: xdp name ....

در این مثال ایدی برنامه 67 میباشد .

دستور زیر برنامه شما را به کارت شبکه میچسباند و هر موقع که بسته جدید به این کارت شبکه برسد برنامه فعال میشود .

bpftool net attach xdp id 67 dev { اسم کارت شبکه مورد نظر }

بعد از این میتواند با دستور زیر اطلاعات پرینت شده را بخوانید:

cat /sys/kernel/debug/tracing/trace_pipe


اگر از سیستم عامل خود پینگ کار شبکه ای که نرم افزار را به آن چسبانده این بگیرید میتوانید MAC آدرس پرینت شده را مشاهده بکنید .

ممنون از توجه شما دوستان و تمام .

bpfلینوکسبرنامه نویسینرم افزار
شاید از این پست‌ها خوشتان بیاید