BPF للصغار، الجزء صفر: BPF الكلاسيكي

تعد Berkeley Packet Filters (BPF) إحدى تقنيات Linux kernel التي ظهرت على الصفحات الأولى للمنشورات التقنية باللغة الإنجليزية لعدة سنوات حتى الآن. تمتلئ المؤتمرات بتقارير حول استخدام وتطوير BPF. ديفيد ميلر، مشرف النظام الفرعي لشبكة Linux، يلقي كلمته في Linux Plumbers 2018 "هذا الحديث ليس عن XDP" (XDP هي حالة استخدام واحدة لـ BPF). بريندان جريج يعطي محادثات بعنوان القوى العظمى لينكس BPF. توك هويلاند-يورجنسن يضحكأن النواة أصبحت الآن نواة صغيرة. يروج توماس غراف لفكرة ذلك BPF هو جافا سكريبت للنواة.

لا يوجد حتى الآن وصف منهجي لـ BPF على حبري، وبالتالي سأحاول في سلسلة من المقالات التحدث عن تاريخ التكنولوجيا، ووصف أدوات الهندسة والتطوير، وتحديد مجالات التطبيق والممارسة لاستخدام BPF. تحكي هذه المقالة، صفر، في السلسلة، تاريخ وهندسة BPF الكلاسيكية، وتكشف أيضًا أسرار مبادئ تشغيلها. tcpdump, seccomp, strace، وأكثر بكثير.

يتم التحكم في تطوير BPF من خلال مجتمع شبكات Linux، وترتبط التطبيقات الرئيسية الحالية لـ BPF بالشبكات، وبالتالي، بإذن @eucariot، أطلقت على المسلسل اسم "BPF للصغار"، تكريماً للمسلسل العظيم "شبكات للصغار".

دورة قصيرة في تاريخ BPF(c)

تقنية BPF الحديثة هي نسخة محسنة وموسعة من التقنية القديمة التي تحمل نفس الاسم، والتي تسمى الآن BPF الكلاسيكية لتجنب الالتباس. تم إنشاء أداة مساعدة معروفة بناءً على BPF الكلاسيكي tcpdump، آلية seccomp، بالإضافة إلى وحدات أقل شهرة xt_bpf إلى iptables والمصنف cls_bpf. في Linux الحديث، تتم ترجمة برامج BPF الكلاسيكية تلقائيًا إلى النموذج الجديد، ومع ذلك، من وجهة نظر المستخدم، ظلت واجهة برمجة التطبيقات (API) في مكانها ولا تزال هناك استخدامات جديدة لـ BPF الكلاسيكي، كما سنرى في هذه المقالة. لهذا السبب، وأيضًا لأنه من خلال متابعة تاريخ تطور BPF الكلاسيكي في Linux، سيصبح أكثر وضوحًا كيف ولماذا تطور إلى شكله الحديث، قررت أن أبدأ بمقالة حول BPF الكلاسيكي.

في نهاية الثمانينات من القرن الماضي، أصبح المهندسون من مختبر لورانس بيركلي الشهير مهتمين بمسألة كيفية تصفية حزم الشبكة بشكل صحيح على الأجهزة الحديثة في أواخر الثمانينات من القرن الماضي. كانت الفكرة الأساسية للتصفية، التي تم تنفيذها في الأصل باستخدام تقنية CSPF (CMU/Stanford Packet Filter)، هي تصفية الحزم غير الضرورية في أقرب وقت ممكن، أي. في مساحة النواة، لأن هذا يتجنب نسخ البيانات غير الضرورية إلى مساحة المستخدم. لتوفير أمان وقت التشغيل لتشغيل تعليمات برمجية للمستخدم في مساحة kernel، تم استخدام جهاز افتراضي معزول.

