BPF برای کوچکترها، قسمت صفر: BPF کلاسیک

فیلترهای بسته برکلی (BPF) یک فناوری هسته لینوکس است که چندین سال است که در صفحات اول انتشارات فنی انگلیسی زبان قرار دارد. کنفرانس ها با گزارش هایی در مورد استفاده و توسعه BPF پر شده است. دیوید میلر، نگهدارنده زیرسیستم شبکه لینوکس، سخنرانی خود را در Linux Plumbers 2018 برگزار می کند "این صحبت در مورد XDP نیست" (XDP یک مورد استفاده برای BPF است). برندان گرگ سخنرانی هایی با عنوان ابرقدرت های لینوکس BPF. توکه هویلند-یورگنسن می خنددکه اکنون هسته یک میکروکرنل است. توماس گراف این ایده را ترویج می کند که BPF جاوا اسکریپت برای هسته است.

هنوز هیچ توصیف منظمی از BPF در Habré وجود ندارد، و بنابراین در یک سری مقالات سعی خواهم کرد در مورد تاریخچه فناوری صحبت کنم، معماری و ابزارهای توسعه را شرح دهم و زمینه های کاربرد و عمل استفاده از BPF را تشریح کنم. این مقاله، صفر، در این سری، تاریخچه و معماری BPF کلاسیک را بیان می‌کند و رازهای اصول عملکرد آن را نیز آشکار می‌کند. tcpdump, seccomp, strace، و خیلی بیشتر.

توسعه BPF توسط جامعه شبکه لینوکس کنترل می شود، برنامه های اصلی موجود BPF مربوط به شبکه ها است و بنابراین، با مجوز @eucariot، به افتخار سریال عالی اسم سریال را گذاشتم BPF برای کوچولوها "شبکه هایی برای کوچولوها".

دوره کوتاهی در تاریخ BPF(c)

فناوری مدرن BPF نسخه بهبود یافته و توسعه یافته فناوری قدیمی با همین نام است که اکنون BPF کلاسیک نامیده می شود تا از سردرگمی جلوگیری شود. یک ابزار معروف بر اساس BPF کلاسیک ایجاد شد tcpdump، سازوکار seccompو همچنین ماژول های کمتر شناخته شده xt_bpf برای iptables و طبقه بندی کننده cls_bpf. در لینوکس مدرن، برنامه های کلاسیک BPF به طور خودکار به شکل جدید ترجمه می شوند، با این حال، از نظر کاربر، API در جای خود باقی مانده است و همانطور که در این مقاله خواهیم دید، استفاده های جدیدی از BPF کلاسیک همچنان یافت می شود. به همین دلیل و همچنین به این دلیل که با توجه به تاریخچه توسعه BPF کلاسیک در لینوکس، چگونگی و چرایی تبدیل آن به شکل مدرن آن مشخص خواهد شد، تصمیم گرفتم با مقاله ای در مورد BPF کلاسیک شروع کنم.

در پایان دهه هشتاد قرن گذشته، مهندسان آزمایشگاه معروف لارنس برکلی به این سؤال علاقه مند شدند که چگونه بسته های شبکه را بر روی سخت افزارهایی که در اواخر دهه هشتاد قرن گذشته مدرن بودند، فیلتر کنند. ایده اصلی فیلتر کردن، که در ابتدا در فناوری CSPF (CMU/Stanford Packet Filter) پیاده‌سازی شد، فیلتر کردن بسته‌های غیر ضروری در اسرع وقت بود. در فضای هسته، زیرا از کپی کردن داده های غیر ضروری در فضای کاربر جلوگیری می کند. برای تامین امنیت زمان اجرا برای اجرای کد کاربر در فضای هسته، از ماشین مجازی سندباکس استفاده شد.

با این حال، ماشین‌های مجازی برای فیلترهای موجود برای اجرا بر روی ماشین‌های مبتنی بر پشته طراحی شده‌اند و در ماشین‌های جدید RISC کارآمد نیستند. در نتیجه، با تلاش مهندسان آزمایشگاه های برکلی، یک فناوری جدید BPF (فیلترهای بسته برکلی) توسعه یافت که معماری ماشین مجازی آن بر اساس پردازنده موتورولا 6502 طراحی شده است - اسب کار محصولات شناخته شده ای مانند اپل II یا NES. ماشین مجازی جدید عملکرد فیلتر را ده ها برابر در مقایسه با راه حل های موجود افزایش داد.

معماری ماشین BPF

با تحلیل نمونه ها به صورت کاری با معماری آشنا می شویم. با این حال، برای شروع، بیایید بگوییم که دستگاه دارای دو رجیستر 32 بیتی بود که در دسترس کاربر بود، یک انباشت کننده A و ثبت فهرست X64 بایت حافظه (16 کلمه)، برای نوشتن و خواندن بعدی، و یک سیستم کوچک از دستورات برای کار با این اشیاء موجود است. دستورالعمل‌های پرش برای اجرای عبارات شرطی نیز در برنامه‌ها موجود بود، اما برای تضمین تکمیل به موقع برنامه، فقط می‌توان به جلو پرش کرد، یعنی به طور خاص، ایجاد حلقه‌ها ممنوع بود.

طرح کلی برای راه اندازی دستگاه به شرح زیر است. کاربر برنامه ای برای معماری BPF ایجاد می کند و با استفاده از مقداری مکانیسم هسته (مانند فراخوانی سیستم)، برنامه را بارگیری و به آن متصل می کند برای بعضی ها به مولد رویداد در هسته (به عنوان مثال، یک رویداد ورود بسته بعدی به کارت شبکه است). هنگامی که یک رویداد رخ می دهد، هسته برنامه را اجرا می کند (به عنوان مثال، در یک مفسر)، و حافظه ماشین مربوط به برای بعضی ها منطقه حافظه هسته (به عنوان مثال، داده های یک بسته ورودی).

موارد فوق برای ما کافی است تا نمونه هایی را شروع کنیم: در صورت لزوم با سیستم و فرمت دستور آشنا می شویم. اگر می خواهید بلافاصله سیستم فرمان یک ماشین مجازی را مطالعه کنید و با تمام قابلیت های آن آشنا شوید، می توانید مقاله اصلی را بخوانید. فیلتر بسته BSD و/یا نیمه اول فایل Documentation/Networking/filter.txt از اسناد هسته علاوه بر این، می توانید ارائه را مطالعه کنید libpcap: یک روش معماری و بهینه سازی برای ضبط بسته ها، که در آن McCanne، یکی از نویسندگان BPF، درباره تاریخچه خلقت صحبت می کند libpcap.

