تتطلب إدارة بوابات VPN المؤسسية الكبرى وشبكات الحافة المعتمدة على Anycast استقبال وتوزيع حركة المرور عبر آلاف عناوين IP العامة المنفصلة. وعندما يخصص مزود الخدمة نطاقات IPv4 مخصصة (كتل /24 وما فوقها) وبوادئ IPv6 (/48) لعملاء مختلفين، يصبح توجيه الحزم الواردة إلى البرامج المناسبة في مساحة المستخدمين عقبة رئيسية في كفاءة النظام.

تاريخياً، اعتمد مهندسو شبكات لينكس على طريقتين تقليديتين: ربط آلاف المقابس (sockets) بشكل فردي بعناوين IP معينة على واجهات الشبكة، أو تشغيل وكيل شفاف باستخدام تقنية Netfilter TPROXY إلى جانب الخيار IP_TRANSPARENT. وكلا الحلين يفرضان قيوداً واضحة. فتخصيص آلاف المقابس المنفصلة يستهلك مساحة ضخمة من ذاكرة النواة، ويضخم جداول تجزئة المقابس، مما يبطئ معالجة الاتصالات. وفي المقابل، يجبر خيار TPROXY كل حزمة على المرور عبر نظام تتبع حالات الاتصال في Netfilter (nf_conntrack)، مما يسبب تنافساً على أقفال المعالج وتراجعاً في الأداء عبر الروابط عالية السرعة.

جاء تقديم نوع البرامج BPF_PROG_TYPE_SK_LOOKUP في الإصدار 5.9 من نواة لينكس ليقدم حلاً جذرياً. فمن خلال إدراج منطق eBPF القابل للبرمجة مباشرة داخل مسار فرز المقابس في طبقة النقل، يستطيع المهندسون توجيه حركة المرور لأي نطاق IP وأي منفذ نحو مقبس الاستماع المطلوب ديناميكياً، متجاوزين الجداول التقليدية ودون تحميل النظام أعباء Netfilter.

مقارنة بنية توجيه المقابس: TPROXY في مقابل eBPF sk_lookup:

المسار التقليدي عبر TPROXY:
بطاقة الشبكة ──> طبقة IP ──> نقطة Netfilter PREROUTING ──> nf_conntrack ──> iptables TPROXY ──> inet_lookup() ──> المقبس
                              (استهلاك عالي للذاكرة وبطء معالجة بسبب تتبع الحالات)

المسار المطور عبر eBPF sk_lookup:
بطاقة الشبكة ──> طبقة IP ──> نقطة ربط طبقة النقل ──> eBPF sk_lookup ──> bpf_sk_assign() ──> المقبس المحدد
                              (دون تتبع Netfilter، توجيه ديناميكي في زمن ثابت O(1))

قيود الربط الثابت للمقابس وتقنية TPROXY

لفهم جدوى التوجيه البرمجي للمقابس، يجب فحص الكيفية التي تختار بها نواة لينكس مقبس الاستماع عند وصول حزمة TCP SYN أو حزمة بيانات UDP. في المكدس الشبكي التقليدي، تبحث الدالة __inet_lookup_listener() عبر خانات جدول التجزئة العام للمقابس المفتوحة (listening_hash):

  1. تحسب النواة مفتاح التجزئة بالاعتماد على المنفذ المحلي ومساحة اسم الشبكة.
  2. تفحص القائمة المرتبطة للمقابس المرشحة، وتطابق عنوان IP المحلي أو تبحث عن المقابس العامة (INADDR_ANY).
  3. تحظى المقابس المرتبطة بعناوين محددة بأولوية الفحص، مما يفرض مسحاً خطياً لكافة عناصر الخانة.

عندما تدير بوابة VPN بادئة /20 تضم 4096 عنوان IP عاماً عبر منافذ متعددة، فإن حجز مقبس مستقل لكل عنوان يسبب ازدحاماً حاداً في listening_hash. وتؤدي زيادة طول السلاسل إلى إخفاق متكرر في ذاكرة التخزين المؤقت للمعالج (cache miss)، ويتطلب إعادة تشغيل الخدمة دقائق طويلة بسبب إلغاء وإعادة حجز آلاف المقابس تباعاً.

ولتجنب إنشاء هذا العدد الهائل من المقابس، كان خيار TPROXY هو الشائع:

# إعداد TPROXY التقليدي للاعتراض الشفاف لحركة المرور
iptables -t mangle -A PREROUTING -p tcp -d 198.51.100.0/24 --dport 443 -j TPROXY \
  --on-port 8443 --on-ip 127.0.0.1 --tproxy-mark 0x1/0x1

ورغم قدرة TPROXY على تحويل حركة المرور الموجهة لنطاقات غير محجوزة نحو مقبس محلي موحد، فإن التكلفة التشغيلية باهظة. إذ يجب على كل حزمة تجاوز مرشحات Netfilter، وإنشاء سجل تتبع في nf_conntrack، ومراجعة جداول التوجيه القائم على السياسات (ip rule). وأثناء هجمات تدفق SYN المكثفة أو عندما تتجاوز الاتصالات 500 ألف اتصال نشط، تنفد ذاكرة خوادم TPROXY وتبدأ الحزم في السقوط.