ومع ذلك، تم تصميم الأجهزة الافتراضية للمرشحات الموجودة لتعمل على الأجهزة المستندة إلى المكدس ولم تعمل بكفاءة على أجهزة RISC الأحدث. نتيجة لذلك، من خلال جهود المهندسين من Berkeley Labs، تم تطوير تقنية BPF (مرشحات حزم Berkeley) الجديدة، والتي تم تصميم بنية الآلة الافتراضية الخاصة بها بناءً على معالج Motorola 6502 - العمود الفقري لمثل هذه المنتجات المعروفة مثل أبل الثاني أو NES. أدى الجهاز الظاهري الجديد إلى زيادة أداء المرشح عشرات المرات مقارنة بالحلول الحالية.

بنية آلة BPF

سوف نتعرف على الهندسة المعمارية بطريقة عملية، ونحلل الأمثلة. ومع ذلك، في البداية، لنفترض أن الجهاز يحتوي على سجلين 32 بت يمكن للمستخدم الوصول إليهما، وهو مجمع A وتسجيل الفهرس X، 64 بايت من الذاكرة (16 كلمة)، متاحة للكتابة والقراءة اللاحقة، ونظام أوامر صغير للعمل مع هذه الكائنات. كانت تعليمات الانتقال السريع لتنفيذ التعبيرات الشرطية متاحة أيضًا في البرامج، ولكن لضمان إكمال البرنامج في الوقت المناسب، لا يمكن إجراء القفزات إلا للأمام، أي، على وجه الخصوص، تم منع إنشاء الحلقات.

المخطط العام لبدء تشغيل الجهاز هو كما يلي. يقوم المستخدم بإنشاء برنامج لبنية BPF واستخدامه بعض تقوم آلية kernel (مثل استدعاء النظام) بتحميل البرنامج وتوصيله لبعض إلى منشئ الأحداث في النواة (على سبيل المثال، الحدث هو وصول الحزمة التالية إلى بطاقة الشبكة). عند وقوع حدث ما، تقوم النواة بتشغيل البرنامج (على سبيل المثال، في مترجم)، وتتوافق ذاكرة الجهاز مع لبعض منطقة ذاكرة kernel (على سبيل المثال، بيانات الحزمة الواردة).

ما ورد أعلاه سيكون كافيًا بالنسبة لنا للبدء في النظر إلى الأمثلة: سوف نتعرف على تنسيق النظام والأوامر حسب الضرورة. إذا كنت ترغب في دراسة نظام أوامر الجهاز الظاهري على الفور والتعرف على جميع إمكانياته، فيمكنك قراءة المقالة الأصلية عامل تصفية حزمة BSD و/أو النصف الأول من الملف التوثيق/الشبكات/filter.txt من وثائق النواة. وبالإضافة إلى ذلك، يمكنك دراسة العرض التقديمي libpcap: منهجية الهندسة المعمارية والتحسين لالتقاط الحزم، حيث يتحدث ماكان، أحد مؤلفي BPF، عن تاريخ الخلق libpcap.

ننتقل الآن إلى النظر في جميع الأمثلة المهمة لاستخدام BPF الكلاسيكي على Linux: tcpdump (libpcap)، سيكومب، xt_bpf, cls_bpf.

com.tcpdump

تم تطوير BPF بالتوازي مع تطوير الواجهة الأمامية لتصفية الحزم - وهي أداة مساعدة معروفة tcpdump. وبما أن هذا هو المثال الأقدم والأكثر شهرة لاستخدام BPF الكلاسيكي، والمتوفر على العديد من أنظمة التشغيل، فسوف نبدأ دراستنا للتكنولوجيا معه.

(لقد قمت بتشغيل كافة الأمثلة الواردة في هذه المقالة على نظام التشغيل Linux 5.6.0-rc6. تم تحرير مخرجات بعض الأوامر لتحسين إمكانية القراءة.)

مثال: مراقبة حزم IPv6

لنتخيل أننا نريد إلقاء نظرة على جميع حزم IPv6 الموجودة على الواجهة eth0. للقيام بذلك يمكننا تشغيل البرنامج tcpdump مع مرشح بسيط ip6:

$ sudo tcpdump -i eth0 ip6

