Scroll to navigation

bpf(2) System Calls Manual bpf(2)

الاسم

bpf - تنفيذ أمر على برنامج أو خريطة BPF ممتدة

موجز

#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);

الوصف

يُنفذ استدعاء النظام bpf() مجموعة من العمليات المتعلقة بمرشحات حزم بيركلي الممتدة (Berkeley Packet Filters). إن BPF الممتد (أو eBPF) مشابه لـ BPF الأصلي ("التقليدي" أو cBPF) المستخدم لترشيح حزم الشبكة. وبالنسبة لبرامج cBPF و eBPF معًا، تحلل النواة البرامج سكونيًا قبل تحميلها، وذلك لضمان عدم إلحاقها الضرر بالنظام المشغل.

يوسع eBPF نظام cBPF بطرق متعددة، بما في ذلك القدرة على استدعاء مجموعة ثابتة من الدوال المساعدة داخل النواة (عبر امتداد شفرة العملية BPF_CALL الذي يوفره eBPF) والوصول إلى هياكل البيانات المشتركة مثل خرائط eBPF.

تصميم/معمارية BPF الممتد

خرائط eBPF هي هيكل بيانات عام لتخزين أنواع مختلفة من البيانات. تُعامل أنواع البيانات عمومًا ككتل ثنائية، لذا يحدد المستخدم حجم المفتاح وحجم القيمة عند وقت إنشاء الخريطة فقط. بمعنى آخر، يمكن أن يكون للمفتاح/القيمة في خريطة معينة هيكل تعسفي.

يمكن لعملية المستخدم إنشاء خرائط متعددة (تكون فيها أزواج المفتاح/القيمة عبارة عن بايتات بيانات مبهمة) والوصول إليها عبر واصفات الملفات. يمكن لبرامج eBPF المختلفة الوصول إلى الخرائط نفسها بالتوازي. ويعود الأمر لعملية المستخدم وبرنامج eBPF لتقرير ما سيخزن داخل الخرائط.

ثمة نوع خاص من الخرائط يسمى مصفوفة البرامج. يخزن هذا النوع من الخرائط واصفات ملفات تشير إلى برامج eBPF أخرى. عند إجراء بحث في الخريطة، يُعاد توجيه تدفق البرنامج في مكانه إلى بداية برنامج eBPF آخر ولا يعود إلى البرنامج المستدعِي. لمستوى التعشيش حد ثابت هو 32، حتى لا يمكن صياغة حلقات لانهائية. في وقت التشغيل، يمكن تعديل واصفات ملفات البرامج المخزنة في الخريطة، وبذلك يمكن تغيير وظائف البرنامج بناءً على متطلبات محددة. يجب أن تكون جميع البرامج المشار إليها في خريطة مصفوفة البرامج قد حُمّلت مسبقًا في النواة عبر bpf(). إذا فشل البحث في الخريطة، يكمل البرنامج الحالي تنفيذه. انظر BPF_MAP_TYPE_PROG_ARRAY أدناه لمزيد من التفاصيل.

عمومًا، تُحمّل برامج eBPF بواسطة عملية المستخدم وتُفرّغ آليًا عند خروج العملية. في بعض الحالات، مثل tc-bpf(8)، سيستمر البرنامج حيًا داخل النواة حتى بعد خروج العملية التي حمّلت البرنامج. في هذه الحالة، يحتفظ نظام tc الفرعي بمرجع لبرنامج eBPF بعد أن يُغلق واصف الملف بواسطة برنامج مساحة المستخدم. وبالتالي، فإن بقاء برنامج معين داخل النواة يعتمد على كيفية ربطه بنظام فرعي معين للنواة بعد تحميله عبر bpf().

كل برنامج eBPF هو مجموعة من التعليمات الآمن تشغيلها حتى اكتمالها. يحدد مدقق داخل النواة سكونيًا أن برنامج eBPF ينتهي وأنه آمن للتنفيذ. أثناء التحقق، تزيد النواة عدادات المراجع لكل خريطة يستخدمها برنامج eBPF، بحيث لا يمكن إزالة الخرائط المتصلة حتى يُفرّغ البرنامج.

يمكن ربط برامج eBPF بأحداث مختلفة. قد تكون هذه الأحداث وصول حزم الشبكة، أو أحداث التتبع، أو أحداث التصنيف بواسطة ضوابط طوابير الشبكة (لبرامج eBPF المربوطة بمصنف tc(8))، وأنواع أخرى قد تُضاف مستقبلاً. يقدح الحدث الجديد تنفيذ برنامج eBPF، والذي قد يخزن معلومات عن الحدث في خرائط eBPF. وإلى جانب تخزين البيانات، قد تستدعي برامج eBPF مجموعة ثابتة من الدوال المساعدة داخل النواة.