اکنون به بررسی تمام نمونه های مهم استفاده از BPF کلاسیک در لینوکس می پردازیم: tcpdump (libpcap)، seccomp، xt_bpf, cls_bpf.

tcpdump

توسعه BPF به موازات توسعه قسمت جلویی برای فیلتر کردن بسته ها - یک ابزار معروف - انجام شد. tcpdump. و از آنجایی که این قدیمی‌ترین و معروف‌ترین نمونه استفاده از BPF کلاسیک است که در بسیاری از سیستم‌عامل‌ها موجود است، مطالعه خود را در مورد این فناوری با آن آغاز خواهیم کرد.

(من تمام نمونه های این مقاله را در لینوکس اجرا کردم 5.6.0-rc6. خروجی برخی از دستورات برای خوانایی بهتر ویرایش شده است.)

مثال: مشاهده بسته های IPv6

بیایید تصور کنیم که می خواهیم به تمام بسته های IPv6 در یک رابط نگاه کنیم eth0. برای این کار می توانیم برنامه را اجرا کنیم tcpdump با یک فیلتر ساده ip6:

$ sudo tcpdump -i eth0 ip6

در این مورد، tcpdump فیلتر را کامپایل می کند ip6 به بایت کد معماری BPF و ارسال آن به هسته (به جزئیات در بخش مراجعه کنید Tcpdump: در حال بارگذاری). فیلتر بارگذاری شده برای هر بسته ای که از رابط عبور می کند اجرا می شود eth0. اگر فیلتر مقدار غیر صفر را برگرداند n، سپس تا n بایت های بسته در فضای کاربر کپی می شود و آن را در خروجی می بینیم tcpdump.

BPF برای کوچکترها، قسمت صفر: BPF کلاسیک

معلوم می شود که ما به راحتی می توانیم بفهمیم کدام بایت کد به هسته ارسال شده است tcpdump با کمک tcpdump، اگر آن را با گزینه اجرا کنیم -d:

$ sudo tcpdump -i eth0 -d ip6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 3
(002) ret      #262144
(003) ret      #0

در خط صفر دستور را اجرا می کنیم ldh [12]، که مخفف "load into register A نیم کلمه (16 بیت) واقع در آدرس 12" و تنها سوال این است که ما به چه نوع حافظه ای خطاب می کنیم؟ پاسخ این است که در x شروع می شود (x+1)بایت دهم بسته شبکه تحلیل شده ما بسته ها را از رابط اترنت می خوانیم eth0و این به معنیکه بسته به این شکل است (برای سادگی، فرض می کنیم که هیچ برچسب VLAN در بسته وجود ندارد):

       6              6          2
|Destination MAC|Source MAC|Ether Type|...|

بنابراین پس از اجرای دستور ldh [12] در ثبت نام A میدانی وجود خواهد داشت Ether Type - نوع بسته ارسال شده در این فریم اترنت. در خط 1 ما محتویات ثبت نام را با هم مقایسه می کنیم A (نوع بسته) ج 0x86ddو این و وجود دارد نوع مورد علاقه ما IPv6 است. در خط 1، علاوه بر دستور مقایسه، دو ستون دیگر وجود دارد - jt 2 и jf 3 - علائمی که در صورت موفقیت آمیز بودن مقایسه باید به آنها بروید (A == 0x86dd) و ناموفق. بنابراین، در یک مورد موفق (IPv6) به خط 2 می رویم، و در مورد ناموفق - به خط 3. در خط 3 برنامه با کد 0 خاتمه می یابد (بسته را کپی نکنید)، در خط 2 برنامه با کد خاتمه می یابد. 262144 (حداکثر بسته 256 کیلوبایتی برای من کپی کنید).

یک مثال پیچیده تر: ما بسته های TCP را بر اساس پورت مقصد بررسی می کنیم

بیایید ببینیم فیلتری که تمام بسته های TCP را با پورت مقصد 666 کپی می کند چگونه به نظر می رسد. ما مورد IPv4 را در نظر خواهیم گرفت، زیرا مورد IPv6 ساده تر است. پس از مطالعه این مثال، می توانید فیلتر IPv6 را خودتان به عنوان تمرین کاوش کنید (ip6 and tcp dst port 666) و یک فیلتر برای حالت کلی (tcp dst port 666). بنابراین، فیلتر مورد علاقه ما به شکل زیر است:

$ sudo tcpdump -i eth0 -d ip and tcp dst port 666
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 10
(002) ldb      [23]
(003) jeq      #0x6             jt 4    jf 10
(004) ldh      [20]
(005) jset     #0x1fff          jt 10   jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 16]
(008) jeq      #0x29a           jt 9    jf 10
(009) ret      #262144
(010) ret      #0

ما قبلاً می دانیم که خطوط 0 و 1 چه کاری انجام می دهند. در خط 2 قبلاً بررسی کرده ایم که این یک بسته IPv4 است (Ether Type = 0x800) و آن را در رجیستر بارگذاری کنید A 24 بایت بسته. بسته ما به نظر می رسد

       14            8      1     1
|ethernet header|ip fields|ttl|protocol|...|

یعنی ما در رجیستر بارگذاری می کنیم A فیلد پروتکل سرآیند IP که منطقی است، زیرا می خواهیم فقط بسته های TCP را کپی کنیم. ما پروتکل را با 0x6 (IPPROTO_TCP) در خط 3.

در خطوط 4 و 5 نیم کلمات واقع در آدرس 20 را بارگذاری می کنیم و از دستور استفاده می کنیم jset بررسی کنید که آیا یکی از این سه تنظیم شده است پرچم ها - پوشیدن ماسک صادر شده jset سه بیت مهم پاک می شوند. دو بیت از سه بیت به ما می گویند که آیا بسته بخشی از یک بسته IP تکه تکه شده است یا خیر، و اگر چنین است، آیا آخرین قطعه است یا خیر. بیت سوم رزرو شده و باید صفر باشد. ما نمی خواهیم بسته های ناقص یا شکسته را بررسی کنیم، بنابراین هر سه بیت را بررسی می کنیم.