في هذه الحالة، tcpdump يجمع المرشح ip6 في الرمز الثانوي لهندسة BPF وأرسله إلى النواة (راجع التفاصيل في القسم تكبدمب: جاري التحميل). سيتم تشغيل عامل التصفية المحمل لكل حزمة تمر عبر الواجهة 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]، والتي تعني "تحميل في التسجيل". A نصف كلمة (16 بت) موجودة في العنوان 12 والسؤال الوحيد هو ما نوع الذاكرة التي نتعامل معها؟ الجواب هو عند x يبدأ (x+1)البايت الرابع من حزمة الشبكة التي تم تحليلها. نقرأ الحزم من واجهة Ethernet eth0و هذا وسائلأن الحزمة تبدو هكذا (للتبسيط، نفترض أنه لا توجد علامات VLAN في الحزمة):

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

وذلك بعد تنفيذ الأمر ldh [12] في السجل A سيكون هناك حقل Ether Type - نوع الحزمة المرسلة في إطار Ethernet هذا. في السطر 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 = 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 بايت مشغولة برأس Ethernet، و X يحتوي على طول رأس IPv4، ونحن نفهم ذلك A تم تحميل منفذ وجهة TCP:

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

أخيرًا، في السطر 8، نقارن المنفذ الوجهة بالقيمة المطلوبة، وفي السطر 9 أو 10 نعيد النتيجة - ما إذا كان سيتم نسخ الحزمة أم لا.

تكبدمب: جاري التحميل

في الأمثلة السابقة، لم نتناول بالتفصيل على وجه التحديد كيفية تحميل كود 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
...

في أول سطرين من الإخراج نقوم بإنشاء المقبس الخام لقراءة جميع إطارات Ethernet وربطها بالواجهة 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 (إسقاط جميع الحزم)، يقوم بتبديل المقبس إلى وضع عدم الحظر ويحاول طرح جميع الحزم التي يمكن أن تبقى من عوامل التصفية السابقة.

في المجمل، لتصفية الحزم على Linux باستخدام BPF الكلاسيكي، يجب أن يكون لديك مرشح على شكل بنية مثل struct sock_fprog ومقبس مفتوح، وبعد ذلك يمكن توصيل الفلتر بالمقبس باستخدام استدعاء النظام setsockopt.

ومن المثير للاهتمام أن الفلتر يمكن توصيله بأي مقبس، وليس فقط الخام. هنا مثال برنامج يقوم بقطع كافة وحدات البايت باستثناء أول وحدتين من كافة مخططات بيانات UDP الواردة. (لقد أضفت تعليقات في الكود حتى لا يحدث فوضى في المقالة.)

مزيد من التفاصيل حول الاستخدام setsockopt لتوصيل المرشحات، انظر المقبس(7)، ولكن حول كتابة المرشحات الخاصة بك مثل struct sock_fprog دون مساعدة tcpdump سنتحدث في القسم برمجة BPF بأيدينا.

BPF الكلاسيكية والقرن الحادي والعشرين

تم تضمين BPF في Linux في عام 1997 وظل بمثابة العمود الفقري لفترة طويلة libpcap دون أي تغييرات خاصة (التغييرات الخاصة بنظام التشغيل Linux، بالطبع، كان عليهلكنهم لم يغيروا الصورة العالمية). جاءت أولى العلامات الجادة على تطور BPF في عام 2011، عندما اقترح إريك دومازيت ذلك رقعة قماشية، الذي يضيف برنامج Just In Time Compiler إلى النواة - وهو مترجم لتحويل كود بايت BPF إلى كود أصلي x86_64 شفرة.

كان مترجم JIT هو الأول في سلسلة التغييرات: في عام 2012 ظهر القدرة على كتابة المرشحات ل سيكومب، باستخدام 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))

إن كتابة البرامج على شكل رموز الآلة ليست مريحة للغاية، ولكنها في بعض الأحيان تكون ضرورية (على سبيل المثال، لتصحيح الأخطاء، وإنشاء اختبارات الوحدة، وكتابة المقالات عن حبري، وما إلى ذلك). للراحة، في الملف <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),
}

