تواجه بوابات شبكات VPN عالية الكثافة التي تخدم عشرات الآلاف من الأنفاق المشفرة المتزامنة عقبة تشغيلية حرجة: اختلال التوازن في استهلاك نوى المعالجة المركزية. قد يُظهر خادم متطور يضم 32 أو 64 نواة معالجة نسبة استخدام إجمالية تقل عن 25%، ومع ذلك يعاني من إسقاط حزم البيانات الواردة خلال فترات الذروة. ويكشف فحص مؤشرات الأداء أن نواة المعالج رقم 0 تعمل بنسبة 100% تحت وطأة المقاطعات البرمجية (ksoftirqd/0 و NET_RX_SOFTIRQ)، بينما تظل بقية نوى المعالج المجاورة في حالة خمول تام.

يعود هذا التفاوت إلى الطريقة التي تتفاعل بها مكونات بطاقة الشبكة وبروتوكولات التغليف النفقي مع نواة نظام التشغيل. عندما تعجز تقنية توزيع التحميل العتادية Receive Side Scaling (RSS) عن تجزئة مخططات البيانات النفقية المغلفة بالتساوي، توفر نواة لينكس آليتين برمجيتين لتسريع الأداء: توجيه حزم الاستقبال (RPS) وتوجيه تدفقات الاستقبال (RFS). يتيح الضبط المحكم لهاتين الآليتين توزيع معالجة الحزم عبر كافة النوى المتاحة مع الحفاظ على كفاءة ذاكرة التخزين المؤقت للمعالج.

اختناق التغليف النفقي: انهيار طوابير الانتظار عند الاستقبال

توزع بطاقات واجهة الشبكة الفعلية (NIC) الإطارات الواردة عبر قوائم انتظار عتادية متعددة باستخدام تقنية RSS. تحسب البطاقة قيمة تجزئة توبليتز (Toeplitz) بناءً على ترويسة الحزمة — عادة الرباعية المكونة من عنوان IP المصدر وعنوان IP الوجهة ومنفذ المصدر ومنفذ الوجهة — لتوجيه التدفق نحو مخزن دائري مخصص للاستقبال (RX ring buffer). ويولد كل طابور مقاطعة عتادية مستقلة من نوع MSI-X مثبتة على نواة معالجة محددة.

حدود تقنية RSS العتادية أمام حركة بيانات VPN المغلفة:
[ العملاء البعيدون ] ---> [ حزم UDP الخارجية لبروتوكولات WireGuard / IPsec / RiceGuard ]
                                       |
                                       v
                      [ منفذ وجهة موحد على الخادم (مثل UDP 51820) ]
                                       |
                         [ تجزئة Toeplitz في بطاقة الشبكة ]
                                       |
       =================================================
       تتجمع كافة التدفقات في طابور RX 0 (مقاطعة عتادية واحدة)
       =================================================
                                       |
                                       v
       [ النواة 0: 100% ksoftirqd / NET_RX_SOFTIRQ ] ---> فيضان المخزن الدائري!
       [ النوى من 1 إلى 31: 0% من حمل المقاطعات البرمجية ]

عندما يتصل آلاف المستخدمين بمجمع VPN عبر أنفاق مثل WireGuard أو IPsec أو حزم البيانات المخصصة، تستهدف الحركة الخارجية منفذ استقبال واحد على البوابة. وإذا كانت حزم العملاء تمر عبر بوابات ترجمة عناوين ناقلة مشتركة (Carrier-Grade NAT)، تتطابق قيم التجزئة الرباعية، مما يؤدي إلى تراكم التدفقات في طابور عتادي واحد.

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

آلية Receive Packet Steering: توزيع برمجي متعدد النوى

تتولى آلية توجيه حزم الاستقبال (RPS) مهمة توزيع عبء معالجة حزم الشبكة على قناع من نوى المعالج يحدده مسؤول النظام، وذلك بمجرد اكتمال المقاطعة العتادية الأولية. وبخلاف تقنية RSS التي تعمل بالكامل داخل دوائر بطاقة الشبكة، تعمل آلية RPS برمجيًا خلال دورة فحص NAPI في برنامج تشغيل البطاقة (netif_receive_skb).

