العودة إلى المدونة

شرح ثغرة أداء خادم UEFN: تحصين Netcode الخاص بـ Unreal Engine

نُشر في 24 فبراير 2026
شرح ثغرة أداء خادم UEFN: تحصين Netcode الخاص بـ Unreal Engine

يعرف كل مطور Multiplayer سيناريو الكابوس: يتصل لاعب واحد سيئ النية بخادمك، ويقوم بسلسلة من الإجراءات التي تبدو حميدة، وفجأة ينخفض tick rate من 60 هرتز إلى أرقام فردية. يتوقف الخادم بالكامل عن العمل، مما يؤثر على عشرات اللاعبين الأبرياء.

مؤخراً، تم الإبلاغ عن ثغرة أداء حرجة في خادم UEFN على منتديات Unreal Engine من قبل المطور Vysena Woyka. يوضح التقرير تقنية قابلة للتكرار بنسبة 100% تسبب تدهوراً شديداً على مستوى الخادم في خرائط Unreal Editor for Fortnite (UEFN). تزداد خطورة الثغرة مع انضمام المزيد من اللاعبين، ولا تتطلب أي أدوات خارجية على الإطلاق، ولديها القدرة على التسبب في عدم استقرار كامل للخادم مع التنفيذ المطول.

نظراً لأن خطوات إعادة الإنتاج الدقيقة يتم الحفاظ على سريتها لمنع سوء الاستخدام على نطاق واسع، يتساءل العديد من المطورين: كيف تعمل ثغرة كهذه فعلياً في الكواليس؟ والأهم من ذلك، كيف أحمي خوادم Unreal Engine dedicated servers المخصصة الخاصة بي من هجمات مماثلة؟

في هذا التعمق التقني، سنقوم بتشريح بنية تدهور الأداء من جانب الخادم في Unreal Engine. سنستكشف النواقل الشائعة التي يستخدمها اللاعبون الخبيثون لخنق dedicated servers، وكيفية تنفيذ تحقق صارم من جانب الخادم باستخدام C++، وكيفية تصميم بنيتك التحتية لتحقيق أقصى قدر من المرونة.

تشريح ثغرة خادم Unreal Engine

لفهم كيف يمكن للاعب إسقاط خادم بدون أدوات اختراق خارجية، يجب أن تفهم كيف يتعامل Unreal Engine مع Game Loop الرئيسية. خوادم Unreal Engine dedicated servers تعمل في الغالب بخيط واحد (single-threaded) عندما يتعلق الأمر بـ Game Logic. بينما يمكن نقل مهام مثل Physics Simulation (عبر محرك Chaos) والتحميل غير المتزامن إلى worker threads، فإن وظيفة Tick الأساسية لـ Actors، و Replication Serialization، وتنفيذ RPC (Remote Procedure Call) كلها تحدث في Game Thread.

إذا كان الخادم يعمل بسرعة 30 tick في الثانية (30 هرتز)، فلديه بالضبط 33.3 مللي ثانية لمعالجة جميع player inputs، وتحديث Game State، وحساب الفيزياء، وتسلسل بيانات الشبكة للإطار التالي. إذا تمكن لاعب من إجبار الخادم على تنفيذ عملية تستغرق 50 مللي ثانية لمعالجتها، فإن tick rate للخادم ينخفض فوراً إلى 20 هرتز.

عندما ينخفض tick rate لخادمك بشكل كبير، لا تحصل فقط على lag مرئي، بل تحصل على تباعد كارثي في الحالة (state divergence). لقد غطينا تداعيات ذلك باستفاضة في دليلنا التقني حول The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.

بدون استخدام أدوات حقن الذاكرة أو محرري الحزم، تعتمد ثغرات الأداء داخل اللعبة عادةً على أحد ثلاثة نواقل: RPC Flooding، أو Physics/Collision Overload، أو Replication Saturation.

الناقل 1: RPC Flooding وفشل التحقق

الطريقة الأكثر شيوعاً لتعطيل خادم Unreal Engine أو إبطائه هي إغراقه بـ Server RPCs. إذا قام العميل بربط Server RPC بعجلة الماوس أو بمدخلات framerate غير مقفلة، فيمكنه إرسال مئات الطلبات في الثانية إلى الخادم.