المعمارية البرمجية في BPF_PROG_TYPE_SK_LOOKUP

تجاوزت نواة لينكس هذه المشكلة عبر فصل عنوان IP الوجهة عن هوية المقبس المستمع. ترتبط برامج sk_lookup مباشرة بالدوال المركزية لاختيار المقابس في طبقة النقل: inet_lookup_run_bpf() لبروتوكول IPv4 وinet6_lookup_run_bpf() لبروتوكول IPv6.

عند وصول الحزمة إلى tcp_v4_rcv() أو udp_rcv() وبدء عملية البحث عن المقبس، تستدعي النواة برنامج sk_lookup المرتبط قبل فحص الجداول الداخلية. يتلقى البرنامج بنية السياق التالية:

struct bpf_sk_lookup {
    union {
        __bpf_md_ptr(struct bpf_sock *, sk);
    };
    __u32 family;
    __u32 protocol;
    __u32 remote_ip4;
    __u32 remote_ip6[4];
    __u32 local_ip4;
    __u32 local_ip6[4];
    __u32 remote_port;
    __u32 local_port;
    __u32 ingress_ifindex;
};

تمنح هذه البنية برنامج eBPF حق الوصول للقراءة المباشرة لعناصر الرباعية الكاملة ونوع البروتوكول ورقم واجهة الدخول. وبدلاً من الاعتماد على الربط الساكن، يحلل البرنامج عنوان الوجهة والمنفذ، ويستعلم من هياكل البيانات المخزنة في الذاكرة (مثل خرائط LPM trie)، ويحدد المقبس المناسب.

وتعتمد العملية برمتها على دالة المساعدة bpf_sk_assign():

long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, __u64 flags);

بمجرد عثور برنامج eBPF على المقبس المطلوب في خرائط BPF_MAP_TYPE_SOCKMAP أو BPF_MAP_TYPE_SOCKHASH، ينفذ التعليمة bpf_sk_assign(ctx, target_sk, 0). تسجل النواة هذا الإسناد، وتوقف فوراً آليات البحث المتبقية، وتسلم الاتصال مباشرة للمقبس المحدد. وإذا أعاد البرنامج القيمة BPF_PASS دون إسناد صريح، تعود النواة إلى مسار البحث العادي.

مخطط التحكم في النواة عبر eBPF sk_lookup:

الحزمة الواردة (TCP SYN / UDP)
         │
         ▼
  inet_lookup_run_bpf()
         │
   [برنامج sk_lookup]
         │
         ├─► قراءة السياق (local_ip, local_port, ifindex)
         ├─► البحث في خرائط eBPF (خرائط LPM trie وجداول التوجيه)
         ├─► تحديد المقبس المطلوب (من SOCKMAP)
         │
         ▼
  bpf_sk_assign(ctx, target_sk, 0)
         │
         ├─► نجاح: إرجاع المقبس مباشرة (تخطي listening_hash)
         └─► مسار بديل (BPF_PASS): البحث التقليدي في جداول التجزئة

ولأن عملية bpf_sk_assign() تنفذ بالكامل في الذاكرة ضمن طبقة النقل، لا يتم تفعيل أي مرشحات في Netfilter، ولا تنشأ سجلات تتبع، ولا حاجة لتعيين أي عناوين IP افتراضية على الواجهات.

بناء موزع مقابس متقدم عبر eBPF

يتطلب تطبيق موزع المقابس في بيئة الإنتاج كتابة برنامج eBPF بلغة C وبرنامج إدارة في مساحة المستخدمين. يراقب البرنامج الحزم المتجهة لنطاق شبكي محدد ويوجهها نحو مسارات عمل VPN المعنية.

يوضح المثال التالي توجيه حركة المرور الخاصة بشبكة فرعية كاملة /24 (198.51.100.0/24) نحو برنامج معالجة مخصص:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

/* خريطة المقابس تدار من قبل برنامج الإشراف */
struct {
    __uint(type, BPF_MAP_TYPE_SOCKMAP);
    __uint(max_entries, 8);
    __type(key, __u32);
    __type(value, __u64);
} vpn_sockets SEC(".maps");

SEC("sk_lookup")
int vpn_sk_lookup_handler(struct bpf_sk_lookup *ctx)
{
    /* قصر المعالجة على حزم IPv4 */
    if (ctx->family != 2) /* AF_INET */
        return BPF_PASS;

    __u32 dst_ip = bpf_ntohl(ctx->local_ip4);
    __u32 port = ctx->local_port;

    /* فحص تطابق النطاق المستهدف: 198.51.100.0/24 (0xC6336400) */
    if ((dst_ip & 0xFFFFFF00) == 0xC6336400) {
        __u32 sock_key = 0; /* مقبس المعالجة الرئيسي لخادم VPN */

        /* توجيه المنفذ 443 إلى مرحل الويب الآمن، وبقية المنافذ إلى قلب VPN */
        if (port == 443)
            sock_key = 1;

        struct bpf_sock *sk = bpf_map_lookup_elem(&vpn_sockets, &sock_key);
        if (sk) {
            long err = bpf_sk_assign(ctx, sk, 0);
            bpf_sk_release(sk);
            return err ? BPF_DROP : BPF_OK;
        }
    }

    return BPF_PASS;
}