يمكن ربط برنامج eBPF نفسه بأحداث متعددة، ويمكن لبرامج eBPF مختلفة الوصول إلى الخريطة ذاتها:


tracing     tracing    tracing    packet      packet     packet
event A     event B    event C    on eth0     on eth1    on eth2

| | | | | ^
| | | | v |
--> tracing <-- tracing socket tc ingress tc egress
prog_1 prog_2 prog_3 classifier action
| | | | prog_4 prog_5
|--- -----| |------| map_3 | |
map_1 map_2 --| map_4 |--

المعطيات

تُحدد العملية المطلوب تنفيذها بواسطة استدعاء النظام bpf() عبر المعطى cmd. تأخذ كل عملية معطى مصاحبًا يُوفر عبر attr، وهو مؤشر إلى اتحاد (union) من النوع bpf_attr (انظر أدناه). يجب تصفير الحقول غير المستخدمة والحشوات (padding) قبل الاستدعاء. المعطى size هو حجم الاتحاد الذي يشير إليه attr.

القيمة المقدمة في cmd هي واحدة مما يلي:

إنشاء خريطة وإرجاع واصف ملف يشير إليها. يُفعّل علم واصف ملف الإغلاق عند التنفيذ (انظر fcntl(2)) آليًا لواصف الملف الجديد.
البحث عن عنصر بواسطة المفتاح في خريطة محددة وإرجاع قيمته.
إنشاء عنصر أو تحديثه (زوج مفتاح/قيمة) في خريطة محددة.
البحث عن عنصر بواسطة المفتاح وحذفه في خريطة محددة.
البحث عن عنصر بواسطة المفتاح في خريطة محددة وإرجاع مفتاح العنصر التالي.
التحقق من برنامج eBPF وتحميله، مع إرجاع واصف ملف جديد مرتبط بالبرنامج. يُفعّل علم واصف ملف الإغلاق عند التنفيذ (انظر fcntl(2)) آليًا لواصف الملف الجديد.
يتكون اتحاد bpf_attr من هياكل مجهولة مختلفة تُستخدم بواسطة أوامر bpf() المختلفة:


union bpf_attr {

struct { /* تُستخدم بواسطة BPF_MAP_CREATE */
__u32 map_type;
__u32 key_size; /* حجم المفتاح بالبايت */
__u32 value_size; /* حجم القيمة بالبايت */
__u32 max_entries; /* أقصى عدد من المدخلات
في الخريطة */
};
struct { /* تُستخدم بواسطة أوامر BPF_MAP_*_ELEM و BPF_MAP_GET_NEXT_KEY */
__u32 map_fd;
__aligned_u64 key;
union {
__aligned_u64 value;
__aligned_u64 next_key;
};
__u64 flags;
};
struct { /* تُستخدم بواسطة BPF_PROG_LOAD */
__u32 prog_type;
__u32 insn_cnt;
__aligned_u64 insns; /* 'const struct bpf_insn *' */
__aligned_u64 license; /* 'const char *' */
__u32 log_level; /* مستوى إسهاب المدقق */
__u32 log_size; /* حجم مخزن المستخدم المؤقت */
__aligned_u64 log_buf; /* مخزن 'char *' المؤقت
الذي يوفره المستخدم */
__u32 kern_version;
/* يُفحص عندما prog_type=kprobe
(منذ لينكس 4.1) */
}; } __attribute__((aligned(8)));

خرائط eBPF

الخرائط هي هيكل بيانات عام لتخزين أنواع مختلفة من البيانات. وهي تتيح مشاركة البيانات بين برامج eBPF في النواة، وكذلك بين النواة وتطبيقات مساحة المستخدم.

لكل نوع خريطة السمات التالية:

النوع
أقصى عدد من العناصر
حجم المفتاح بالبايت
حجم القيمة بالبايت

توضح الدوال المغلفة التالية كيفية استخدام أوامر bpf() المختلفة للوصول إلى الخرائط. تستخدم الدوال المعطى cmd لاستدعاء عمليات مختلفة.

يُنشئ الأمر BPF_MAP_CREATE خريطة جديدة، ويُرجع واصف ملف جديد يشير إلى الخريطة.

int
bpf_create_map(enum bpf_map_type map_type,

unsigned int key_size,
unsigned int value_size,
unsigned int max_entries) {
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); }