خط 6 جالب ترین در این لیست است. اصطلاح ldxb 4*([14]&0xf) یعنی ما در رجیستر بارگذاری می کنیم X کم اهمیت ترین چهار بیت از پانزدهمین بایت بسته ضرب در 4. کم اهمیت ترین چهار بیت پانزدهمین بایت فیلد است. طول هدر اینترنت هدر IPv4، که طول هدر را در کلمات ذخیره می کند، بنابراین باید در 4 ضرب کنید. جالب توجه است که عبارت 4*([14]&0xf) یک نام برای یک طرح آدرس دهی خاص است که فقط در این فرم و فقط برای یک ثبت قابل استفاده است X، یعنی ما هم نمی توانیم بگوییم ldb 4*([14]&0xf) و یا ldxb 5*([14]&0xf) (ما فقط می توانیم یک افست متفاوت را مشخص کنیم، برای مثال، ldxb 4*([16]&0xf)). واضح است که این طرح آدرس دهی دقیقاً به منظور دریافت به BPF اضافه شده است X (ثبت فهرست) طول سرصفحه IPv4.

بنابراین در خط 7 سعی می کنیم نیم کلمه را در بارگذاری کنیم (X+16). به یاد داشته باشید که 14 بایت توسط هدر اترنت اشغال شده است، و X شامل طول هدر IPv4 است، ما درک می کنیم که در A پورت مقصد TCP بارگیری می شود:

       14           X           2             2
|ethernet header|ip header|source port|destination port|

در نهایت در خط 8 پورت مقصد را با مقدار مورد نظر مقایسه می کنیم و در خطوط 9 یا 10 نتیجه را برمی گردانیم - بسته را کپی کنیم یا نه.

Tcpdump: در حال بارگذاری

در مثال‌های قبلی، ما به طور خاص به جزئیات نحوه بارگذاری بایت کد BPF در هسته برای فیلتر کردن بسته‌ها پرداختیم. به طور کلی، tcpdump به بسیاری از سیستم ها و برای کار با فیلترها منتقل می شود tcpdump از کتابخانه استفاده می کند libpcap. به طور خلاصه، برای قرار دادن یک فیلتر بر روی یک رابط با استفاده از libpcap، شما باید موارد زیر را انجام دهید:

برای دیدن نحوه عملکرد pcap_setfilter پیاده سازی شده در لینوکس، ما استفاده می کنیم strace (برخی از خطوط حذف شده است):

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

در دو خط اول خروجی ایجاد می کنیم سوکت خام برای خواندن تمام فریم های اترنت و اتصال آن به رابط eth0. از اولین مثال ما ما می دانیم که فیلتر ip از چهار دستورالعمل BPF تشکیل شده است و در خط سوم نحوه استفاده از گزینه را می بینیم SO_ATTACH_FILTER تماس سیستمی setsockopt فیلتری به طول 4 بارگذاری و وصل می کنیم. این فیلتر ماست.

شایان ذکر است که در BPF کلاسیک بارگذاری و اتصال فیلتر همیشه به صورت اتمی انجام می شود و در نسخه جدید BPF بارگذاری برنامه و اتصال آن به مولد رویداد به موقع از هم جدا می شود.

حقیقت پنهان

یک نسخه کمی کاملتر از خروجی به صورت زیر است:

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0xbeefbeefbeef}, 16) = 0
recvfrom(3, 0x7ffcad394257, 1, MSG_TRUNC, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

همانطور که در بالا ذکر شد، فیلتر خود را در خط 5 بارگذاری و به سوکت وصل می کنیم، اما در خطوط 3 و 4 چه اتفاقی می افتد؟ معلوم می شود که این libpcap از ما مراقبت می کند - به طوری که خروجی فیلتر ما شامل بسته هایی نیست که آن را برآورده نمی کند، کتابخانه متصل می کند فیلتر ساختگی ret #0 (همه بسته ها را رها کنید)، سوکت را به حالت غیر مسدود می کند و سعی می کند تمام بسته هایی را که می توانند از فیلترهای قبلی باقی بمانند کم کند.

در مجموع، برای فیلتر کردن بسته‌ها در لینوکس با استفاده از BPF کلاسیک، باید یک فیلتر به شکل ساختاری مانند struct sock_fprog و یک سوکت باز، پس از آن فیلتر را می توان با استفاده از یک تماس سیستمی به سوکت متصل کرد setsockopt.

جالب اینجاست که فیلتر را می توان به هر سوکتی وصل کرد، نه فقط خام. اینجا مثال برنامه ای که تمام دیتاگرام های UDP ورودی به جز دو بایت اول را قطع می کند. (من نظرات را در کد اضافه کردم تا مقاله به هم نریزد.)

جزئیات بیشتر در مورد استفاده setsockopt برای اتصال فیلترها را ببینید سوکت (7)، اما در مورد نوشتن فیلترهای خود مانند struct sock_fprog بدون کمک tcpdump ما در بخش صحبت خواهیم کرد برنامه نویسی BPF با دستان خودمان.

BPF کلاسیک و قرن بیست و یکم

BPF در سال 1997 در لینوکس گنجانده شد و برای مدت طولانی به عنوان یک نیروی کار باقی مانده است libpcap بدون هیچ تغییر خاصی (البته تغییرات خاص لینوکس، ما بودیم، اما آنها تصویر جهانی را تغییر ندادند). اولین نشانه های جدی مبنی بر تکامل BPF در سال 2011 بود، زمانی که اریک دومازت پیشنهاد داد. وصله، که کامپایلر Just In Time را به هسته اضافه می کند - مترجمی برای تبدیل بایت کد BPF به بومی x86_64 کد.

کامپایلر JIT اولین در زنجیره تغییرات بود: در سال 2012 ظاهر شد توانایی نوشتن فیلتر برای seccomp، با استفاده از BPF، در ژانویه 2013 وجود داشت اضافه ماژول xt_bpf، که به شما امکان می دهد قوانینی برای آن بنویسید iptables با کمک BPF، و در اکتبر 2013 بود اضافه همچنین یک ماژول cls_bpf، که به شما امکان می دهد طبقه بندی کننده های ترافیک را با استفاده از BPF بنویسید.

به زودی همه این نمونه ها را با جزئیات بیشتری بررسی خواهیم کرد، اما ابتدا برای ما مفید خواهد بود که یاد بگیریم چگونه برنامه های دلخواه را برای BPF بنویسیم و کامپایل کنیم، زیرا قابلیت های ارائه شده توسط کتابخانه libpcap محدود (مثال ساده: فیلتر ایجاد شده است libpcap می تواند تنها دو مقدار را برگرداند - 0 یا 0x40000) یا به طور کلی، مانند مورد seccomp، قابل استفاده نیستند.

برنامه نویسی BPF با دستان خودمان

بیایید با فرمت باینری دستورالعمل های BPF آشنا شویم، بسیار ساده است:

   16    8    8     32
| code | jt | jf |  k  |

هر دستورالعمل 64 بیت را اشغال می کند، که در آن 16 بیت اول کد دستورالعمل است، سپس دو تورفتگی هشت بیتی وجود دارد. jt и jfو 32 بیت برای آرگومان K، که هدف از دستوری به دستور دیگر متفاوت است. مثلا دستور ret، که برنامه را خاتمه می دهد دارای کد است 6، و مقدار بازگشتی از ثابت گرفته می شود K. در C، یک دستورالعمل BPF به عنوان یک ساختار نشان داده می شود

struct sock_filter {
        __u16   code;
        __u8    jt;
        __u8    jf;
        __u32   k;
}

و کل برنامه به صورت یک ساختار است

struct sock_fprog {
        unsigned short len;
        struct sock_filter *filter;
}

بنابراین، ما می‌توانیم برنامه‌هایی بنویسیم (مثلاً کدهای دستورالعمل را از آن می‌دانیم [1]). این همان چیزی است که فیلتر به نظر می رسد ip6 از اولین مثال ما:

struct sock_filter code[] = {
        { 0x28, 0, 0, 0x0000000c },
        { 0x15, 0, 1, 0x000086dd },
        { 0x06, 0, 0, 0x00040000 },
        { 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog prog = {
        .len = ARRAY_SIZE(code),
        .filter = code,
};

برنامه prog ما به طور قانونی می توانیم در تماس استفاده کنیم

setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))

نوشتن برنامه ها در قالب کدهای ماشین خیلی راحت نیست، اما گاهی اوقات لازم است (به عنوان مثال، برای اشکال زدایی، ایجاد تست واحد، نوشتن مقاله در Habré و غیره). برای راحتی، در فایل <linux/filter.h> ماکروهای کمکی تعریف شده اند - همان مثال فوق می تواند بازنویسی شود

struct sock_filter code[] = {
        BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
        BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETH_P_IPV6, 0, 1),
        BPF_STMT(BPF_RET|BPF_K, 0x00040000),
        BPF_STMT(BPF_RET|BPF_K, 0),
}