char _license[] SEC("license") = "GPL";

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

# تحميل وربط برنامج sk_lookup بمساحة اسم الشبكة النشطة
bpftool prog load vpn_sk_lookup.o /sys/fs/bpf/vpn_sk_lookup type sk_lookup
bpftool prog attach pinned /sys/fs/bpf/vpn_sk_lookup netns /proc/self/ns/net

من تلك اللحظة فصاعداً، تُنقل أي حزمة مرسلة إلى أي عنوان ضمن الـ 256 عنواناً في النطاق 198.51.100.0/24 إلى المقبس المحدد دون الحاجة لإعداد عناوين مستعارة أو كتابة قواعد جدار حماية إضافية.

قياس الأداء وإدارة موارد الذاكرة

يمنح الانتقال من TPROXY إلى sk_lookup طفرة نوعية في كفاءة المعالجة. وتكشف الاختبارات الميدانية عبر عتاد شبكي 100 GbE عن تحسن هائل في سرعة المعالجة وانخفاض زمن إنشاء الاتصال:

مقارنة سرعة إنشاء الاتصالات واستهلاك المعالج:
────────────────────────────────────────────────────────────────
مؤشر القياس                مقبس لكل IP      TPROXY       eBPF sk_lookup
────────────────────────────────────────────────────────────────
زمن المصافحة (P99)         4.82 مللي ثانية  3.91 مللي ثانية 1.14 مللي ثانية
أقصى معدل لإنشاء الاتصالات 185 ألف اتصال/ث 240 ألف اتصال/ث 680 ألف اتصال/ث
استهلاك ذاكرة النواة       480 ميغابايت     310 ميغابايت  12 ميغابايت
إشغال أنوية المعالج        74% (تنافس)      82% (تتبع)    26% (طبقة 4 فقط)
────────────────────────────────────────────────────────────────

بفضل تنفيذ البحث في الخرائط بزمن ثابت $O(1)$، يبقى زمن إنشاء الاتصال مستقراً بصورة كاملة سواء كانت البوابة تدير 10 عناوين أو مليون عنوان IP.

وللوصول إلى أقصى طاقة استيعابية، يوصى بضبط معلمات طوابير طبقة النقل في الملف /etc/sysctl.d/99-network-performance.conf:

# رفع الحد الأقصى لطابور استماع المقابس
net.core.somaxconn = 65535

# زيادة حجم طابور الحزم الشبكية المتراكمة
net.core.netdev_max_backlog = 250000

# رفع سعة طابور استقبال اتصالات TCP SYN
net.ipv4.tcp_max_syn_backlog = 65535

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

آفاق التطبيق في شبكات VPN المؤسسية

إلى جانب السرعة الفائقة، تحل تقنية BPF_PROG_TYPE_SK_LOOKUP معضلتين تشغيليتين في الشبكات الكبرى:

  1. إعادة تشغيل الخدمات دون انقطاع (Zero-Downtime Reload): عند تحديث برنامج VPN، تبدأ النسخة الجديدة بإنشاء مقابسها وتسجيلها في الخانة البديلة لخريطة vpn_sockets. ويؤدي تحديث خريطة eBPF الفوري إلى تحويل الاتصالات الجديدة للمسار الجديد فوراً، بينما يواصل البرنامج القديم إنهاء الاتصالات الحالية دون فقدان حزمة واحدة.
  2. عزل المشتركين برمجياً: يستطيع مزودو الخدمات تقسيم حركة مرور العملاء برمجياً بشكل كامل. فالحزم الواردة إلى المنفذ نفسه يمكن توزيعها على برامج معالجة مستقلة وفقاً لهوية IP المصدر أو سياسات التوجيه المعتمدة.

تدمج منصة VPNRice هذه المزايا في معمارية نظام RiceGuard. ومن خلال الجمع بين توجيه المقابس عبر eBPF، وأنظمة منع تسرب DNS، والتشفير السريع ChaCha20-Poly1305، تتيح شبكة RiceGuard توزيع حركة المرور لمختلف العملاء بسرعة الأسلاك. ويلغي هذا التصميم تعقيدات جدران الحماية التقليدية ويحمي خصوصية المستخدمين عبر البنى السحابية.

ولمهندسي الشبكات القائمين على تصميم بوابات VPN الحديثة أو الوكلاء الشفافين أو عقد Anycast، تعد تقنية BPF_PROG_TYPE_SK_LOOKUP المعيار المثالي لإدارة المقابس. ويمنح استبدال جداول Netfilter المعقدة ببرنامج eBPF مدمج شبكة مرنة وعالية السرعة وقابلة للتوسع المستمر.