إذا كان Server RPC الخاص بك يحتوي على منطق معقد - مثل إنشاء Actor، أو إجراء line trace (Raycast)، أو التكرار عبر مصفوفات كبيرة - فسيضطر الخادم إلى تنفيذ هذا المنطق المكلف مئات المرات في كل إطار.

يوفر Unreal Engine ماكرو WithValidation لـ RPCs، لكن العديد من المطورين يستخدمونه فقط للتحقق مما إذا كان المؤشر صالحاً، متجاهلين تماماً Rate Limiting.

الحل: تنفيذ RPC Rate Limiter باستخدام C++

لحماية خادمك، يجب عليك تنفيذ Rate Limiting صارم على جميع الاتصالات من العميل إلى الخادم. إليك نهج تم اختباره في المعارك لتقنين Server RPCs باستخدام Actor Component مخصص في C++.

أولاً، نحدد منطق تحديد المعدل في ملف الرأس (header file):

// RateLimiterComponent.h
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "RateLimiterComponent.generated."

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MULTIPLAYER_API URateLimiterComponent : public UActorComponent
{
    GENERATED_BODY()

public:	
    URateLimiterComponent();

    // Checks if the action is allowed. Returns false if the client is spamming.
    UFUNCTION(BlueprintCallable, Category = "Security")
    bool CanExecuteAction(FName ActionName, float CooldownTime);

private:
    // Maps action names to the last time they were executed
    TMap<FName, float> LastExecutionTimes;

    // Threshold for maximum allowed actions per second before flagging the player
    const int32 MaxActionsPerSecond = 20;
    int32 CurrentActionCount;
    float LastResetTime;
};

بعد ذلك، نقوم بتنفيذ منطق التحقق في ملف CPP. لاحظ كيف نستخدم وقت الخادم (GetWorld()->GetTimeSeconds()) لضمان عدم تمكن العميل من تزييف وقته المحلي لتجاوز فترة التهدئة (cooldown).

// RateLimiterComponent.cpp
#include "RateLimiterComponent.h"

URateLimiterComponent::URateLimiterComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
    CurrentActionCount = 0;
    LastResetTime = 0.0f;
}

bool URateLimiterComponent::CanExecuteAction(FName ActionName, float CooldownTime)
{
    // Only run this logic on the server
    if (!GetOwner()->HasAuthority())
    {
        return false;
    }

    float CurrentTime = GetWorld()->GetTimeSeconds();

    // Reset the global action counter every second
    if (CurrentTime - LastResetTime >= 1.0f)
    {
        CurrentActionCount = 0;
        LastResetTime = CurrentTime;
    }

    // Global spam check
    CurrentActionCount++;
    if (CurrentActionCount > MaxActionsPerSecond)
    {
        UE_LOG(LogTemp, Warning, TEXT("Player %s is exceeding global RPC limits!"), *GetOwner()->GetName());
        return false;
    }

    // Specific action cooldown check
    if (LastExecutionTimes.Contains(ActionName))
    {
        float LastTime = LastExecutionTimes[ActionName];
        if (CurrentTime - LastTime < CooldownTime)
        {
            // Client is spamming this specific action
            return false;
        }
    }

    // Update the execution time and allow the action
    LastExecutionTimes.Add(ActionName, CurrentTime);
    return true;
}

الآن، عند تنفيذ وظيفة Server_PerformAction_Validate الخاصة بك، يمكنك رفض RPC ديناميكياً إذا كان العميل يغرق الخادم به:

bool AMyPlayerController::Server_PerformExpensiveAction_Validate()
{
    // If the rate limiter returns false, the RPC is rejected and the client is disconnected
    if (URateLimiterComponent* RateLimiter = GetComponentByClass<URateLimiterComponent>())
    {
        return RateLimiter->CanExecuteAction(FName("ExpensiveAction"), 0.5f);
    }
    return true;
}

الناقل 2: Physics و Collision Overload