با این حال، این گزینه خیلی راحت نیست. این همان چیزی است که برنامه نویسان هسته لینوکس استدلال کردند، و بنابراین در دایرکتوری tools/bpf در هسته ها می توانید یک اسمبلر و دیباگر برای کار با BPF کلاسیک پیدا کنید.

زبان اسمبلی بسیار شبیه به خروجی دیباگ است tcpdump، اما علاوه بر این می توانیم برچسب های نمادین را مشخص کنیم. به عنوان مثال، در اینجا برنامه ای وجود دارد که همه بسته ها را به جز TCP/IPv4 حذف می کند:

$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0

به طور پیش فرض، اسمبلر کد را در قالب تولید می کند <количество инструкций>,<code1> <jt1> <jf1> <k1>,...، برای مثال ما با TCP خواهد بود

$ tools/bpf/bpf_asm /tmp/tcp-over-ipv4.bpf
6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 6,6 0 0 4294967295,6 0 0 0,

برای راحتی برنامه نویسان C، می توان از یک فرمت خروجی متفاوت استفاده کرد:

$ tools/bpf/bpf_asm -c /tmp/tcp-over-ipv4.bpf
{ 0x28,  0,  0, 0x0000000c },
{ 0x15,  0,  3, 0x00000800 },
{ 0x30,  0,  0, 0x00000017 },
{ 0x15,  0,  1, 0x00000006 },
{ 0x06,  0,  0, 0xffffffff },
{ 0x06,  0,  0, 0000000000 },

این متن را می توان در تعریف ساختار نوع کپی کرد struct sock_filter، همانطور که در ابتدای این بخش انجام دادیم.

پسوندهای لینوکس و netsniff-ng

علاوه بر استاندارد BPF، لینوکس و tools/bpf/bpf_asm پشتیبانی و مجموعه غیر استاندارد. اصولاً از دستورالعمل ها برای دسترسی به فیلدهای یک سازه استفاده می شود struct sk_buff، که یک بسته شبکه را در هسته توصیف می کند. با این حال، برای مثال، انواع دیگری از دستورالعمل های کمکی نیز وجود دارد ldw cpu در رجیستر بارگذاری می شود A نتیجه اجرای یک تابع هسته raw_smp_processor_id(). (در نسخه جدید BPF، این افزونه‌های غیراستاندارد برای ارائه برنامه‌هایی با مجموعه‌ای از کمک‌کننده‌های هسته برای دسترسی به حافظه، ساختارها و ایجاد رویدادها گسترش یافته‌اند.) در اینجا یک مثال جالب از فیلتری وجود دارد که در آن ما فقط فایل را کپی می‌کنیم. هدرهای بسته را با استفاده از پسوند به فضای کاربر وارد کنید poff، جبران بار محموله:

ld poff
ret a

پسوندهای BPF را نمی توان در آن استفاده کرد tcpdump، اما این دلیل خوبی برای آشنایی با پکیج یوتیلیتی است netsniff-ng، که از جمله شامل یک برنامه پیشرفته است netsniff-ng، که علاوه بر فیلتر کردن با استفاده از BPF، دارای یک مولد ترافیک موثر و پیشرفته تر از tools/bpf/bpf_asm، یک اسمبلر BPF تماس گرفت bpfc. این بسته حاوی اسناد کاملاً دقیق است، همچنین پیوندهای انتهای مقاله را ببینید.

seccomp