للخريطة الجديدة النوع المحدد بواسطة map_type، والسمات المحددة في key_size و value_size و max_entries. عند النجاح، تُرجع هذه العملية واصف ملف. عند حدوث خطأ، يُرجع -1 ويُضبط errno على EINVAL أو EPERM أو ENOMEM.
ستُستخدم سمتا key_size و value_size بواسطة المدقق أثناء تحميل البرنامج للتحقق من أن البرنامج يستدعي الدوال المساعدة bpf_map_*_elem() مع key مُهيأ بشكل صحيح وللتحقق من أن البرنامج لا يصل إلى عنصر الخريطة value بما يتجاوز value_size المحدد. على سبيل المثال، عندما تُنشأ خريطة بـ key_size قدره 8 ويستدعي برنامج eBPF

bpf_map_lookup_elem(map_fd, fp - 4)
    

سيُرفض البرنامج، لأن الدالة المساعدة داخل النواة

bpf_map_lookup_elem(map_fd, void *key)
    

تتوقع قراءة 8 بايت من الموقع الذي يشير إليه key، لكن عنوان البدء fp - 4 (حيث fp هو قمة المكدس) سيؤدي إلى وصول للمكدس خارج الحدود.
وبالمثل، عندما تُنشأ خريطة بـ value_size قدره 1 ويحتوي برنامج eBPF على

value = bpf_map_lookup_elem(...);
*(u32 *) value = 1;
    

سيُرفض البرنامج، لأنه يصل إلى مؤشر value بما يتجاوز حد 1 بايت لـ value_size المحدد.
حاليًا، القيم التالية مدعومة لـ map_type:

enum bpf_map_type {

BPF_MAP_TYPE_UNSPEC, /* حجز 0 كنوع خريطة غير صالح */
BPF_MAP_TYPE_HASH,
BPF_MAP_TYPE_ARRAY,
BPF_MAP_TYPE_PROG_ARRAY,
BPF_MAP_TYPE_PERF_EVENT_ARRAY,
BPF_MAP_TYPE_PERCPU_HASH,
BPF_MAP_TYPE_PERCPU_ARRAY,
BPF_MAP_TYPE_STACK_TRACE,
BPF_MAP_TYPE_CGROUP_ARRAY,
BPF_MAP_TYPE_LRU_HASH,
BPF_MAP_TYPE_LRU_PERCPU_HASH,
BPF_MAP_TYPE_LPM_TRIE,
BPF_MAP_TYPE_ARRAY_OF_MAPS,
BPF_MAP_TYPE_HASH_OF_MAPS,
BPF_MAP_TYPE_DEVMAP,
BPF_MAP_TYPE_SOCKMAP,
BPF_MAP_TYPE_CPUMAP,
BPF_MAP_TYPE_XSKMAP,
BPF_MAP_TYPE_SOCKHASH,
BPF_MAP_TYPE_CGROUP_STORAGE,
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
BPF_MAP_TYPE_QUEUE,
BPF_MAP_TYPE_STACK,
/* انظر /usr/include/linux/bpf.h للقائمة الكاملة. */ };

يختار map_type أحد تطبيقات الخرائط المتاحة في النواة. بالنسبة لجميع أنواع الخرائط، تصل برامج eBPF إلى الخرائط باستخدام نفس الدوال المساعدة bpf_map_lookup_elem() و bpf_map_update_elem(). ترد مزيد من التفاصيل حول أنواع الخرائط المختلفة أدناه.
يبحث الأمر BPF_MAP_LOOKUP_ELEM عن عنصر بـ key معين في الخريطة التي يشير إليها واصف الملف fd.

int
bpf_lookup_elem(int fd, const void *key, void *value)
{

union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); }

إذا عُثر على عنصر، تُرجع العملية صفرًا وتخزن قيمة العنصر في value، والذي يجب أن يشير إلى مخزن مؤقت بحجم value_size بايت.
إذا لم يُعثر على أي عنصر، تُرجع العملية -1 وتُضبط errno على ENOENT.
يُنشئ الأمر BPF_MAP_UPDATE_ELEM عنصرًا أو يحدثه بـ key/value معطيين في الخريطة التي يشير إليها واصف الملف fd.

int
bpf_update_elem(int fd, const void *key, const void *value,

uint64_t flags) {
union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
.flags = flags,
};
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); }

يجب تحديد المعطى flags كواحد مما يلي:
إنشاء عنصر جديد أو تحديث عنصر موجود.
أنشئ عنصراً جديداً فقط إذا لم يكن موجوداً.
حدّث عنصراً موجوداً بالفعل.
عند النجاح، تُعيد العملية صفراً. عند حدوث خطأ، يُعاد -1 ويُضبط errno على EINVAL، أو EPERM، أو ENOMEM، أو E2BIG. تشير E2BIG إلى أن عدد العناصر في الخريطة قد وصل إلى حد max_entries المحدد وقت إنشاء الخريطة. تُعاد EEXIST إذا حددت flags القيمة BPF_NOEXIST وكان العنصر ذو المفتاح key موجوداً بالفعل في الخريطة. وتُعاد ENOENT إذا حددت flags القيمة BPF_EXIST وكان العنصر ذو المفتاح key غير موجود في الخريطة.
يحذف الأمر BPF_MAP_DELETE_ELEM العنصر الذي مفتاحه key من الخريطة المشار إليها بواسطة واصف الملف fd.

