يتطلب توجيه حركة البيانات المشفرة عالية الإنتاجية توافقاً دقيقاً بين بنيات المقابس الشبكية وطوابير معالجة الحزم في المستويات المنخفضة من نظام التشغيل. تعمل برمجيات الشبكات الافتراضية الخاصة التقليدية في الغالب داخل مساحة المستخدم (user space)، متبادلة الإطارات الشبكية عبر واجهات محاكاة الأجهزة TUN/TAP. يترتب على هذا النموذج خضوع كل حزمة مشفرة لتبديلين على الأقل في سياق التنفيذ بين المستخدم والنواة، مع استهلاك إضافي لموارد الذاكرة في نسخ البيانات بين مخازن التطبيق ومخازن حزم النواة (sk_buff).
يتخلص بروتوكول WireGuard من هذه المعوقات التشغيلية عبر الاندماج المباشر في مكدس شبكة نواة لينكس. ويشكل ملف المصدر drivers/net/wireguard/socket.c الركيزة الأساسية لهذا التصميم، حيث يتولى إنشاء مقابس UDP وربطها وتوجيهها ومضاعفة منافذها مباشرة في مساحة النواة. وبدلاً من التعامل مع المقابس كنقاط نهاية برمجية معزولة، يربط WireGuard المقابس الأصلية بالنواة بمسار الاستقبال المباشر فائق السرعة.
دورة حياة مقابس النواة وتهيئة المكدس المزدوج
عند إنشاء واجهة WireGuard جديدة (مثل wg0)، تخصص وحدة النواة البنى التشفيرية ثم تستدعي الدالة wg_socket_init(). وبخلاف التطبيقات التي تعتمد على استدعاءات نظام POSIX القياسية (socket() و bind())، يتعامل البروتوكول مباشرة مع واجهات برمجة النواة الداخلية:
int wg_socket_init(struct wg_device *wg, u16 port)
{
struct net *net = dev_net(wg->dev);
struct socket *new_4 = NULL, *new_6 = NULL;
struct udp_port_cfg port_4 = {
.family = AF_INET,
.local_ip.s_addr = htonl(INADDR_ANY),
.local_udp_port = htons(port),
.use_saddr = false
};
#if IS_ENABLED(CONFIG_IPV6)
struct udp_port_cfg port_6 = {
.family = AF_INET6,
.local_ip6 = IN6ADDR_ANY_INIT,
.local_udp_port = htons(port),
.use_saddr = false,
.ipv6_v6only = true
};
#endif
/* تخصيص مقبسي النواة IPv4 و IPv6 على نفس منفذ الاستماع */
ret = udp_sock_create(net, &port_4, &new_4);
if (ret < 0)
return ret;
#if IS_ENABLED(CONFIG_IPV6)
ret = udp_sock_create(net, &port_6, &new_6);
if (ret < 0) {
sock_release(new_4);
return ret;
}
#endif
/* ربط دوال الاعتراض المباشر لتغليف الأنفاق */
setup_udp_tunnel_sock(net, new_4, &tunnel_cfg);
setup_udp_tunnel_sock(net, new_6, &tunnel_cfg);
return 0;
}
يخصص التصميم المزدوج مؤشرين مستقلين من نوع struct socket داخل نفس نطاق الشبكة (dev_net(wg->dev)): أحدهما لبروتوكول IPv4 (AF_INET) والآخر لبروتوكول IPv6 (AF_INET6). وعند تفعيل دعم IPv6 في النواة، يحدد المعامل ipv6_v6only = true بوضوح لمنع تداخل العناوين، مما يضمن تدفق حزم البروتوكولين في مسارات مستقلة دون أعباء تحويل إضافية.
يرتبط كلا المقبسين بمنفذ UDP محدد بدقة. وإذا حُدد المنفذ بالقيمة صفر، تختار النواة منفذاً مؤقتاً متاحاً للمقبس الأول وتكرر ربطه للمقبس الثاني لضمان اتساق حركة البيانات من الخارج.
اعتراض الحزم بدون نسخ عبر udp_tunnel_sock
في برمجة المقابس العادية، يؤدي وصول الحزمة إلى إيقاظ خيط المراقبة epoll لتوضع البيانات في طابور استلام المقبس انتظاراً لقراءتها عبر recvmsg(). يسبب هذا الأسلوب اختناقاً في الذاكرة المخبأة للمعالج وزيادة زمن الاستجابة في التدفقات العالية.
يتجاوز WireGuard طوابير استقبال UDP النمطية بتسجيل معالج تغليف مباشر عبر الدالة setup_udp_tunnel_sock(). وتحدد البنية struct udp_tunnel_sock_cfg مؤشر الاستدعاء المباشر encap_rcv = wg_receive.
برنامج تشغيل بطاقة الشبكة الفعلية (Ring Buffer)
│
▼ (NAPI poll / NET_RX_SOFTIRQ)
ip_rcv() / ip6_rcv()
│
▼
udp_rcv()
│
▼
┌───────────────────────────────────────┐
│ udp_tunnel_sock_cfg.encap_rcv │ <--- اعتراض مباشر داخل النواة
│ (wg_receive في drivers/net/wireguard) │ (بدون انتقال لمساحة المستخدم)
└───────────────────────────────────────┘
│
▼
التحقق من ملفات تعريف الارتباط و MAC
│
▼
طوابير فك التشفير المتوازية (padata)
│
▼
wg_xmit() للتسليم إلى واجهة wg0
عند وصول الحزمة إلى الدالة udp_rcv() في قلب مكدس الشبكة، تتحقق النواة من وجود خطاف تغليف في المقبس. ومع رصد المؤشر encap_rcv، ينفذ المكدس فوراً الدالة wg_receive(sk, skb) داخل سياق المقاطعة البرمجية (NET_RX_SOFTIRQ).
يوفر هذا الإجراء مزايا هندسية حاسمة:
- انعدام نسخ البيانات (Zero-Copy): يبقى المخزن المؤقت للحزمة (
skb) ثابتاً في ذاكرة النواة من لحظة استلامه من بطاقة الشبكة وحتى اكتمال فك التشفير. - تحقق فوري من البيانات: يقرأ المعالج ترويسة الرسالة المكونة من 4 بايتات ويتحقق من رموز ملفات تعريف الارتباط وحالة مصافحة Noise قبل استهلاك موارد المعالج في حسابات ChaCha20-Poly1305.
- تلاشي النزاع على أقفال التزامن: نظراً لعدم تراكم الحزم في طوابير المقابس التقليدية، يتفادى النظام مشكلات انتفاخ المخازن الموقوتة وتأخيرات أقفال المقابس المشتركة.
حماية مسارات التوجيه من الحلقات عبر Fwmark
يعد تشكل حلقات التوجيه (routing loops) خطراً شائعاً عند إعداد الأنفاق: فإذا شُفرت الحزمة وأُرسلت كحزمة UDP عبر بطاقة الشبكة لكن جدول التوجيه الافتراضي (0.0.0.0/0) أعاد توجيه الحزمة المشفرة ذاتها إلى النفق، يدخل النظام في حلقة مغلقة تؤدي إلى استنفاد الذاكرة.
في ملف socket.c، يعالج WireGuard هذا الخطر في طبقة إرسال المقبس بإدراج علامة جدار الحماية (fwmark) والبحث المعزول في مسارات التوجيه:
static struct dst_entry *wg_socket_route4(struct wg_device *wg,
struct sockaddr_in *dst,
u32 fwmark)
{
struct flowi4 fl = {
.daddr = dst->sin_addr.s_addr,
.saddr = 0,
.flowi4_proto = IPPROTO_UDP,
.flowi4_mark = fwmark,
.flowi4_flags = FLOWI_FLAG_ANYSRC
};
struct rtable *rt = ip_route_output_key(dev_net(wg->dev), &fl);
return &rt->dst;
}
عند إرسال حزم المصافحة أو الحمولات المشفرة، تنسخ الدالة wg_socket_send_skb_to_peer() قيمة incoming_handshake_fwmark إلى معلمات التوجيه (flowi4_mark أو flowi6_mark). ويضبط مسؤولو الشبكات قواعد التوجيه المستند إلى السياسات (ip rule add not fwmark 51820 table 51820)، مما يضمن خروج كافة حزم UDP الخاصة بالنفق مباشرة إلى البوابة المادية دون المرور بجدول النفق.
وعلاوة على ذلك، يربط النظام المقبس بالواجهة المادية المحددة (sk_bound_dev_if) عند توفر معلومات العنوان المصدر src_if4 أو src_if6.
إدارة مخازن sk_buff أثناء الإرسال
يتجنب مسار الإرسال في socket.c تخصيص مخازن sk_buff جديدة قدر الإمكان. فعندما تدخل حزمة IP واجهة wg0 عبر الدالة wg_xmit()، يحسب برنامج التشغيل مسبقاً كافة المساحات الإضافية المطلوبة:
- 16 بايتاً لعلامة مصادقة Poly1305.
- 16 بايتاً لترويسة نقل WireGuard.
- 8 بايتات لترويسة UDP.
- 20 بايتاً لترويسة IPv4 (أو 40 بايتاً لترويسة IPv6).
وبفضل الحساب المسبق للمساحات في بداية الحزمة ونهايتها، تعدل الدالة skb_cow_head() المخزن المؤقت القائم مباشرة. وتُشفر البيانات في موضعها الأصلي، ثم تسلمها الدالة udp_tunnel_xmit_skb() مباشرة إلى طابور بطاقة الشبكة.
التطويرات المتقدمة في بنية RiceGuard
يعتمد تصميم WireGuard الافتراضي على زوج مقابس واحد لكل واجهة شبكية. وبالرغم من كفاية هذا النموذج للأجهزة الفردية والخوادم المتوسطة، فإن بوابات Anycast التابعة لشبكة RiceGuard، والتي تدير مئات الآلاف من الجلسات النشطة المتزامنة، تواجه تنافساً على خطوط الذاكرة المخبأة في المعالجات متعددة الأنوية.
طورت شبكة RiceGuard المعالجة النواتية لمقابس UDP بالتحسينات التالية:
- توجيه المقابس وفق تقنيات RSS/RPS: توزيع تدفقات حزم UDP على أنوية معالجة مخصصة استناداً إلى تجزئة عناوين الأطراف البعيدة، مما يمنع إبطال الذاكرة المخبأة في مقاطعات softirq.
- تتبع فوري لوحدة الإرسال القصوى (PMTU): رصد إشارات ICMP المتعلقة بالتجزئة مباشرة من مكدس النواة عبر دالة
sk_error_report()، وتعديل حجم الحزم فوراً دون قطع الجلسات التشفيرية. - عزل وحماية الطوابير بواسطة SO_RCVBUFFORCE: تخصيص سعات استيعاب تفضيلية للمسارات الموثقة أثناء هجمات تدفق UDP العنيفة لضمان استقرار اتصالات المشتركين.
يسهم دمج إدارة المقابس في صميم نواة لينكس وإلغاء الطبقات الوسيطة في تمكين WireGuard و RiceGuard من تقديم أداء حتمي بزمن استجابة يقاس بأجزاء من الملي ثانية وعزل شبكي فائق الدقة.