ومع ذلك، فإن هذا الخيار ليس مريحا للغاية. هذا ما استنتجه مبرمجو Linux kernel، وبالتالي في الدليل tools/bpf kernels، يمكنك العثور على مُجمِّع ومصحح أخطاء للعمل مع 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كما فعلنا في بداية هذا القسم.

ملحقات Linux وnetsniff-ng

بالإضافة إلى BPF القياسية، Linux و tools/bpf/bpf_asm دعم و مجموعة غير قياسية. في الأساس، يتم استخدام التعليمات للوصول إلى حقول البنية struct sk_buff، الذي يصف حزمة الشبكة في النواة. ومع ذلك، هناك أيضًا أنواع أخرى من التعليمات المساعدة، على سبيل المثال ldw cpu سيتم تحميله في السجل A نتيجة تشغيل وظيفة النواة raw_smp_processor_id(). (في الإصدار الجديد من BPF، تم توسيع هذه الامتدادات غير القياسية لتزويد البرامج بمجموعة من مساعدات kernel للوصول إلى الذاكرة والهياكل وتوليد الأحداث.) فيما يلي مثال مثير للاهتمام لمرشح نقوم فيه بنسخ فقط رؤوس الحزمة في مساحة المستخدم باستخدام الامتداد poffإزاحة الحمولة:

ld poff
ret a

لا يمكن استخدام ملحقات BPF في tcpdumpولكن هذا سبب وجيه للتعرف على حزمة المرافق netsniff-ng، والذي يحتوي، من بين أمور أخرى، على برنامج متقدم netsniff-ng، والذي، بالإضافة إلى التصفية باستخدام BPF، يحتوي أيضًا على مولد حركة مرور فعال، وأكثر تقدمًا من tools/bpf/bpf_asm، تم استدعاء مجمع BPF bpfc. تحتوي الحزمة على وثائق مفصلة تمامًا، راجع أيضًا الروابط الموجودة في نهاية المقالة.

سيكومب

لذلك، نحن نعرف بالفعل كيفية كتابة برامج BPF ذات التعقيد التعسفي ومستعدون للنظر في أمثلة جديدة، أولها تقنية seccomp، والتي تسمح، باستخدام مرشحات BPF، بإدارة مجموعة ومجموعة وسائط استدعاء النظام المتاحة لـ عملية معينة وأحفادها.

تمت إضافة الإصدار الأول من seccomp إلى النواة في عام 2005 ولم يحظ بشعبية كبيرة، لأنه قدم خيارًا واحدًا فقط - لقصر مجموعة استدعاءات النظام المتاحة لعملية ما على ما يلي: read, write, exit и sigreturn، وتم قتل العملية التي انتهكت القواعد باستخدام SIGKILL. ومع ذلك، في عام 2012، أضافت seccomp القدرة على استخدام مرشحات BPF، مما يسمح لك بتحديد مجموعة من مكالمات النظام المسموح بها وحتى إجراء عمليات فحص على وسيطاتها. (ومن المثير للاهتمام أن Chrome كان من أوائل مستخدمي هذه الوظيفة، ويعمل موظفو Chrome حاليًا على تطوير آلية KRSI استنادًا إلى إصدار جديد من BPF والسماح بتخصيص وحدات أمان Linux.) يمكن العثور على روابط لوثائق إضافية في النهاية من هذه المادة.

لاحظ أن هناك بالفعل مقالات على المركز حول استخدام 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، أي. برنامج بي بي إف.

كيف تختلف برامج 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() حبات. (الشيء المضحك هو أنه في الالتزام الأصلي الذي أضاف وظيفة seccomp، نسوا إضافة إذن لاستخدام التعليمات لهذه الوظيفة mod (القسم المتبقي) وهو الآن غير متاح لبرامج seccomp BPF، منذ إضافته سأكسر واجهة برمجة التطبيقات.)