int
bpf_delete_elem(int fd, const void *key)
{

union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
};
return bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); }

عند النجاح، يُعاد صفر. إذا لم يُعثر على العنصر، يُعاد -1 ويُضبط errno على ENOENT.
يبحث الأمر BPF_MAP_GET_NEXT_KEY عن عنصر بواسطة key في الخريطة المشار إليها بواسطة واصف الملف fd ويضبط مؤشر next_key على مفتاح العنصر التالي.

int
bpf_get_next_key(int fd, const void *key, void *next_key)
{

union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.next_key = ptr_to_u64(next_key),
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); }

إذا وُجد key، تُعيد العملية صفراً وتضبط مؤشر next_key على مفتاح العنصر التالي. إذا لم يُعثر على key، تُعيد العملية صفراً وتضبط مؤشر next_key على مفتاح العنصر الأول. إذا كان key هو العنصر الأخير، يُعاد -1 ويُضبط errno على ENOENT. قيم errno المحتملة الأخرى هي ENOMEM و EFAULT و EPERM و EINVAL. يمكن استخدام هذا الأسلوب للتكرار عبر جميع العناصر في الخريطة.
احذف الخريطة المشار إليها بواسطة واصف الملف map_fd. عندما يخرج برنامج مساحة المستخدم الذي أنشأ الخريطة، ستُحذف جميع الخرائط آلياً (لكن انظر الملاحظات NOTES).

أنواع خرائط eBPF

أنواع الخرائط التالية مدعومة:

تتميز خرائط جدول التجزئة (Hash-table) بالخصائص التالية:
تُنشأ الخرائط وتُدمر بواسطة برامج مساحة المستخدم. يمكن لكل من برامج مساحة المستخدم وبرامج eBPF إجراء عمليات البحث والتحديث والحذف.
تتولى النواة مسؤولية تخصيص وتحرير أزواج المفتاح/القيمة.
سيفشل المساعد map_update_elem() في إدراج عنصر جديد عند الوصول إلى حد max_entries. (يضمن هذا عدم استنفاد برامج eBPF للذاكرة.)
يستبدل map_update_elem() العناصر الموجودة بشكل ذري.
خُصصت خرائط جدول التجزئة لتكون محسنة لسرعة البحث.
تتميز خرائط المصفوفة (Array) بالخصائص التالية:
خُصصت لتكون محسنة لأسرع بحث ممكن. في المستقبل، قد يتعرف المدقق أو مصرف JIT على عمليات lookup() التي تستخدم مفتاحاً ثابتاً ويحسنها إلى مؤشر ثابت. من الممكن أيضًا تحسين المفتاح غير الثابت إلى حسابات مؤشرات مباشرة، بما أن المؤشرات و value_size ثابتة طوال عمر برنامج eBPF. بمعنى آخر، قد يُضمن array_map_lookup_elem() داخلياً بواسطة المدقق أو مصرف JIT مع الحفاظ على الوصول المتزامن لهذه الخريطة من مساحة المستخدم.
جميع عناصر المصفوفة مخصصة مسبقاً ومصفرة عند التهيئة.
المفتاح هو فهرس مصفوفة، ويجب أن يكون أربعة بايتات بالضبط.
يفشل map_delete_elem() بالخطأ EINVAL، حيث لا يمكن حذف العناصر.
يستبدل map_update_elem() العناصر بطريقة غير ذرية (nonatomic)؛ وللتحديثات الذرية، يجب استخدام خريطة جدول تجزئة بدلاً من ذلك. ومع ذلك، توجد حالة خاصة واحدة يمكن استخدامها أيضًا مع المصفوفات: يمكن استخدام البنية الذرية المدمجة __sync_fetch_and_add() على العدادات الذرية ذات 32 و 64 بت. على سبيل المثال، يمكن تطبيقها على القيمة بأكملها إذا كانت تمثل عداداً واحداً، أو في حالة وجود بنية تحتوي على عدادات متعددة، يمكن استخدامها على عدادات فردية. وهذا مفيد غالباً لتجميع وحساب الأحداث.
من بين استخدامات خرائط المصفوفة ما يلي:
كمتغيرات eBPF "عالمية": مصفوفة من عنصر واحد مفتاحها هو (الفهرس) 0 وتكون القيمة فيها عبارة عن مجموعة من المتغيرات 'العالمية' التي يمكن لبرامج eBPF استخدامها للحفاظ على الحالة بين الأحداث.
تجميع أحداث التتبع في مجموعة ثابتة من السلال (buckets).
حساب أحداث الشبكة، مثل عدد الحزم وأحجام الحزم.
خريطة مصفوفة البرامج هي نوع خاص من خرائط المصفوفة التي تحتوي قيمها فقط على واصفات ملفات تشير إلى برامج eBPF أخرى. وبالتالي، يجب أن يكون كل من key_size و value_size أربعة بايتات بالضبط. تُستخدم هذه الخريطة بالاقتران مع المساعد bpf_tail_call().
هذا يعني أن برنامج eBPF الذي أُرفقت به خريطة مصفوفة برامج يمكنه الاستدعاء من جانب النواة إلى