بنية التوزيع البرمجي لآلية RPS عبر النوى:
[ حزمة واردة في طابور الاستقبال RX 0 ]
                    |
                    v
          [ روتين فحص NAPI البرمجي ]
                    |
    [ قناع النوى rps_cpus: مثلاً 0x0000ffff (النوى 0 إلى 15) ]
                    |
    [ حساب قيمة التجزئة jhash على ترويسة الحزمة ]
                    |
                    v
   [ إدراج sk_buff في طابور input_pkt_queue للنواة المستهدفة ]
                    |
   [ مقاطعة بين المعالجات (IPI): إطلاق NET_RX على النواة المستهدفة ]
                    |
     +--------------+--------------+--------------+
     |              |              |              |
     v              v              v              v
  [النواة 0]     [النواة 1]     [النواة 2]     [النواة 3] ... [النواة 15]

عند وصول الحزمة، يتحقق برنامج التشغيل من تفعيل RPS على ذلك الطابور عبر نظام ملفات sysfs. وفي حال التفعيل، تحسب النواة تجزئة لترويسة الشبكة والنقل باستخدام خوارزمية جينكينز (jhash). وتُحدد النواة المستهدفة من بين النوى المفعلة في قناع rps_cpus.

تضع النواة المستقبلة واصف الحزمة sk_buff في طابور الانتظار الخاص بالنواة المستهدفة (input_pkt_queue) وترسل مقاطعة بين المعالجات (IPI). وتتولى النواة المستهدفة بقية مراحل المعالجة: تطبيق قواعد الجدار الناري، والبحث في جدول التوجيه، وتسليم البيانات إلى المقبس.

آلية Receive Flow Steering: الحفاظ على ذاكرة التخزين المؤقت

على الرغم من نجاح RPS في توزيع الحمل عبر نوى المعالج، فإنها تختار النواة المستهدفة بناءً على التجزئة الرياضية المجردة دون معرفة موضع خيط المعالجة الذي يقرأ من المقبس في مساحة المستخدم.

إذا قامت النواة 4 بمعالجة الحزمة داخل مكدس النواة بينما يعمل خيط تطبيق VPN على النواة 12، فإن بيانات الحمولة يجب أن تنتقل عبر مسارات النواقل بين المعالجات. وتتسبب هذه الهجرة عبر النوى في ضياع الذاكرة المؤقتة L1/L2، وتعطيل خطوط الكاش، وتراجع سعة معالجة الذاكرة الرئيسية.

تعالج آلية توجيه التدفق (RFS) هذا التراجع بتوجيه معالجة الحزمة مباشرة إلى نواة المعالج التي تستضيف خيط التطبيق النشط في قراءة بيانات المقبس.