ناقل ثغرة شائع آخر (ومشتبه به بشدة في بيئات sandbox مثل UEFN) هو التحميل الزائد للفيزياء. إذا كان بإمكان اللاعبين إنشاء كائنات، أو إسقاط عناصر، أو التلاعب بـ Physics Bodies، فيمكنهم تعمد تكديس مئات الكائنات في مساحة ضيقة.

عندما تتداخل Physics Bodies، يحاول محرك الفيزياء Chaos حل التصادمات. إذا تم إجبار 500 كائن على التواجد في نفس إحداثيات المساحة، فإن حسابات حل التصادم تنمو بشكل كبير، مما يتسبب في قفل كامل للمعالج (CPU) على الخادم.

علاوة على ذلك، إذا كانت هذه الكائنات تحتوي على bGenerateOverlapEvents مضبوطة على true، فسيقوم الخادم بتشغيل OnComponentBeginOverlap مئات الآلاف من المرات في كل إطار.

الحل: تقليم التصادم الهجومي (Aggressive Collision Culling)

لمنع تدهور الخادم القائم على الفيزياء، يجب عليك فصل الفيزياء المرئية عن التحقق من التصادم من جانب الخادم.

  1. تعطيل Overlaps على العناصر المسقطة: إذا أسقط لاعب عنصراً، فقم بتعطيل bGenerateOverlapEvents على الخادم بعد استقراره.
  2. تحديد حدود الإنشاء (Spawn Limits): قم ببرمجة حد أقصى لكثافة الكائنات الفيزيائية لكل قطاع شبكة.
  3. تقنين منطق التداخل: إذا كان يجب عليك استخدام overlaps، فلا تنفذ منطقاً معقداً مباشرة داخل حدث التداخل. بدلاً من ذلك، قم بتعيين flag ومعالجة التداخل في دفعة محكومة أثناء وظيفة Tick.

الناقل 3: Replication Saturation وخنق عرض النطاق الترددي

نظام التزامن في Unreal Engine قوي، ولكنه يعتمد أيضاً بشكل كبير على المعالج. يجب على الخادم التكرار عبر كل Actor متزامن، والتحقق مما إذا كان ذا صلة بعميل معين، ومقارنة خصائصه بآخر حالة معترف بها، وتسلسل التغييرات.

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

الحل: تحسين NetUpdateFrequency

لا تترك NetUpdateFrequency أبداً عند قيمتها الافتراضية (100.0) للجهات غير الحرجة. يجب عليك توسيع تردد التزامن ديناميكياً بناءً على قرب اللاعب وحالة الإجراء.

بالإضافة إلى ذلك، يجب عليك استخدام DefaultEngine.ini لفرض حدود صارمة لعرض النطاق الترددي على dedicated server الخاص بك. هذا يمنع عميلاً خبيثاً واحداً من إجبار الخادم على معالجة تدفقات حزم ضخمة:

[/Script/OnlineSubsystemUtils.IpNetDriver]
MaxClientRate=15000
MaxInternetClientRate=10000
NetServerMaxTickRate=30
LanServerMaxTickRate=30
ConnectionTimeout=15.0
InitialConnectTimeout=30.0

من خلال تحديد MaxClientRate ، سيقوم الخادم ببساطة بإسقاط الحزم الزائدة من عميل يحاول إغراق قناة الشبكة، مما يحافظ على دورات المعالج للاعبين الشرعيين.

مرونة البنية التحتية: التعامل مع الحتمي

حتى مع كود C++ مثالي، ستحدث ثغرات zero-day. عندما تضرب ثغرة مثل خطأ أداء خادم UEFN لعبتك المخصصة، ستصل عقد الخادم حتماً إلى استخدام 100% من المعالج وتتعطل.

إذا كانت بنية أسطول الخوادم بالكامل عرضة لنقطة فشل واحدة، فإنك تخاطر بفقدان اللاعبين بشكل دائم. إن بناء بنية تحتية مرنة مع توجيه fallback مناسب هو شيء ندعو إليه بشدة، تماماً كما ناقشنا في تحليلنا المعماري لـ The Stop Killing Games Campaign Vs Live Ops Architecting Server Fallbacks.