void bpf_tail_call(void *context, void *prog_map,

unsigned int index);

وبالتالي يستبدل تدفق البرنامج الخاص به بالتدفق الموجود في البرنامج عند فتحة مصفوفة البرامج المعطاة، إن وجد. يمكن اعتبار ذلك نوعاً من جدول القفز إلى برنامج eBPF مختلف. سيعيد البرنامج المُستدعى استخدام نفس المكدس. عند تنفيذ القفزة إلى البرنامج الجديد، لن يعود إلى البرنامج القديم مرة أخرى.
إذا لم يُعثر على برنامج eBPF عند الفهرس المعطى لمصفوفة البرامج (بسبب عدم احتواء فتحة الخريطة على واصف ملف برنامج صالح، أو لأن فهرس/مفتاح البحث المحدد خارج النطاق، أو تجاوز حد 32 استدعاءً متداخلاً)، يستمر التنفيذ مع برنامج eBPF الحالي. يمكن استخدام ذلك كمسار احتياطي للحالات المبدئية.
تعد خريطة مصفوفة البرامج مفيدة، على سبيل المثال، في التتبع أو الشبكات، للتعامل مع استدعاءات النظام أو البروتوكولات الفردية في برامجها الفرعية الخاصة واستخدام معرفاتها كفهرس خريطة فردي. قد يؤدي هذا النهج إلى فوائد في الأداء، كما يجعل من الممكن التغلب على الحد الأقصى للتعليمات لبرنامج eBPF واحد. في البيئات الديناميكية، قد يستبدل عفريت (daemon) مساحة المستخدم بشكل ذري البرامج الفرعية الفردية في وقت التشغيل بإصدارات أحدث لتغيير سلوك البرنامج العام، على سبيل المثال، إذا تغيرت السياسات العالمية.

برامج eBPF

يُستخدم الأمر BPF_PROG_LOAD لتحميل برنامج eBPF في النواة. القيمة المعادة لهذا الأمر هي واصف ملف جديد مرتبط ببرنامج eBPF هذا.


char bpf_log_buf[LOG_BUF_SIZE];
int
bpf_prog_load(enum bpf_prog_type type,

const struct bpf_insn *insns, int insn_cnt,
const char *license) {
union bpf_attr attr = {
.prog_type = type,
.insns = ptr_to_u64(insns),
.insn_cnt = insn_cnt,
.license = ptr_to_u64(license),
.log_buf = ptr_to_u64(bpf_log_buf),
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
return bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); }

يكون prog_type واحداً من أنواع البرامج المتاحة:


enum bpf_prog_type {

BPF_PROG_TYPE_UNSPEC, /* حجز 0 كنوع برنامج
غير صالح */
BPF_PROG_TYPE_SOCKET_FILTER,
BPF_PROG_TYPE_KPROBE,
BPF_PROG_TYPE_SCHED_CLS,
BPF_PROG_TYPE_SCHED_ACT,
BPF_PROG_TYPE_TRACEPOINT,
BPF_PROG_TYPE_XDP,
BPF_PROG_TYPE_PERF_EVENT,
BPF_PROG_TYPE_CGROUP_SKB,
BPF_PROG_TYPE_CGROUP_SOCK,
BPF_PROG_TYPE_LWT_IN,
BPF_PROG_TYPE_LWT_OUT,
BPF_PROG_TYPE_LWT_XMIT,
BPF_PROG_TYPE_SOCK_OPS,
BPF_PROG_TYPE_SK_SKB,
BPF_PROG_TYPE_CGROUP_DEVICE,
BPF_PROG_TYPE_SK_MSG,
BPF_PROG_TYPE_RAW_TRACEPOINT,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
BPF_PROG_TYPE_LWT_SEG6LOCAL,
BPF_PROG_TYPE_LIRC_MODE2,
BPF_PROG_TYPE_SK_REUSEPORT,
BPF_PROG_TYPE_FLOW_DISSECTOR,
/* انظر /usr/include/linux/bpf.h للقائمة الكاملة. */ };