بنابراین، ما قبلاً می دانیم که چگونه برنامه های BPF با پیچیدگی دلخواه بنویسیم و آماده بررسی نمونه های جدید هستیم که اولین آنها فناوری seccomp است که با استفاده از فیلترهای BPF امکان مدیریت مجموعه و مجموعه آرگومان های فراخوانی سیستم در دسترس را می دهد. یک فرآیند معین و فرزندان آن

اولین نسخه seccomp در سال 2005 به هسته اضافه شد و چندان محبوب نبود، زیرا تنها یک گزینه را ارائه می کرد - محدود کردن مجموعه فراخوانی های سیستمی موجود برای یک فرآیند به موارد زیر: read, write, exit и sigreturn، و فرآیندی که قوانین را نقض می کرد با استفاده از آن کشته شد SIGKILL. با این حال، در سال 2012، seccomp قابلیت استفاده از فیلترهای BPF را اضافه کرد که به شما امکان می داد مجموعه ای از تماس های سیستمی مجاز را تعریف کنید و حتی آرگومان های آنها را بررسی کنید. (جالب اینجاست که Chrome یکی از اولین کاربران این قابلیت بود، و افراد Chrome در حال حاضر در حال توسعه یک مکانیسم KRSI بر اساس نسخه جدید BPF و اجازه سفارشی‌سازی ماژول‌های امنیتی لینوکس هستند.) پیوندهای اسناد اضافی را می‌توانید در پایان پیدا کنید. از مقاله

توجه داشته باشید که قبلاً مقالاتی در مورد استفاده از seccomp در هاب وجود داشته است، شاید کسی بخواهد آنها را قبل از (یا به جای) خواندن زیربخش های زیر بخواند. در مقاله کانتینرها و امنیت: seccomp نمونه هایی از استفاده از seccomp، هم نسخه 2007 و هم نسخه با استفاده از BPF را ارائه می دهد (فیلترها با استفاده از libseccomp تولید می شوند)، در مورد اتصال seccomp با Docker صحبت می کند و همچنین لینک های مفید زیادی را ارائه می دهد. در مقاله جداسازی دیمون ها با systemd یا "شما برای این کار به Docker نیاز ندارید!" به ویژه نحوه افزودن لیست سیاه یا لیست سفید فراخوان های سیستمی برای دیمون های در حال اجرا systemd را پوشش می دهد.

در ادامه نحوه نوشتن و بارگذاری فیلترها را خواهیم دید seccomp در C خالی و با استفاده از کتابخانه libseccomp و مزایا و معایب هر گزینه چیست، و در نهایت، بیایید ببینیم که seccomp چگونه توسط برنامه استفاده می شود strace.

نوشتن و بارگذاری فیلترها برای seccomp

ما قبلاً می دانیم که چگونه برنامه های BPF بنویسیم، بنابراین اجازه دهید ابتدا به رابط برنامه نویسی seccomp نگاه کنیم. شما می توانید یک فیلتر در سطح فرآیند تنظیم کنید، و همه پردازش های فرزند محدودیت ها را به ارث خواهند برد. این کار با استفاده از یک تماس سیستمی انجام می شود seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

جایی که &filter - این یک اشاره گر به ساختاری است که قبلاً برای ما آشناست struct sock_fprog، یعنی برنامه BPF

برنامه های seccomp چه تفاوتی با برنامه های سوکت دارند؟ زمینه منتقل شده در مورد سوکت ها، یک ناحیه حافظه حاوی بسته به ما داده شد و در مورد seccomp ساختاری مانند

struct seccomp_data {
    int   nr;
    __u32 arch;
    __u64 instruction_pointer;
    __u64 args[6];
};

اینجا nr شماره تماس سیستمی است که باید راه اندازی شود، arch - معماری فعلی (اطلاعات بیشتر در این مورد در زیر)، args - تا شش آرگومان فراخوانی سیستم و instruction_pointer یک اشاره گر به دستورالعمل فضای کاربر است که سیستم را فراخوانی کرده است. بنابراین، برای مثال، برای بارگذاری شماره تماس سیستم در رجیستر A باید بگوییم

ldw [0]

ویژگی‌های دیگری برای برنامه‌های seccomp وجود دارد، به‌عنوان مثال، می‌توان به متن فقط با تراز 32 بیتی دسترسی پیدا کرد و نمی‌توانید نیم کلمه یا یک بایت را بارگیری کنید - هنگام تلاش برای بارگیری فیلتر. ldh [0] تماس سیستمی seccomp بر خواهد گشت EINVAL. تابع فیلترهای بارگذاری شده را بررسی می کند seccomp_check_filter() هسته ها (نکته خنده دار این است که در commit اصلی که عملکرد seccomp را اضافه کرد، آنها فراموش کردند مجوز استفاده از دستورالعمل را به این تابع اضافه کنند. mod (باقی مانده تقسیم) و اکنون برای برنامه های seccomp BPF، از زمان اضافه شدن آن، در دسترس نیست خواهد شکست ABI.)

اساساً ما همه چیز را برای نوشتن و خواندن برنامه های seccomp می دانیم. معمولاً منطق برنامه به صورت یک لیست سفید یا سیاه از فراخوانی های سیستمی مرتب می شود، برای مثال برنامه

ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0

لیست سیاهی از چهار تماس سیستمی با شماره های 304، 176، 239، 279 را بررسی می کند. این تماس های سیستمی چیست؟ نمی‌توانیم با اطمینان بگوییم، زیرا نمی‌دانیم این برنامه برای کدام معماری نوشته شده است. بنابراین، نویسندگان seccomp پیشنهاد همه برنامه ها را با بررسی معماری شروع کنید (معماری فعلی در متن به عنوان یک فیلد نشان داده شده است arch سازه های struct seccomp_data). با بررسی معماری، ابتدای مثال به صورت زیر خواهد بود:

ld [4]
jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64

و سپس شماره تماس سیستم ما مقادیر خاصی را دریافت می کند.

ما فیلترها را برای seccomp می نویسیم و بارگذاری می کنیم libseccomp

نوشتن فیلترها در کد بومی یا در مجموعه BPF به شما امکان می دهد کنترل کاملی بر نتیجه داشته باشید، اما در عین حال، گاهی اوقات ترجیح داده می شود که کد قابل حمل و/یا قابل خواندن داشته باشید. کتابخانه در این امر به ما کمک خواهد کرد libseccomp، که یک رابط استاندارد برای نوشتن فیلترهای سیاه یا سفید ارائه می دهد.

