كل ذلك مع العطلات الماضية!
قررنا تخصيص مقالنا الأول بعد الإجازات لنظام Linux ، أي لدورة
مسؤول Linux الرائعة ، والتي لدينا في مجموعة من أكثر الدورات ديناميكية ، أي مع المواد والممارسات الأكثر صلة. حسنًا ، ووفقًا لذلك ، نقدم مقالات مثيرة للاهتمام
ودرسًا مفتوحًا .
أرسلت بواسطة ماتيو كروس
العنوان الأصلي: تصحيح الشبكة باستخدام eBPF (RHEL 8 Beta)مقدمةالشبكات هي تجربة مثيرة ، ولكن لا يتم تجنب المشاكل دائمًا. يمكن أن تكون عملية استكشاف الأخطاء وإصلاحها صعبة ، كما تحاول إعادة إنتاج السلوك الخاطئ الذي يحدث "في الحقل".
لحسن الحظ ، هناك أدوات يمكن أن تساعد في ذلك: مساحات أسماء الشبكات ، والأجهزة الظاهرية ،
tc
و
netfilter
. يمكن إعادة إنتاج إعدادات الشبكة البسيطة باستخدام مساحات أسماء الشبكات وأجهزة veth ، بينما تتطلب الإعدادات الأكثر تعقيدًا توصيل الأجهزة الظاهرية بجسر برامج واستخدام أدوات الشبكة القياسية ، مثل
iptables
أو
tc
، لمحاكاة السلوك غير الصحيح. إذا كانت هناك مشكلة في استجابات ICMP التي تم إنشاؤها عند
iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable
خادم SSH ، فيمكن أن تساعد
iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable
في مساحة الاسم الصحيحة المساعدة في حل المشكلة.
توضح هذه المقالة كيفية استكشاف مشكلات الشبكة المعقدة باستخدام
eBPF (امتداد BPF) ، وهو إصدار متقدم من Berkeley Packet Filter. يعد eBPF تقنية جديدة نسبيًا ، والمشروع في مرحلة مبكرة ، وبالتالي فإن الوثائق و SDK ليست جاهزة بعد. لكن دعونا نأمل في إجراء تحسينات ، خاصة وأن XDP (مسار بيانات eXpress) يأتي مع
Red Hat Enterprise Linux 8 Beta ، والذي يمكنك تنزيله وتشغيله الآن.
لن يحل eBPF جميع المشكلات ، لكنه لا يزال أداة تصحيح أخطاء شبكة قوية تستحق الاهتمام. أنا متأكد من أنها ستلعب دورًا مهمًا في مستقبل الشبكات.
المشكلةقمت
بتصحيح مشكلة شبكة
Open vSwitch (OVS) ، والتي تضمنت
تثبيتًا معقدًا للغاية: تم تناثر بعض حزم TCP وتسليمها بالترتيب الخاطئ ، وانخفض عرض النطاق الترددي للأجهزة الافتراضية من 6 جيجابت / ثانية مستقرة إلى 2-4 جيجابت / ثانية. أظهر التحليل أنه تم إرسال أول حزمة TCP لكل اتصال بعلامة PSH بالترتيب الخاطئ: فقط الأولى والوحيدة لكل اتصال.
حاولت إعادة إنتاج هذا الإعداد باستخدام جهازين
nftables
، وبعد العديد من مقالات المساعدة واستعلامات البحث ، وجدت أنه لا يمكن لأي من
iptables
أو
nftables
معالجة علامات TCP ، بينما يمكن
tc
، ولكن فقط عن طريق الكتابة فوق الإشارات ومقاطعة الاتصالات الجديدة و TCP بشكل عام.
قد يكون من الممكن حل المشكلة باستخدام مزيج من
iptables
و
conntrack
و
tc
، لكنني قررت أن هذه مهمة رائعة لـ eBPF.
ما هو eBPF؟eBPF هو نسخة محسنة من مرشح حزم بيركلي. إنها تجلب الكثير من التحسينات على BPF. على وجه الخصوص ، يتيح لك الكتابة في الذاكرة ، وليس فقط القراءة ، لذلك لا يمكن تصفية الحزم فقط ، ولكن أيضًا تحريرها.
غالبًا ما تسمى eBPF ببساطة BPF ، وتسمى BPF نفسها cBPF (الكلاسيكية (الكلاسيكية) BPF) ، لذلك يمكن استخدام كلمة "BPF" لتعني كلا الإصدارين ، اعتمادًا على السياق: في هذه المقالة أتحدث دائمًا عن الإصدار الموسّع.
"تحت الغطاء" ، يحتوي eBPF على جهاز افتراضي بسيط للغاية يمكنه تنفيذ أجزاء صغيرة من الرمز الفرعي وتحرير بعض المخازن المؤقتة للذاكرة. توجد قيود في eBPF تحميها من الاستخدام الضار:
- الدورات محظورة بحيث ينتهي البرنامج دائمًا في وقت محدد ؛
- يمكن فقط الوصول إلى الذاكرة من خلال المخزن المؤقت المكدس والخدش؛
- يمكن استدعاء وظائف kernel المسموح بها فقط.
يمكن تحميل برنامج في النواة بطرق مختلفة باستخدام
تصحيح الأخطاء والتتبع . في حالتنا ، يهتم eBPF بالعمل مع الأنظمة الفرعية للشبكة. هناك طريقتان لاستخدام برنامج eBPF:
- متصل عبر XDP ببداية مسار RX لبطاقة شبكة فعلية أو افتراضية ؛
- متصلة عبر
tc
إلى qdisc في الإدخال أو الإخراج.
لإنشاء برنامج eBPF للاتصال ، ما عليك سوى كتابة رمز C وتحويله إلى رمز ثانوي. التالي مثال بسيط باستخدام XDP:
SEC("prog") int xdp_main(struct xdp_md *ctx) { void *data_end = (void *)(uintptr_t)ctx->data_end; void *data = (void *)(uintptr_t)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct icmphdr *icmph = (struct icmphdr *)(iph + 1); if (icmph + 1 > data_end) return XDP_PASS; if (eth->h_proto != ntohs(ETH_P_IP) || iph->protocol != IPPROTO_ICMP || icmph->type != ICMP_ECHOREPLY) return XDP_PASS; if (iph->ttl) { uint16_t *ttlproto = (uint16_t *)&iph->ttl; uint16_t old_ttlproto = *ttlproto; iph->ttl = bpf_get_prandom_u32() % iph->ttl + 1; csum_replace2(&iph->check, old_ttlproto, *ttlproto); } return XDP_PASS; } char _license[] SEC("license") = "GPL";
المقتطف أعلاه ، دون
include
التعبيرات والمساعدين والرمز الاختياري ، هو برنامج XDP يغير TTL لردود صدى ICMP المستلمة ، أي البونغ ، برقم عشوائي. الوظيفة الرئيسية تحصل على بنية
xdp_md
، والتي تحتوي على مؤشرين إلى بداية ونهاية الحزمة.
لتجميع الكود الخاص بنا إلى eBPF bytecode ، يلزم وجود مترجم مع الدعم المناسب. Clang يدعمها ويقوم بإنشاء eBPF bytecode عن طريق تحديد bpf كهدف في وقت الترجمة:
$ clang -O2 -target bpf -c xdp_manglepong.c -o xdp_manglepong.o
ينشئ الأمر أعلاه ملفًا يبدو للوهلة الأولى أنه ملف كائن عادي ، ولكن بعد الفحص الدقيق ، اتضح أن نوع الكمبيوتر المحدد هو Linux eBPF ، وليس النوع الأصلي لنظام التشغيل:
$ readelf -h xdp_manglepong.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF <--- HERE [...]
بعد تلقي حزمة ملف كائن عادي ، يكون برنامج eBPF جاهزًا لتنزيل الجهاز والاتصال به عبر XDP. يمكن القيام بذلك باستخدام
ip
من الحزمة
iproute2
مع بناء الجملة التالي:
يحدد هذا الأمر واجهة wlan0 الهدف ، وبفضل خيار القوة ، يقوم بالكتابة فوق أي كود eBPF موجود بالفعل تم تحميله بالفعل. بعد تحميل كود eBPF الثانوي ، يتصرف النظام على النحو التالي:
$ ping -c10 192.168.85.1 PING 192.168.85.1 (192.168.85.1) 56(84) bytes of data. 64 bytes from 192.168.85.1: icmp_seq=1 ttl=41 time=0.929 ms 64 bytes from 192.168.85.1: icmp_seq=2 ttl=7 time=0.954 ms 64 bytes from 192.168.85.1: icmp_seq=3 ttl=17 time=0.944 ms 64 bytes from 192.168.85.1: icmp_seq=4 ttl=64 time=0.948 ms 64 bytes from 192.168.85.1: icmp_seq=5 ttl=9 time=0.803 ms 64 bytes from 192.168.85.1: icmp_seq=6 ttl=22 time=0.780 ms 64 bytes from 192.168.85.1: icmp_seq=7 ttl=32 time=0.847 ms 64 bytes from 192.168.85.1: icmp_seq=8 ttl=50 time=0.750 ms 64 bytes from 192.168.85.1: icmp_seq=9 ttl=24 time=0.744 ms 64 bytes from 192.168.85.1: icmp_seq=10 ttl=42 time=0.791 ms --- 192.168.85.1 ping statistics --- 10 packets transmitted, 10 received, 0% packet loss, time 125ms rtt min/avg/max/mdev = 0.744/0.849/0.954/0.082 ms
تمر كل حزمة عبر eBPF ، مما يؤدي في النهاية إلى إجراء بعض التغييرات وتحديد ما إذا كان سيتم إسقاط الحزمة أو تخطيها.
كيف يمكن eBPF المساعدةبالعودة إلى مشكلة الشبكة الأصلية ، نذكر أنه كان من الضروري وضع علامة على عدة علامات TCP ، واحدة لكل اتصال ، ولا يمكن لـ
iptables
أو
tc
القيام بذلك. كتابة التعليمات البرمجية لهذا السيناريو ليست صعبة على الإطلاق: تكوين جهازين ظاهريين متصلين بجسر OVS ، وقم ببساطة بتوصيل eBPF بأحد أجهزة VM الافتراضية.
يبدو هذا حلاً رائعًا ، لكن ضع في اعتبارك أن XDP يدعم معالجة الحزم المستلمة فقط ، ولن يكون لتوصيل eBPF بمسار
rx
الخاص بالجهاز الظاهري المستقبِل أي تأثير على المحول.
لحل هذه المشكلة ، يجب تحميل eBPF باستخدام
tc
وتوصيله بمسار إخراج VM ، لأن
tc
يمكنه تحميل وتوصيل برامج eBPF بـ qdisk. لتعليم الحزم الخارجة من المضيف ، يجب أن يكون eBPF متصلاً بـ qdisk الإخراج.
عند تحميل برنامج eBPF ، هناك بعض الاختلافات بين
XDP
و
tc
API: بشكل افتراضي ، أسماء أقسام مختلفة ، ونوع بنية وسيطة الوظيفة الرئيسية ، وقيم الإرجاع المختلفة. لكن هذه ليست مشكلة. يوجد أدناه مقتطف من برنامج يقوم بتمييز TCP عند الانضمام إلى إجراء tc:
#define RATIO 10 SEC("action") int bpf_main(struct __sk_buff *skb) { void *data = (void *)(uintptr_t)skb->data; void *data_end = (void *)(uintptr_t)skb->data_end; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct tcphdr *tcphdr = (struct tcphdr *)(iph + 1); if ((void *)(tcphdr + 1) > data_end) return TC_ACT_OK; if (eth->h_proto != __constant_htons(ETH_P_IP) || iph->protocol != IPPROTO_TCP) return TC_ACT_OK; if (tcphdr->syn || tcphdr->fin || tcphdr->rst || tcphdr->psh) return TC_ACT_OK; if (bpf_get_prandom_u32() % RATIO == 0) tcphdr->psh = 1; return TC_ACT_OK; } char _license[] SEC("license") = "GPL";
يتم التجميع في bytecode كما هو موضح في مثال XDP أعلاه باستخدام ما يلي:
clang -O2 -target bpf -c tcp_psh.c -o tcp_psh.o
لكن التنزيل مختلف:
الآن يتم تحميل eBPF في المكان الصحيح ويتم وضع علامة على الحزم التي تغادر جهاز VM. بعد التحقق من الحزم المستلمة في جهاز VM الثاني ، سنرى ما يلي:

يؤكد
tcpdump
أن كود eBPF الجديد يعمل ، وحوالي 1 من كل 10 حزم TCP بها مجموعة إشارة PSH. كانت هناك حاجة إلى 20 سطرًا فقط من التعليمات البرمجية لـ C لتمييز حزم TCP بشكل انتقائي وترك الجهاز الظاهري ، وإعادة إنتاج الخطأ الذي يحدث "في المعركة" ، وكل ذلك دون إعادة ترجمة أو حتى إعادة التشغيل! أدى ذلك إلى تبسيط عملية التحقق من
إصلاح برنامج
Open vSwitch ، والذي كان من المستحيل تحقيقه باستخدام أدوات أخرى.
الخاتمةeBPF هي تقنية جديدة إلى حد ما ، وللمجتمع رأي واضح حول تنفيذها. تجدر الإشارة أيضًا إلى أن المشروعات القائمة على eBPF ، مثل
bpfilter ، أصبحت أكثر شيوعًا ، ونتيجة لذلك ، بدأ العديد من موردي المعدات في تنفيذ دعم eBPF مباشرةً على بطاقات الشبكة.
لن يحل eBPF جميع المشاكل ، لذلك لا تسيء استخدامها ، ولكن لا يزال أداة قوية للغاية لتصحيح أخطاء الشبكة وتستحق الاهتمام. أنا متأكد من أنها ستلعب دورًا مهمًا في مستقبل الشبكات.
النهايةنحن في انتظار تعليقاتكم هنا ، ونحن ندعوك لزيارة
درسنا المفتوح ، حيث يمكنك أيضًا طرح الأسئلة.