في الأساس، نحن نعرف بالفعل كل شيء لكتابة وقراءة برامج 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 هي أداة لا غنى عنها لدراسة سلوك العمليات على نظام Linux. ومع ذلك، فقد سمع الكثيرون عنها أيضًا مشاكل الأداء عند استخدام هذه الأداة المساعدة. الحقيقة انه 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 هناك نوعان من القيود. أولاً، لن يكون من الممكن الانضمام إلى عملية موجودة بالفعل (option -p برنامج strace)، نظرًا لأن هذا غير مدعوم بواسطة seccomp. ثانيا، ليس هناك أي احتمال لا انظر إلى العمليات الفرعية، نظرًا لأن مرشحات 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 غير متساوٍ 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.

cls_bpf

المثال الأخير لاستخدام BPF الكلاسيكي في النواة هو المصنف cls_bpf للنظام الفرعي للتحكم في حركة المرور في Linux، تمت إضافته إلى Linux في نهاية عام 2013 ليحل محل النظام القديم من الناحية النظرية cls_u32.

ومع ذلك، فإننا لن تصف العمل الآن cls_bpf، نظرًا لأنه من وجهة نظر المعرفة حول BPF الكلاسيكي، فإن هذا لن يمنحنا أي شيء - لقد أصبحنا بالفعل على دراية بجميع الوظائف. بالإضافة إلى ذلك، في المقالات اللاحقة التي تتحدث عن Extended BPF، سنلتقي بهذا المصنف أكثر من مرة.

سبب آخر لعدم الحديث عن استخدام BPF الكلاسيكي ج cls_bpf المشكلة هي أنه بالمقارنة مع Extended BPF، فإن نطاق التطبيق في هذه الحالة يضيق بشكل جذري: لا يمكن للبرامج الكلاسيكية تغيير محتويات الحزم ولا يمكنها حفظ الحالة بين المكالمات.

لذا فقد حان الوقت لنقول وداعًا لـ BPF الكلاسيكي ونتطلع إلى المستقبل.

وداعًا لـ BPF الكلاسيكي

لقد نظرنا في كيفية نجاح تقنية BPF، التي تم تطويرها في أوائل التسعينيات، لمدة ربع قرن وحتى النهاية وجدت تطبيقات جديدة. ومع ذلك، على غرار الانتقال من أجهزة المكدس إلى RISC، والذي كان بمثابة قوة دافعة لتطوير BPF الكلاسيكي، في العقد الأول من القرن الحادي والعشرين، كان هناك انتقال من أجهزة 32 بت إلى 64 بت وبدأ BPF الكلاسيكي في التقادم. بالإضافة إلى ذلك، فإن إمكانيات BPF الكلاسيكية محدودة للغاية، بالإضافة إلى البنية القديمة - ليس لدينا القدرة على حفظ الحالة بين المكالمات إلى برامج BPF، ولا توجد إمكانية للتفاعل المباشر مع المستخدم، ولا توجد إمكانية للتفاعل مع النواة، باستثناء قراءة عدد محدود من حقول البنية sk_buff وتشغيل أبسط الوظائف المساعدة، لا يمكنك تغيير محتويات الحزم وإعادة توجيهها.

في الواقع، كل ما تبقى حاليًا من BPF الكلاسيكي في Linux هو واجهة API، وداخل النواة، تتم ترجمة جميع البرامج الكلاسيكية، سواء كانت مرشحات المقبس أو مرشحات seccomp، تلقائيًا إلى تنسيق جديد، Extended BPF. (سنتحدث عن كيفية حدوث ذلك بالضبط في المقالة التالية.)

بدأ الانتقال إلى بنية جديدة في عام 2013، عندما اقترح أليكسي ستاروفويتوف مخطط تحديث BPF. في عام 2014 بقع المقابلة بدأت تظهر في القلب. بقدر ما أفهم، كانت الخطة الأولية تهدف فقط إلى تحسين البنية ومترجم JIT للعمل بشكل أكثر كفاءة على أجهزة 64 بت، ولكن بدلاً من ذلك كانت هذه التحسينات بمثابة بداية فصل جديد في تطوير Linux.

ستغطي المقالات الإضافية في هذه السلسلة بنية وتطبيقات التكنولوجيا الجديدة، التي كانت تُعرف في البداية باسم 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. حبر: الحاويات والأمن: 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

إضافة تعليق