به عنوان مثال، بیایید برنامه ای بنویسیم که یک فایل باینری را به انتخاب کاربر اجرا می کند و قبلاً لیست سیاهی از تماس های سیستمی را نصب کرده است. مقاله فوق (این برنامه برای خوانایی بیشتر ساده شده است، نسخه کامل آن را می توان یافت اینجا):

#include <seccomp.h>
#include <unistd.h>
#include <err.h>

static int sys_numbers[] = {
        __NR_mount,
        __NR_umount2,
       // ... еще 40 системных вызовов ...
        __NR_vmsplice,
        __NR_perf_event_open,
};

int main(int argc, char **argv)
{
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);

        for (size_t i = 0; i < sizeof(sys_numbers)/sizeof(sys_numbers[0]); i++)
                seccomp_rule_add(ctx, SCMP_ACT_TRAP, sys_numbers[i], 0);

        seccomp_load(ctx);

        execvp(argv[1], &argv[1]);
        err(1, "execlp: %s", argv[1]);
}

ابتدا یک آرایه تعریف می کنیم sys_numbers بیش از 40 شماره تماس سیستم برای مسدود کردن. سپس، زمینه را مقداردهی اولیه کنید ctx و به کتابخانه بگویید چه چیزی می خواهیم اجازه دهیم (SCMP_ACT_ALLOW) همه تماس های سیستمی به طور پیش فرض (ساخت لیست سیاه آسان تر است). سپس یک به یک همه تماس های سیستمی را از لیست سیاه اضافه می کنیم. در پاسخ به یک تماس سیستمی از لیست، ما درخواست می کنیم SCMP_ACT_TRAP، در این حالت seccomp یک سیگنال به فرآیند ارسال می کند SIGSYS با توضیح اینکه کدام تماس سیستمی قوانین را نقض کرده است. در نهایت برنامه را با استفاده از کرنل بارگذاری می کنیم seccomp_load، که برنامه را کامپایل کرده و با استفاده از یک فراخوانی سیستمی به فرآیند متصل می کند seccomp(2).

برای کامپایل موفق، برنامه باید با کتابخانه مرتبط باشد libseccomp، برای مثال:

cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccomp

نمونه ای از پرتاب موفق:

$ ./seccomp_lib echo ok
ok

مثالی از تماس سیستمی مسدود شده:

$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Bad system call

ما استفاده می کنیم straceبرای جزئیات:

$ sudo strace -e seccomp ./seccomp_lib mount -t bpf bpf /tmp
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=50, filter=0x55d8e78428e0}) = 0
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xboobdeadbeef, si_syscall=__NR_mount, si_arch=AUDIT_ARCH_X86_64} ---
+++ killed by SIGSYS (core dumped) +++
Bad system call

چگونه می توانیم بفهمیم که برنامه به دلیل استفاده از تماس غیرقانونی سیستم خاتمه یافته است mount(2).

بنابراین، ما یک فیلتر با استفاده از کتابخانه نوشتیم libseccomp، کدهای غیر ضروری را در چهار خط قرار می دهد. در مثال بالا، اگر تعداد زیادی تماس سیستمی وجود داشته باشد، زمان اجرا می تواند به میزان قابل توجهی کاهش یابد، زیرا بررسی فقط لیستی از مقایسه است. برای بهینه سازی، libseccomp اخیرا داشت پچ گنجانده شده است، که پشتیبانی از ویژگی فیلتر را اضافه می کند SCMP_FLTATR_CTL_OPTIMIZE. تنظیم این ویژگی روی 2، فیلتر را به یک برنامه جستجوی باینری تبدیل می کند.

اگر می خواهید ببینید فیلترهای جستجوی باینری چگونه کار می کنند، نگاهی به آن بیندازید اسکریپت ساده، که با شماره گیری شماره تماس سیستم، چنین برنامه هایی را در اسمبلر BPF تولید می کند، به عنوان مثال:

$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, bad
jgt #6, check8
jeq #1, bad
jeq #3, bad
ret #0x7fff0000
check8:
jeq #8, bad
jeq #13, bad
ret #0x7fff0000
bad: ret #0

شما نمی توانید چیزی را به طور قابل توجهی سریعتر بنویسید، زیرا برنامه های BPF نمی توانند پرش های تورفتگی را انجام دهند (برای مثال، ما نمی توانیم انجام دهیم، jmp A یا jmp [label+X]) و بنابراین همه انتقال ها ثابت هستند.

seccomp و strace

همه کاربرد را می دانند strace ابزاری ضروری برای مطالعه رفتار فرآیندها در لینوکس است. با این حال، بسیاری نیز در مورد آن شنیده اند مسائل مربوط به عملکرد هنگام استفاده از این ابزار حقیقت این هست که strace با استفاده از ptrace(2)و در این مکانیسم نمی‌توانیم مشخص کنیم که در چه مجموعه‌ای از فراخوانی‌های سیستمی باید فرآیند را متوقف کنیم، به عنوان مثال، دستورات

$ time strace du /usr/share/ >/dev/null 2>&1

real    0m3.081s
user    0m0.531s
sys     0m2.073s

и

$ time strace -e open du /usr/share/ >/dev/null 2>&1

real    0m2.404s
user    0m0.193s
sys     0m1.800s

تقریباً در یک زمان پردازش می شوند، اگرچه در مورد دوم می خواهیم فقط یک تماس سیستمی را ردیابی کنیم.

گزینه جدید --seccomp-bpf، اضافه شده به strace نسخه 5.3، به شما امکان می دهد چندین بار روند را تسریع کنید و زمان راه اندازی تحت ردیابی یک تماس سیستمی در حال حاضر با زمان راه اندازی معمولی قابل مقایسه است:

$ time strace --seccomp-bpf -e open du /usr/share/ >/dev/null 2>&1

real    0m0.148s
user    0m0.017s
sys     0m0.131s

$ time du /usr/share/ >/dev/null 2>&1

real    0m0.140s
user    0m0.024s
sys     0m0.116s

(البته در اینجا یک فریب جزئی وجود دارد که ما در حال ردیابی تماس اصلی سیستم این دستور نیستیم. اگر مثلاً ردیابی می کردیم، newfsstat، و سپس strace به همان شدت ترمز می‌کرد --seccomp-bpf.)