لمزيد من التفاصيل حول أنواع برامج eBPF، انظر أدناه.

تُضبط الحقول المتبقية من bpf_attr على النحو التالي:

insns عبارة عن مصفوفة من تعليمات struct bpf_insn.
insn_cnt هو عدد التعليمات في البرنامج المشار إليه بواسطة insns.
license هي سلسلة الترخيص، والتي يجب أن تكون متوافقة مع GPL لاستدعاء الدوال المساعدة المميزة بـ gpl_only. (قواعد الترخيص هي نفسها المتبعة في وحدات النواة، لذا يمكن أيضًا استخدام التراخيص المزدوجة، مثل "Dual BSD/GPL".)
log_buf هو مؤشر إلى مخزن مؤقت خصصه المستدعِي (caller) حيث يمكن لمدقق داخل النواة تخزين سجل التحقق فيه. هذا السجل عبارة عن سلسلة متعددة الأسطر يمكن لمؤلف البرنامج فحصها لفهم كيف توصل المدقق إلى استنتاج مفاده أن برنامج eBPF غير آمن. قد يتغير تنسيق المخرجات في أي وقت مع تطور المدقق.
log_size هو حجم المخزن المؤقت الذي يشير إليه log_buf. إذا لم يكن حجم المخزن المؤقت كبيراً بما يكفي لتخزين جميع رسائل المدقق، يُعاد -1 ويُضبط errno على ENOSPC.
log_level هو مستوى إسهاب المدقق. تعني القيمة صفر أن المدقق لن يقدم سجلاً؛ في هذه الحالة، يجب أن يكون log_buf مؤشراً فارغاً (null)، ويجب أن يكون log_size صفراً.

يؤدي تطبيق close(2) على واصف الملف المعاد بواسطة BPF_PROG_LOAD إلى إلغاء تحميل برنامج eBPF (ولكن انظر الملاحظات NOTES).

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

أنواع برامج eBPF

يحدد نوع برنامج eBPF (prog_type) المجموعة الفرعية من الدوال المساعدة للنواة التي قد يستدعيها البرنامج. كما يحدد نوع البرنامج مدخلات البرنامج (السياق) — تنسيق struct bpf_context (وهو كتلة البيانات الممررة إلى برنامج eBPF كمعامل أول).

على سبيل المثال، لا يمتلك برنامج التتبع نفس المجموعة الفرعية من الدوال المساعدة التي يمتلكها برنامج مرشح المقبس (على الرغم من أنه قد يكون بينهما بعض المساعدات المشتركة). وبالمثل، فإن المدخلات (السياق) لبرنامج التتبع هي مجموعة من قيم المسجلات، بينما لمرشح المقبس هي حزمة شبكة.

قد تزداد مجموعة الدوال المتاحة لبرامج eBPF من نوع معين في المستقبل.

أنواع البرامج التالية مدعومة:

حالياً، مجموعة الدوال لـ BPF_PROG_TYPE_SOCKET_FILTER هي:

bpf_map_lookup_elem(map_fd, void *key)

/* ابحث عن مفتاح في map_fd */ bpf_map_update_elem(map_fd, void *key, void *value)
/* حدّث المفتاح/القيمة */ bpf_map_delete_elem(map_fd, void *key)
/* احذف مفتاحاً في map_fd */

المعامل bpf_context هو مؤشر إلى struct __sk_buff.
[سيُوثّق]
[سيُوثّق]
[سيُوثّق]

أحداث

بمجرد تحميل البرنامج، يمكن إرفاقه بحدث ما. تتبع الأنظمة الفرعية المختلفة للنواة طرقاً متباينة للقيام بذلك.

بدءًا من لينكس 3.19، سيقوم الاستدعاء التالي بربط البرنامج prog_fd بالمقبس sockfd، الذي أُنشئ عبر استدعاء سابق لـ socket(2):


setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF,

&prog_fd, sizeof(prog_fd));

بدءًا من لينكس 4.1، يمكن استخدام الاستدعاء التالي لربط برنامج eBPF المشار إليه بواسطة واصف الملف prog_fd بواصف ملف حدث perf، event_fd، الذي أُنشئ عبر استدعاء سابق لـ perf_event_open(2):


ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);

قيمة الإرجاع

في حالة الاستدعاء الناجح، تعتمد قيمة الإرجاع على العملية:

واصف الملف الجديد المرتبط بخريطة eBPF.
واصف الملف الجديد المرتبط ببرنامج eBPF.
كل الأوامر الأخرى
صفر.

عند الخطأ، تُعاد القيمة -1، ويُضبط errno للإشارة إلى الخطأ.

الأخطاء

برنامج eBPF كبير جدًا أو وصلت الخريطة إلى حد max_entries (العدد الأقصى للعناصر).
بالنسبة لـ BPF_PROG_LOAD، وعلى الرغم من أن جميع تعليمات البرنامج صالحة، إلا أنه رُفض لأنه اعتبر غير آمن. قد يكون ذلك بسبب وصوله إلى منطقة ذاكرة غير مسموح بها أو مكدس/سجل غير مهيأ، أو لأن قيود الدالة لا تطابق الأنواع الفعلية، أو بسبب وجود وصول غير محاذٍ للذاكرة. في هذه الحالة، يُنصح باستدعاء bpf() مجددًا مع ضبط log_level = 1 وفحص log_buf لمعرفة السبب المحدد الذي قدمه المدقق.
بالنسبة لـ BPF_PROG_LOAD، يشير هذا إلى أن الموارد المطلوبة محجوبة. يحدث هذا عندما يكتشف المدقق إشارات معلقة أثناء فحصه لصلاحية برنامج bpf. في هذه الحالة، ما عليك سوى استدعاء bpf() مجددًا بنفس المعاملات.
fd ليس واصف ملف مفتوح.
أحد المؤشرات (key أو value أو log_buf أو insns) يقع خارج مساحة العناوين التي يمكن الوصول إليها.
القيمة المحددة في cmd غير معروفة لهذه النواة.
بالنسبة لـ BPF_MAP_CREATE، إما map_type أو السمات غير صالحة.
بالنسبة لأوامر BPF_MAP_*_ELEM، بعض حقول union bpf_attr التي لا يستخدمها هذا الأمر لم تُضبط على صفر.
بالنسبة لـ BPF_PROG_LOAD، يشير إلى محاولة تحميل برنامج غير صالح. يمكن اعتبار برامج eBPF غير صالحة بسبب تعليمات غير معروفة، أو استخدام حقول محجوزة، أو قفزات خارج النطاق، أو حلقات لانهائية، أو استدعاء دوال مجهولة.
بالنسبة لأوامر BPF_MAP_LOOKUP_ELEM أو BPF_MAP_DELETE_ELEM، يشير إلى أن العنصر ذا المفتاح key المعطى لم يُعثر عليه.
تعذر تخصيص ذاكرة كافية.
أُجري الاستدعاء دون امتياز كافٍ (بدون قدرة CAP_SYS_ADMIN).

المعايير

لينكس.

التاريخ

لينكس 3.18.

ملاحظات

قبل لينكس 4.4، كانت جميع أوامر bpf() تتطلب أن يمتلك المستدعِي قدرة CAP_SYS_ADMIN. بدءًا من لينكس 4.4 فصاعدًا، قد يقوم مستخدم غير ممتلك للامتيازات بإنشاء برامج محدودة من النوع BPF_PROG_TYPE_SOCKET_FILTER والخرائط المرتبطة بها. ومع ذلك، لا يجوز لهم تخزين مؤشرات النواة داخل الخرائط وهم مقيدون حاليًا بدوال المساعدة التالية:

get_random
get_smp_processor_id
tail_call
ktime_get_ns

يمكن حظر وصول غير ذوي الامتيازات عبر كتابة القيمة 1 إلى الملف /proc/sys/kernel/unprivileged_bpf_disabled.

يمكن مشاركة كائنات eBPF (الخرائط والبرامج) بين العمليات. على سبيل المثال، بعد fork(2)، يرث الابن واصفات الملفات التي تشير إلى نفس كائنات eBPF. بالإضافة إلى ذلك، يمكن نقل واصفات الملفات التي تشير إلى كائنات eBPF عبر مقابس نطاق يونكس. كما يمكن مضاهاة واصفات الملفات التي تشير إلى كائنات eBPF بالطريقة المعتادة، باستخدام dup(2) والاستدعاءات المماثلة. يُحرر تخصيص كائن eBPF فقط بعد إغلاق جميع واصفات الملفات التي تشير إلى ذلك الكائن.

