DetectionSeverity

DetectionSeverity

#Overview

name: DetectionSeverity

The value of this variable can be defined or overridden in .ini config files. 19 .ini config files referencing this setting variable.

It is referenced in 21 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of DetectionSeverity is to manage different levels of severity for RPC DoS (Denial of Service) detection in Unreal Engine’s networking system. This variable is used to define and control the escalating states of DoS detection based on the amount of RPC (Remote Procedure Call) spam detected.

DetectionSeverity is primarily used in the networking subsystem of Unreal Engine, specifically in the RPC DoS detection module. It’s referenced in the Engine’s networking code, particularly in the RPCDoSDetection and DDoSDetection classes.

The value of this variable is typically set during the initialization of the DoS detection system. It’s populated from configuration files, specifically from the GEngineIni file, where different severity categories are defined.

DetectionSeverity interacts with several other variables and systems:

  1. ActiveState: Represents the current severity state.
  2. WorstActiveState: Tracks the worst severity state reached.
  3. CounterPerSecHistory: Stores historical data for quota calculations.

Developers should be aware of several things when using this variable:

  1. The severity states are defined in configuration files and loaded at runtime.
  2. The system automatically escalates or de-escalates between these states based on detected RPC activity.
  3. Each state can have different quota limits, cooldown times, and actions (like kicking players).

Best practices for using this variable include:

  1. Carefully configure the severity states in the engine configuration files.
  2. Monitor logs for state changes to track potential DoS attacks.
  3. Balance the states to provide adequate protection without unnecessarily impacting legitimate players.
  4. Regularly review and adjust the configuration based on observed network behavior and any detected attacks.

Remember that this system is crucial for protecting the game servers from potential DoS attacks, so it should be configured and monitored carefully.

#Setting Variables

#References In INI files