عندما يتعطل خادم بسبب ثغرة، يجب أن يكتشف backend الخاص بك العقدة الميتة فوراً، ويقوم بتشغيل مثيل جديد، وينقل اللاعبين المتأثرين بسلاسة إلى طابور matchmaking دون فقدان بياناتهم الدائمة.

يتطلب بناء هذا بنفسك إعداد load balancers مخصصة، و database sharding، وتنسيق الحاويات (مثل Kubernetes)، وإدارة شهادات SSL - وهو ما يستغرق بسهولة 4-6 أشهر من العمل الهندسي المخصص. مع horizOn، تأتي خدمات backend هذه مسبقة التكوين. تراقب بنيتنا التحتية تلقائياً صحة الخادم، وتقوم بتوسيع المثيلات تلقائياً بناءً على حمل المعالج، وتتعامل مع توجيه جلسات اللاعبين، مما يتيح لك التركيز على إصلاح كود اللعبة بدلاً من محاربة بنيتك التحتية.

5 ممارسات فضلى لاستقرار الخادم

لحماية لعبة Multiplayer الخاصة بك على Unreal Engine ضد ثغرات الأداء، قم بتنفيذ هذه القواعد المعمارية الخمس فوراً:

  1. تنفيذ حصص RPC صارمة: لا تثق أبداً في معدل إدخال العميل. استخدم مكون rate limiter في C++ الموضح أعلاه لفرض cooldowns صارمة على كل Server RPC.
  2. تطهير ناقلات الحركة: تعمل اختراقات السرعة وثغرات الانتقال الآني عن طريق إرسال ناقلات ضخمة إلى الخادم. قم دائماً بتقييد طلبات AddMovementInput و SetActorLocation من جانب الخادم مقابل أقصى سرعة حركة نظرية للشخصية.
  3. استخدام Replication Graph: إذا كانت لعبتك تدعم أكثر من 40 لاعباً، فسيصبح نظام التزامن الافتراضي عنق زجاجة. قم بتنفيذ Unreal Engine Replication Graph لتجميع الجهات مكانياً وتقليل العبء على المعالج لفحوصات الصلة بشكل كبير.
  4. تعطيل المرئيات من جانب الخادم: يجب ألا تقوم dedicated servers أبداً بتحميل UI أو أنظمة الجسيمات أو رسوم skeletal mesh المتحركة. تأكد من أن إعدادات مشروعك تجرد هذه الأصول بصرامة من بناء dedicated server لتوفير الذاكرة ودورات المعالج.
  5. مراقبة Tick Rate ديناميكياً: قم بتنفيذ نظام فرعي من جانب الخادم يراقب متوسط delta time. إذا اكتشف الخادم انخفاض tick rate إلى أقل من 15 هرتز لأكثر من 5 ثوانٍ، فيجب عليه تلقائياً إيقاف المهام الخلفية غير الضرورية (مثل إنشاء AI أو إنشاء الأحداث المحيطة) للتعافي.

الخاتمة

تعد ثغرة أداء خادم UEFN الأخيرة تذكيراً صارخاً بأن تطوير ألعاب Multiplayer هو في جوهره تمرين في الأمن السيبراني. لا يمكنك ببساطة الوثوق في أن اللاعبين سيتفاعلون مع لعبتك كما هو مقصود. كل RPC، وكل تفاعل فيزيائي، وكل متغير متزامن هو ناقل هجوم محتمل.

من خلال تحويل عقليتك إلى نموذج "Server-Authoritative, Client-Distrusted"، وتحسين منطق التزامن في C++ بعمق، وتنفيذ حدود معدل صارمة، يمكنك تحصين لعبتك ضد هذه الأنواع من أعطال الأداء الكارثية.

عندما تجمع بين كود لعبة مضاد للرصاص وبنية تحتية للخادم ذاتية التوسع والشفاء، فإنك تخلق بيئة تصبح فيها الثغرات مجرد إزعاجات بسيطة بدلاً من كوارث تقتل اللعبة. هل أنت مستعد لتوسيع نطاق Multiplayer backend الخاص بك دون صداع dev-ops؟ جرب horizOn مجاناً ودعنا نتولى تنسيق خوادمك.


المصدر: [CRITICAL] Server Performance Exploit