این گزینه چگونه کار می کند؟ بدون او strace به فرآیند متصل می شود و استفاده از آن را شروع می کند PTRACE_SYSCALL. هنگامی که یک فرآیند مدیریت شده یک تماس (هر) سیستمی را صادر می کند، کنترل به آن منتقل می شود strace، که به آرگومان های فراخوانی سیستم نگاه می کند و آن را با آن اجرا می کند PTRACE_SYSCALL. پس از مدتی فرآیند فراخوانی سیستم را کامل می کند و هنگام خروج از آن، کنترل مجدداً منتقل می شود strace، که به مقادیر بازگشتی نگاه می کند و فرآیند را با استفاده از آن شروع می کند PTRACE_SYSCALL، و غیره.

BPF برای کوچکترها، قسمت صفر: BPF کلاسیک

با این حال، با seccomp، این فرآیند می تواند دقیقاً همانطور که می خواهیم بهینه شود. یعنی اگر بخواهیم فقط به فراخوانی سیستم نگاه کنیم X، سپس می توانیم یک فیلتر BPF بنویسیم که برای X مقداری را برمی گرداند SECCOMP_RET_TRACEو برای تماس هایی که مورد علاقه ما نیستند - SECCOMP_RET_ALLOW:

ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000

در این مورد strace در ابتدا فرآیند را به عنوان شروع می کند PTRACE_CONT، فیلتر ما برای هر تماس سیستمی پردازش می شود، اگر تماس سیستمی نباشد X، سپس روند به اجرا ادامه می یابد، اما اگر این X، سپس seccomp کنترل را منتقل می کند straceکه به آرگومان ها نگاه می کند و فرآیند را شروع می کند PTRACE_SYSCALL (از آنجایی که seccomp توانایی اجرای برنامه در هنگام خروج از تماس سیستمی را ندارد). وقتی تماس سیستمی برمی گردد، strace با استفاده از این فرآیند دوباره راه اندازی می شود PTRACE_CONT و منتظر پیام های جدید از seccomp خواهد بود.

BPF برای کوچکترها، قسمت صفر: BPF کلاسیک