<Workspace>/Engine/Config/BaseEngine.ini:1652, section: [DDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1653, section: [DDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1654, section: [DDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1655, section: [DDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1656, section: [DDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1706, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1707, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1708, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1709, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1710, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1711, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1712, section: [GameNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1720, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1721, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1722, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1723, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1724, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1725, section: [BeaconNetDriver RPCDoSDetection]
<Workspace>/Engine/Config/BaseEngine.ini:1726, section: [BeaconNetDriver RPCDoSDetection]

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:424

Scope (from outer to inner):

file
function     void FRPCDoSDetection::InitConfig

Source code excerpt:



	DetectionSeverity.Empty();

	if (bRPCDoSDetection)
	{
		int32 HighestHistoryRequirment = 0;

		if (CurConfigObj != nullptr)
		{
			TArray<FString>& SeverityCategories = CurConfigObj->DetectionSeverity;
			const TCHAR* RPCDoSSectionName = URPCDoSDetectionConfig::GetConfigSectionName();

			for (const FString& CurCategory : SeverityCategories)
			{
				FString CurSection = FString(RPCDoSSectionName) + TEXT(".") + CurCategory;

				if (GConfig->DoesSectionExist(*CurSection, GEngineIni))
				{
					FRPCDoSStateConfig& CurState = DetectionSeverity.AddDefaulted_GetRef();

					CurState.SeverityCategory = CurCategory;

					bool bSuccess = CurState.LoadStructConfig(*CurSection, *GEngineIni);

					if (bSuccess)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:466

Scope (from outer to inner):

file
function     void FRPCDoSDetection::InitConfig

Source code excerpt:

		}

		if (DetectionSeverity.Num() > 0)
		{
			DetectionSeverity[ActiveState].ApplyState(*this);

			CounterPerSecHistory.SetNum(HighestHistoryRequirment);
		}
		else
		{
			UE_LOG(LogNet, Warning, TEXT("RPC DoS detection enabled, but no DetectionSeverity states specified! Disabling."));

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:506

Scope (from outer to inner):

file
function     void FRPCDoSDetection::UpdateSeverity_Private

Source code excerpt:

{
	bool bEscalate = Update == ERPCDoSSeverityUpdate::Escalate || Update == ERPCDoSSeverityUpdate::AutoEscalate;
	int32 NewStateIdx = FMath::Clamp(ActiveState + (bEscalate ? 1 : -1), 0, DetectionSeverity.Num()-1);

	// If kicking is disabled, and the new state is the kick state, exclude that state (otherwise RPC DoS Detection will become stuck)
	const bool bPreventKick = CVarAllowRPCDoSDetectionKicking.GetValueOnAnyThread() == 0;

	if (DetectionSeverity[NewStateIdx].bKickPlayer && bPreventKick)
	{
		NewStateIdx = ActiveState;
	}

	if (NewStateIdx != ActiveState)
	{

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:538

Scope (from outer to inner):

file
function     void FRPCDoSDetection::UpdateSeverity_Private

Source code excerpt:

			while (bCooloffReached && NewStateIdx > 0)
			{
				FRPCDoSStateConfig& PrevState = DetectionSeverity[NewStateIdx-1];
				FRPCDoSStateConfig& CurState = DetectionSeverity[NewStateIdx];
				int32 CurStateCooloffTime = CurState.CooloffTime;
				const TArray<int8>& CurStateTimePeriods = CurState.GetAllTimePeriods();
				FRPCDoSCounters CurPerPeriodHistory[16] = {};

				check(CounterPerSecHistory.Num() >= CurStateCooloffTime);

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:577

Scope (from outer to inner):

file
function     void FRPCDoSDetection::UpdateSeverity_Private

Source code excerpt:



		FRPCDoSStateConfig& OldState = DetectionSeverity[ActiveState];

		while (true)
		{
			FRPCDoSStateConfig& CurState = DetectionSeverity[NewStateIdx];

			ActiveState = NewStateIdx;

			CurState.ApplyState(*this);

			const TArray<int8>& NeededTimePeriods = DetectionSeverity[ActiveState].GetAllTimePeriods();

			RecalculatePeriodHistory(NeededTimePeriods, CounterPerPeriodHistory);


			// If escalating, keep escalating until the quota checks fail
			if (bEscalate && (HasHitQuota_Count(CounterPerPeriodHistory, TickScope.FrameCounter) ||
								HasHitQuota_Time(CounterPerPeriodHistory, TickScope.FrameCounter)))
			{
				NewStateIdx = FMath::Clamp(ActiveState + 1, 0, DetectionSeverity.Num()-1);

				if (DetectionSeverity[NewStateIdx].bKickPlayer && bPreventKick)
				{
					NewStateIdx = ActiveState;
					break;
				}

				if (NewStateIdx == ActiveState)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:616

Scope (from outer to inner):

file
function     void FRPCDoSDetection::UpdateSeverity_Private

Source code excerpt:



		FRPCDoSStateConfig& NewState = DetectionSeverity[NewStateIdx];

		if (bLogEscalate)
		{
			UE_LOG(LogNet, Warning, TEXT("Updated RPC DoS detection severity for '%s' from '%s' to '%s' (Reason: %s)"),
					*GetPlayerAddress(), *OldState.SeverityCategory, *NewState.SeverityCategory, LexToString(Reason));
		}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:714

Scope (from outer to inner):

file
function     void FRPCDoSDetection::UpdateSeverity_Private

Source code excerpt:

					{
						UE_LOG(LogNet, Warning, TEXT("Updated analytics RPC DoS detection severity from '%s' to '%s'."),
								*DetectionSeverity[OldWorstState].SeverityCategory, *NewState.SeverityCategory);

						// Log RPC's which could not be timed individually (as they won't display in analytics)
						if (UntimedRPCs.Num() > 0)
						{
							TStringBuilder<2048> RPCGroupStr;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetection.cpp:830

Scope (from outer to inner):

file
function     void FRPCDoSDetection::PreTickDispatch

Source code excerpt:

			SecondCounter.ResetRPCCounters();

			const TArray<int8>& NeededTimePeriods = DetectionSeverity[ActiveState].GetAllTimePeriods();

			RecalculatePeriodHistory(NeededTimePeriods, CounterPerPeriodHistory);


			SecondsIncrementer++;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Net/RPCDoSDetectionConfig.h:74

Scope (from outer to inner):

file
class        class URPCDoSDetectionConfig : public UObject

Source code excerpt:

	/** Names of the different RPC DoS detection states, for escalating severity, depending on the amount of RPC spam */
	UPROPERTY(config)
	TArray<FString> DetectionSeverity;

	/** The amount of time since the client connected, before time-based checks should become active (to reduce false positives) */
	UPROPERTY(config)
	int32 InitialConnectToleranceMS;

	UE_DEPRECATED(5.1, "This property is no longer supported. Use RPCBlockAllowlist.")

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Public/Net/RPCDoSDetection.h:1169

Scope (from outer to inner):

file
class        class FRPCDoSDetection : protected FRPCDoSState

Source code excerpt:


	/** The different RPC DoS detection states, of escalating severity, depending on the amount of RPC spam */
	TArray<FRPCDoSStateConfig> DetectionSeverity;

	/** The currently active RPC DoS severity state settings */
	int8 ActiveState												= 0;

	/** The worst RPC DoS severity state that has been active */
	int8 WorstActiveState											= 0;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Public/Net/RPCDoSDetection.h:1196

Scope: file

Source code excerpt:

	double LastPerSecQuotaBegin										= 0.0;

	/** Stores enough per second quota history, to allow all DetectionSeverity states to recalculate if their CooloffTime is reached */
	TArray<FRPCDoSCounters> CounterPerSecHistory;

	/** The last written index of CounterPerSecHistory */
	int32 LastCounterPerSecHistoryIdx								= 0;

	/** Counter history which is precalculated/cached from CounterPerSecHistory, for tracking per-period time (up to a maximum of 16s) */

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:67

Scope (from outer to inner):

file
function     FDDoSDetection::FDDoSDetection

Source code excerpt:

	, bHitFrameNonConnLimit(false)
	, bHitFrameNetConnLimit(false)
	, DetectionSeverity()
	, ActiveState(0)
	, WorstActiveState(0)
	, LastMetEscalationConditions(0.0)
	, bMetEscalationConditionsThisFrame(false)
	, bDDoSLogRestrictions(false)
	, DDoSLogSpamLimit(0)

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:111

Scope (from outer to inner):

file
function     void FDDoSDetection::InitConfig

Source code excerpt:

	DDoSLogSpamLimit = DDoSLogSpamLimit > 0 ? DDoSLogSpamLimit : 64;

	DetectionSeverity.Empty();

	UE_LOG(LogNetCore, Log, TEXT("DDoS detection status: detection enabled: %d analytics enabled: %d"), bDDoSDetection, bDDoSAnalytics);

	if (bDDoSDetection)
	{
		TArray<FString> SeverityCatagories;
		int32 HighestCooloffTime = 0;

		GConfig->GetArray(DDoSSection, TEXT("DetectionSeverity"), SeverityCatagories, GEngineIni);

		for (const FString& CurCategory : SeverityCatagories)
		{
			FString CurSection = FString(DDoSSection) + TEXT(".") + CurCategory;

			if (GConfig->DoesSectionExist(*CurSection, GEngineIni))
			{
				FDDoSStateConfig& CurState = DetectionSeverity.AddDefaulted_GetRef();
				int32 EscalateTime32 = 0;

				CurState.SeverityCategory = CurCategory;

				GConfig->GetBool(*CurSection, TEXT("bSendEscalateAnalytics"), CurState.bSendEscalateAnalytics, GEngineIni);
				GConfig->GetInt(*CurSection, TEXT("EscalateQuotaPacketsPerSec"), CurState.EscalateQuotaPacketsPerSec, GEngineIni);

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:155

Scope (from outer to inner):

file
function     void FDDoSDetection::InitConfig

Source code excerpt:

		}

		if (DetectionSeverity.Num() > 0)
		{
			DetectionSeverity[ActiveState].ApplyState(*this);

			CounterPerSecHistory.SetNum(HighestCooloffTime);
		}
		else
		{
			UE_LOG(LogNetCore, Warning, TEXT("DDoS detection enabled, but no DetectionSeverity states specified! Disabling."));

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:177

Scope (from outer to inner):

file
function     void FDDoSDetection::UpdateSeverity

Source code excerpt:

void FDDoSDetection::UpdateSeverity(bool bEscalate)
{
	int8 NewState = static_cast<int8>(FMath::Clamp(ActiveState + (bEscalate ? 1 : -1), 0, DetectionSeverity.Num()-1));

	if (NewState != ActiveState)
	{
		double CurTime = FPlatformTime::Seconds();

		if (bEscalate)

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:195

Scope (from outer to inner):

file
function     void FDDoSDetection::UpdateSeverity

Source code excerpt:

			while (bCooloffReached && NewState > 0)
			{
				FDDoSStateConfig& PrevState = DetectionSeverity[NewState-1];
				int32 CurStateCooloffTime = DetectionSeverity[NewState].CooloffTime;

				check(CounterPerSecHistory.Num() >= CurStateCooloffTime);

				for (int32 SecondsDelta=0; SecondsDelta<CurStateCooloffTime; SecondsDelta++)
				{
					int32 CurIdx = LastCounterPerSecHistoryIdx - SecondsDelta;

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:228

Scope (from outer to inner):

file
function     void FDDoSDetection::UpdateSeverity

Source code excerpt:



		FDDoSStateConfig& OldState = DetectionSeverity[ActiveState];
		FDDoSStateConfig& CurState = DetectionSeverity[NewState];

		// If we're at anything other than the base state, then disable all unnecessary logs
		bDDoSLogRestrictions = NewState > 0;
		ActiveState = NewState;
		bMetEscalationConditionsThisFrame = false;

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:291

Scope (from outer to inner):

file
function     void FDDoSDetection::PreFrameReceive

Source code excerpt:

		}

		DetectionSeverity[ActiveState].ApplyAdjustedState(*this, FMath::Max(0.25f, FrameAdjustment));

		if (((StartFrameRecvTimestamp - LastPerSecQuotaBegin) - 1.0) > 0.0)
		{
			UE_CLOG(DroppedPacketCounter > 0, LogNetCore, Warning,
				TEXT("DDoS Detection dropped '%i' packets during last second (bHitFrameNonConnLimit: %i, bHitFrameNetConnLimit: %i, ")
				TEXT("DetectionSeverity: %s)."),
				DroppedPacketCounter, (int32)bHitFrameNonConnLimit, (int32)bHitFrameNetConnLimit,
				*DetectionSeverity[ActiveState].SeverityCategory);


			// Record the last quota
			check(CounterPerSecHistory.Num() > 0);

			LastCounterPerSecHistoryIdx++;

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Private/Net/Core/Misc/DDoSDetection.cpp:365

Scope (from outer to inner):

file
function     bool FDDoSDetection::CheckNonConnQuotasAndLimits

Source code excerpt:

		const int32 PrevState = ActiveState - 1;

		if (DetectionSeverity[PrevState].HasHitQuota(*this, TimePassedMS))
		{
			LastMetEscalationConditions = CurTime;
			bMetEscalationConditionsThisFrame = true;
		}
	}

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Public/Net/Core/Misc/DDoSDetection.h:304

Scope (from outer to inner):

file
class        class FDDoSDetection : protected FDDoSPacketCounters, protected FDDoSState

Source code excerpt:


	/** The different DDoS detection states, of escalating severity, depending on the strength of the DDoS */
	TArray<FDDoSStateConfig> DetectionSeverity;

	/** The currently active DDoS severity state settings */
	int8 ActiveState;

	/** The worst DDoS severity state that has been active - used for limiting analytics events */
	int8 WorstActiveState;

#Loc: <Workspace>/Engine/Source/Runtime/Net/Core/Public/Net/Core/Misc/DDoSDetection.h:342

Scope (from outer to inner):

file
class        class FDDoSDetection : protected FDDoSPacketCounters, protected FDDoSState

Source code excerpt:

	double LastPerSecQuotaBegin;

	/** Stores enough per second quota history, to allow all DetectionSeverity states to recalculate if their CooloffTime is reached */
	TArray<FDDoSPacketCounters> CounterPerSecHistory;

	/** The last written index of CounterPerSecHistory */
	int32 LastCounterPerSecHistoryIdx;