آلية توافق التدفق في تقنية RFS:
1. خيط التطبيق على النواة 3 يستدعي recvmsg(sock_fd)
   ---> تقوم النواة بتحديث جدول التدفق العام (rps_sock_flow_table):
        [ تجزئة التدفق #A18C -> يعمل حالياً على النواة 3 ]

2. الحزمة التالية للتدفق #A18C تصل إلى بطاقة الشبكة
   ---> يستعلم برنامج التشغيل من جدول rps_sock_flow_table:
        النواة المستهدفة = النواة 3 (حيث البيانات محملة مسبقاً بالكاش!)

3. يتم إدراج sk_buff مباشرة في input_pkt_queue للنواة 3
   ---> تشترك المقاطعات البرمجية والتطبيق في ذات الكاش L1/L2!

تنسق RFS عملها عبر جدولين داخليين:

  1. جدول تدفق المقابس العام (rps_sock_flow_table): يُحدث تلقائيًا بمجرد استدعاء دوال recvmsg(2) أو read(2) أو epoll_wait(7). وتسجل النواة تجزئة التدفق ورقم النواة الحالية.
  2. جدول التدفق المحلي لكل طابور (rps_flow_table): يُدار مستقلاً لكل طابور استقبال في البطاقة، ويحفظ رقم النواة التي عالجت آخر حزمة لذلك التدفق.

قبل توجيه الحزمة إلى النواة المحددة في الجدول العام، تقارن RFS البيانات مع جدول الطابور المحلي. وإذا انتقل خيط التطبيق إلى نواة جديدة، تنتظر الآلية اكتمال معالجة كافة الحزم القديمة العالقة في طابور النواة السابقة قبل التحويل، مما يمنع حدوث أي اضطراب في ترتيب وصول الحزم.

التطبيق العملي: إعداد RPS و RFS على بوابات VPN

توضح الخطوات التالية كيفية ضبط خادم VPN يعمل بنواة لينكس ويضم 16 نواة معالجة مع واجهة شبكة خارجية eth0 وواجهة نفقية wg0.

الخطوة 1: تعيين قناع النوى لآلية RPS

في نظام يضم 16 نواة (من 0 إلى 15)، يُفعل القناع الست عشري 0xffff كافة النوى. ولتأمين استقرار الأداء، يُفضل تخصيص النواتين 0 و 1 للمهام الإدارية والمقاطعات العتادية، وإسناد النوى من 2 إلى 15 (القناع 0xfffc) لمعالجة حزم الشبكة:

# تفعيل RPS للنوى من 2 إلى 15 على كافة طوابير استقبال eth0
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo "fffc" > "$rx_queue/rps_cpus"
done

# تفعيل RPS على واجهة النفق الافتراضية
echo "fffc" > /sys/class/net/wg0/queues/rx-0/rps_cpus

الخطوة 2: ضبط سعة جداول RFS العامة والمحلية

تُحدد سعة جداول التدفق استنادًا إلى حجم الاتصالات المتزامنة. لبوابة تستوعب حتى 32,768 جلسة، يُختار أقرب مضاعف للقوة 2 وهو 65,536:

# تعيين سعة جدول تدفق المقابس العام
sysctl -w net.core.rps_sock_flow_entries=65536

# توزيع الإدخالات بالتساوي على الطوابير العتادية (4 طوابير: 65536 / 4 = 16384)
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo 16384 > "$rx_queue/rps_flow_cnt"
done

الخطوة 3: توسيع سعة طابور تراكم الحزم

في حالات الارتفاع المفاجئ لحركة المرور، قد يفيض طابور النواة المستهدفة إذا تأخرت معالجة المقاطعات البرمجية. يساهم رفع قيمة netdev_max_backlog في منع فقدان الحزم المسجل في /proc/net/softnet_stat:

# رفع سعة طابور التراكم للنواة الواحدة من 1000 إلى 10000 حزمة
sysctl -w net.core.netdev_max_backlog=10000

# زيادة حصة المعالجة في دورة فحص NAPI الواحدة
sysctl -w net.core.netdev_budget=600
sysctl -w net.core.netdev_budget_usecs=4000

التكامل البنيوي في تقنية RiceGuard VPN

يتكامل بروتوكول RiceGuard للتوجيه على الطبقة الثالثة والمطور لدى VPNRice مع محركات توزيع الحزم في نواة لينكس:

  1. المواءمة مع طوبولوجيا NUMA: يعزل RiceGuard مقاطعات العتاد وحسابات التشفير وحلقات المقابس في عقد NUMA مخصصة. وعند ورود الحركة عبر بطاقة متصلة بالعقدة NUMA 0، يُحدد القناع rps_cpus حصريًا على النوى التي تشترك في ذاكرة الكاش L3 مع مسار PCIe لتلك البطاقة.
  2. المزامنة الدورية لجداول التدفق: عند التدفق الكثيف للاتصالات الجديدة، تسجل مسارات المقابس إشارات التوافق في جداول النواة، مما يضمن بقاء المقاطعات البرمجية وخيوط التطبيق ضمن نفس النطاق التخزيني المؤقت.
  3. نتائج القياس الواقعي: خلال اختبارات التحمل على خطوط 10 جيجابت/ثانية تخدم 25,000 نفق نشط، خفض التنسيق بين RPS و RFS زمن انتقال الحزم عند الشريحة المئوية 99 (p99) من 8.4 مللي ثانية إلى 1.9 مللي ثانية، مع القضاء التام على اختناقات المقاطعات.

الأسئلة الشائعة

ما الفارق الجوهري بين تقنيات RSS و RPS و RFS؟

تعد RSS ميزة عتادية في بطاقة الشبكة تستخدم طوابير فيزيائية ومقاطعات MSI-X. وتعتبر RPS نظامًا برمجيًا في نواة لينكس يوزع الحزم بعد انتهاء المقاطعة العتادية. أما RFS فتطور فكرة RPS لتوجيه الحزم خصيصًا نحو النواة التي تشغل التطبيق المالك للمقبس.

هل يؤدي تفعيل تقنية RPS إلى استنزاف موارد المعالج؟

تتطلب RPS قدرًا ضئيلاً من طاقة المعالجة (حوالي 1% إلى 2%) لحساب تجزئة جينكينز وإرسال المقاطعات بين المعالجات IPI. ومع ذلك، فإن توزيع العمل وتفادي إشباع نواة واحدة يرفع الإنتاجية الإجمالية للبوابة بدرجة كبيرة.

هل يمكن تفعيل آليتي RPS و RFS على واجهات tun/tap الافتراضية؟

نعم، تستفيد واجهات tun و tap الافتراضية بشكل كبير من RPS. وبما أن هذه الواجهات تمتلك عادة طابورًا واحدًا، فإن تفعيل rps_cpus على rx-0 يوزع معالجة الحزم المفكوكة تشفيرها على بقية نوى المعالج المتاحة.