هنگام استفاده از گزینه --seccomp-bpf دو محدودیت وجود دارد اولاً، پیوستن به یک فرآیند از قبل موجود امکان پذیر نخواهد بود (گزینه -p برنامه strace، زیرا این توسط seccomپ پشتیبانی نمی شود. ثانیا امکانی وجود ندارد هیچ به فرآیندهای فرزند نگاه کنید، زیرا فیلترهای seccomp توسط همه فرآیندهای فرزند بدون توانایی غیرفعال کردن به ارث برده می شوند.

کمی جزئیات بیشتر در مورد چگونگی دقیق strace کار میکند با seccomp را می توان از گزارش اخیر. برای ما، جالب ترین واقعیت این است که BPF کلاسیک که توسط seccomp نشان داده شده است، هنوز هم امروزه استفاده می شود.

xt_bpf

حالا برگردیم به دنیای شبکه ها.

زمینه: مدت ها پیش، در سال 2007، هسته اصلی بود اضافه ماژول xt_u32 برای نت فیلتر این به قیاس با یک طبقه بندی ترافیک حتی قدیمی تر نوشته شده است cls_u32 و به شما این امکان را می دهد که قوانین دودویی دلخواه را برای iptables با استفاده از عملیات ساده زیر بنویسید: 32 بیت را از یک بسته بارگیری کنید و مجموعه ای از عملیات حسابی را روی آنها انجام دهید. مثلا،

sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"

32 بیت هدر IP را بارگیری می کند که از صفحه 6 شروع می شود و یک ماسک روی آنها اعمال می کند. 0xFF (بایت کم را بردارید). این زمینه protocol هدر IP و ما آن را با 1 (ICMP) مقایسه می کنیم. شما می توانید چک های زیادی را در یک قانون ترکیب کنید و همچنین می توانید اپراتور را اجرا کنید @ - X بایت را به سمت راست حرکت دهید. به عنوان مثال، قانون

iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"

بررسی می کند که آیا TCP Sequence Number برابر نیست 0x29. من به جزئیات بیشتر نمی پردازم ، زیرا از قبل واضح است که نوشتن چنین قوانینی با دست خیلی راحت نیست. در مقاله BPF - بایت کد فراموش شده، چندین لینک با مثال هایی از استفاده و تولید قوانین برای وجود دارد xt_u32. پیوندهای انتهای این مقاله را نیز ببینید.

از سال 2013 ماژول به جای ماژول xt_u32 می توانید از یک ماژول مبتنی بر BPF استفاده کنید xt_bpf. هر کسی که تا اینجا خوانده است باید از قبل در مورد اصل عملکرد آن کاملاً آگاه باشد: بایت کد BPF را به عنوان قوانین iptables اجرا کنید. شما می توانید یک قانون جدید ایجاد کنید، به عنوان مثال، مانند این:

iptables -A INPUT -m bpf --bytecode <байткод> -j LOG

اینجا <байткод> - این کد در فرمت خروجی اسمبلر است bpf_asm به طور پیش فرض، به عنوان مثال،

$ cat /tmp/test.bpf
ldb [9]
jneq #17, ignore
ret #1
ignore: ret #0

$ bpf_asm /tmp/test.bpf
4,48 0 0 9,21 0 1 17,6 0 0 1,6 0 0 0,

# iptables -A INPUT -m bpf --bytecode "$(bpf_asm /tmp/test.bpf)" -j LOG

در این مثال ما تمام بسته های UDP را فیلتر می کنیم. زمینه برای یک برنامه BPF در یک ماژول xt_bpfالبته، در مورد iptables، به داده های بسته به ابتدای هدر IPv4 اشاره می کند. مقدار برگشتی از برنامه BPF بولیجایی که false یعنی بسته مطابقت نداشت

واضح است که ماژول xt_bpf از فیلترهای پیچیده تری نسبت به مثال بالا پشتیبانی می کند. بیایید به نمونه های واقعی از Cloudfare نگاه کنیم. تا همین اواخر از ماژول استفاده می کردند xt_bpf برای محافظت در برابر حملات DDoS در مقاله معرفی ابزار BPF آنها توضیح می دهند که چگونه (و چرا) فیلترهای BPF را تولید می کنند و پیوندهایی را به مجموعه ای از ابزارهای کاربردی برای ایجاد چنین فیلترهایی منتشر می کنند. به عنوان مثال، با استفاده از ابزار bpfgen شما می توانید یک برنامه BPF ایجاد کنید که با یک کوئری DNS برای یک نام مطابقت دارد habr.com:

$ ./bpfgen --assembly dns -- habr.com
ldx 4*([0]&0xf)
ld #20
add x
tax

lb_0:
    ld [x + 0]
    jneq #0x04686162, lb_1
    ld [x + 4]
    jneq #0x7203636f, lb_1
    ldh [x + 8]
    jneq #0x6d00, lb_1
    ret #65535

lb_1:
    ret #0

در برنامه ابتدا در رجیستر بارگذاری می کنیم X آدرس شروع خط x04habrx03comx00 داخل یک دیتاگرام UDP و سپس درخواست را بررسی کنید: 0x04686162 <-> "x04hab" غیره

کمی بعد، Cloudfare کد کامپایلر p0f -> BPF را منتشر کرد. در مقاله معرفی کامپایلر p0f BPF آنها در مورد چیستی p0f و نحوه تبدیل امضاهای p0f به BPF صحبت می کنند:

$ ./bpfgen p0f -- 4:64:0:0:*,0::ack+:0
39,0 0 0 0,48 0 0 8,37 35 0 64,37 0 34 29,48 0 0 0,
84 0 0 15,21 0 31 5,48 0 0 9,21 0 29 6,40 0 0 6,
...

در حال حاضر دیگر از Cloudfare استفاده نمی‌شود xt_bpf، از آنجایی که آنها به XDP منتقل شدند - یکی از گزینه های استفاده از نسخه جدید BPF، نگاه کنید. L4Drop: XDP DDoS Mitigations.

cls_bpf

آخرین نمونه استفاده از BPF کلاسیک در هسته، طبقه بندی کننده است cls_bpf برای زیرسیستم کنترل ترافیک در لینوکس، که در پایان سال 2013 به لینوکس اضافه شد و به طور مفهومی جایگزین سیستم قدیمی شد. cls_u32.

با این حال، ما اکنون کار را شرح نمی دهیم cls_bpf، از آنجایی که از نقطه نظر دانش در مورد BPF کلاسیک این چیزی به ما نمی دهد - ما قبلاً با تمام عملکردها آشنا شده ایم. علاوه بر این، در مقالات بعدی که در مورد Extended BPF صحبت می کنیم، بیش از یک بار با این طبقه بندی کننده آشنا خواهیم شد.

دلیل دیگری برای صحبت نکردن در مورد استفاده از BPF کلاسیک c cls_bpf مشکل این است که، در مقایسه با Extended BPF، دامنه کاربرد در این مورد به شدت محدود شده است: برنامه های کلاسیک نمی توانند محتویات بسته ها را تغییر دهند و نمی توانند وضعیت را بین تماس ها ذخیره کنند.

پس وقت آن است که با BPF کلاسیک خداحافظی کنیم و به آینده نگاه کنیم.

خداحافظی با BPF کلاسیک

ما به این نگاه کردیم که چگونه فناوری BPF که در اوایل دهه نود توسعه یافته بود، با موفقیت به مدت یک ربع قرن زندگی کرد و تا پایان آن کاربردهای جدیدی پیدا کرد. با این حال، مشابه انتقال از ماشین های پشته ای به RISC، که به عنوان انگیزه ای برای توسعه BPF کلاسیک عمل کرد، در دهه 32 انتقال از ماشین های 64 بیتی به ماشین های XNUMX بیتی انجام شد و BPF کلاسیک شروع به منسوخ شدن کرد. علاوه بر این، قابلیت های BPF کلاسیک بسیار محدود است و علاوه بر معماری قدیمی - ما توانایی ذخیره حالت بین تماس ها با برنامه های BPF را نداریم، امکان تعامل مستقیم با کاربر وجود ندارد، امکان تعامل وجود ندارد. با هسته، به جز خواندن تعداد محدودی از فیلدهای ساختار sk_buff و با راه اندازی ساده ترین توابع کمکی، نمی توانید محتویات بسته ها را تغییر دهید و آنها را تغییر مسیر دهید.

در واقع، در حال حاضر تنها چیزی که از BPF کلاسیک در لینوکس باقی مانده است، رابط API است، و در داخل هسته، تمام برنامه های کلاسیک، چه فیلترهای سوکت یا فیلترهای seccomp، به طور خودکار به فرمت جدید، Extended BPF ترجمه می شوند. (در مقاله بعدی در مورد چگونگی این اتفاق صحبت خواهیم کرد.)

انتقال به یک معماری جدید در سال 2013 آغاز شد، زمانی که الکسی استاروویتوف یک طرح به روز رسانی BPF را پیشنهاد کرد. در سال 2014 پچ های مربوطه شروع به ظاهر شدن کرد در هسته تا آنجا که من متوجه شدم، برنامه اولیه فقط بهینه سازی معماری و کامپایلر JIT برای اجرای کارآمدتر بر روی ماشین های 64 بیتی بود، اما در عوض این بهینه سازی ها آغاز فصل جدیدی در توسعه لینوکس بود.

مقالات بیشتر در این مجموعه، معماری و کاربردهای فناوری جدید را که در ابتدا با نام BPF داخلی، سپس BPF توسعه یافته و اکنون به سادگی BPF شناخته می‌شود، پوشش خواهند داد.

مراجع

  1. استیون مک کان و ون جاکوبسون، "فیلتر بسته BSD: معماری جدید برای ضبط بسته در سطح کاربر"، https://www.tcpdump.org/papers/bpf-usenix93.pdf
  2. استیون مک کان، "libpcap: یک روش معماری و بهینه سازی برای ضبط بسته"، https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. آموزش بازی IPtable U32.
  5. BPF - بایت کد فراموش شده: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. معرفی ابزار BPF: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. یک نمای کلی ثانویه: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: ظروف و امنیت: seccomp
  11. habr: جداسازی دیمون ها با systemd یا "شما برای این کار به Docker نیاز ندارید!"
  12. پل چینیون، "strace --seccomp-bpf: نگاهی به زیر کاپوت"، https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. netsniff-ng: http://netsniff-ng.org/

منبع: www.habr.com

اضافه کردن نظر