يمكن كتابة برامج eBPF بلغة C مقيدة تُصرَّف (باستخدام مصرِّف clang) إلى رمز بايتات (bytecode) لـ eBPF. حُذفت ميزات متنوعة من لغة C المقيدة هذه، مثل الحلقات، والمتغيرات العامة، والدوال متنوعة المعاملات، والأعداد ذات الفاصلة العائمة، وتمرير الهياكل كمعاملات للدوال. يمكن العثور على بعض الأمثلة في ملفات samples/bpf/*_kern.c في شجرة مصادر النواة.

تحتوي النواة على مصرِّف في الوقت المناسب (JIT) يترجم رمز بايتات eBPF إلى رمز آلة أصيل للحصول على أداء أفضل. قبل لينكس 4.15، كان مصرِّف JIT معطلاً بشكل مبدئي، ولكن يمكن التحكم في عمله عبر كتابة إحدى السلاسل الصحيحة التالية إلى الملف /proc/sys/net/core/bpf_jit_enable:

0
تعطيل تصريف JIT (مبدئي).
1
تصريف عادي.
2
وضع التنقيح. تُفرغ رموز العمليات (opcodes) المولدة بصيغة ست عشرية في سجل النواة. يمكن بعد ذلك تفكيك هذه الرموز باستخدام البرنامج tools/net/bpf_jit_disasm.c المتوفر في شجرة مصادر النواة.

بدءًا من لينكس 4.15، قد تُضبط النواة مع الخيار CONFIG_BPF_JIT_ALWAYS_ON. في هذه الحالة، يكون مصرِّف JIT مفعلاً دائمًا، ويُهيأ bpf_jit_enable إلى القيمة 1 ويكون غير قابل للتغيير. (وُفر خيار ضبط النواة هذا كتخفيف لإحدى هجمات Spectre ضد مفسر BPF.)

يتوفر مصرِّف JIT لـ eBPF حاليًا للمعماريت التالية:

x86-64 (منذ لينكس 3.18؛ cBPF منذ لينكس 3.0)؛
ARM32 (منذ لينكس 3.18؛ cBPF منذ لينكس 3.4)؛
SPARC 32 (منذ لينكس 3.18؛ cBPF منذ لينكس 3.5)؛
ARM-64 (منذ لينكس 3.18)؛
s390 (منذ لينكس 4.1؛ cBPF منذ لينكس 3.7)؛
PowerPC 64 (منذ لينكس 4.8؛ cBPF منذ لينكس 3.1)؛
SPARC 64 (منذ لينكس 4.12)؛
x86-32 (منذ لينكس 4.18)؛
MIPS 64 (منذ لينكس 4.18؛ cBPF منذ لينكس 3.16)؛
riscv (منذ لينكس 5.1).

أمثلة

#include <stdcountof.h>
/* bpf+sockets example:

* 1. create array map of 256 elements
* 2. load program that counts number of packets received
* r0 = skb->data[ETH_HLEN + offsetof(struct iphdr, protocol)]
* map[r0]++
* 3. attach prog_fd to raw socket via setsockopt()
* 4. print number of received TCP/UDP packets every second
*/ int main(int argc, char *argv[]) {
int sock, map_fd, prog_fd, key;
long long value = 0, tcp_cnt, udp_cnt;
map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key),
sizeof(value), 256);
if (map_fd < 0) {
printf("failed to create map '%s'\n", strerror(errno));
/* likely not run as root */
return 1;
}
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), /* r6 = r1 */
BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol)),
/* r0 = ip->proto */
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
/* *(u32 *) (fp - 4) = r0 */
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = r2 - 4 */
BPF_LD_MAP_FD(BPF_REG_1, map_fd), /* r1 = map_fd */
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),
/* r0 = map_lookup(r1, r2) */
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
/* if (r0 == 0) goto pc+2 */
BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
BPF_XADD(BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0),
/* lock *(u64 *) r0 += r1 */
BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
BPF_EXIT_INSN(), /* return r0 */
};
prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog,
countof(prog), "GPL");
sock = open_raw_sock("lo");
assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,
sizeof(prog_fd)) == 0);
for (;;) {
key = IPPROTO_TCP;
assert(bpf_lookup_elem(map_fd, &key, &tcp_cnt) == 0);
key = IPPROTO_UDP;
assert(bpf_lookup_elem(map_fd, &key, &udp_cnt) == 0);
printf("TCP %lld UDP %lld packets\n", tcp_cnt, udp_cnt);
sleep(1);
}
return 0; }

يمكن العثور على بعض الأكواد العاملة الكاملة في دليل samples/bpf في شجرة مصادر النواة.

انظر أيضًا

seccomp(2), bpf-helpers(7), socket(7), tc(8), tc-bpf(8)

شُرح كل من BPF التقليدي والممتد في ملف مصدر النواة Documentation/networking/filter.rst.

ترجمة

تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.

10 فبراير 2026 صفحات دليل لينكس 6.18