なぜあなたの Dedicated Servers はフリーズするのか:Unreal Engine サーバーにおける DDoS Protection の現実
すべての Multiplayer ゲーム開発者が恐れるのは、突然の不可解なサーバーフリーズです。あなたの Dedicated instance は安定した 30 Tick rate で完璧に動作していましたが、予告なしにシミュレーション全体が停止します。プレイヤーはマップ上で Rubber-banding を起こし、RPCs はドロップし、その数分後、致命的な Connection timeout がマッチを終了させます。本能的に最新の Movement replication コードや複雑な物理計算を疑うかもしれませんが、プレイヤーベースが拡大している場合、現実はもっと悪意に満ちていることが多いのです。つまり、あなたのインフラが組織的な Distributed Denial of Service (DDoS) 攻撃の犠牲になっているということです。
Unreal Engine 開発者コミュニティからの最近の報告では、ゲームサーバーを標的とした組織的な DDoS 攻撃が急増しており、特に Battle Royale のような大規模モードやカスタムの Creative インスタンスに影響を与えています。これらの攻撃はサーバーの Network processing thread を完全に圧倒し、深刻な Lag、グローバルな同期ズレ、そして最終的には Hard crashes を引き起こします。
Indie 開発者や AA スタジオにとって、強力な Unreal Engine Server DDoS Protection を実装することはもはやオプションではなく、あらゆる Live-ops ゲームにとって必須の要件です。このテクニカルな解説では、これらの攻撃がどのように Unreal Engine Netcode を操作するのか、悪意のある Flood と標準的なネットワークの不調をどのように見分けるのか、そしてゲームのインフラを強化するために取ることができる具体的な手順を分析します。
Unreal Engine サーバークラッシュの構造
サーバーを保護する方法を理解するには、まず Unreal Engine が着信ネットワークトラフィックをどのように処理するかを理解する必要があります。Unreal は NetDriver によって管理されるカスタムの UDP ベースのプロトコルを利用します。UDP はコネクションレスであるため、インターネット上のあらゆるクライアントが、正式な Handshake なしにサーバーのオープンポートにパケットを送信できてしまいます。
レイヤー 4 帯域消費型攻撃 vs レイヤー 7 アプリケーション層攻撃
ほとんどのサーバークラッシュは、次の 2 つのタイプのネットワーク攻撃のいずれかによって引き起こされます。
1. 帯域消費型 UDP Flood (レイヤー 4): これはブルートフォース攻撃です。Botnet がサーバーのパブリック IP アドレスとポートに対して、1 秒間に数ギガバイトのゴミ UDP パケットを送りつけます。サーバーのネットワークインターフェースカード (NIC) と OS の Network stack は完全に飽和状態になります。Unreal Engine がパケットを確認する間もなく、基礎となるマシンの Bandwidth が枯渇するか、CPU 割り込み(CPU interrupts)が発生し、正当なプレイヤーのトラフィックが完全にドロップされます。
2. アプリケーション層の枯渇 (レイヤー 7):
これらの攻撃ははるかに巧妙です。ランダムなゴミを送信する代わりに、攻撃者は Packet capture ツールや改造されたゲームクライアントを使用して、正しくフォーマットされた Unreal Engine 接続リクエスト(NMT_Hello や NMT_Login パケットなど)や、特定の重い RPC スパムを送信します。NetDriver はこれらの見かけ上有効なパケットを受け入れ、処理のために Game thread に渡します。サーバー CPU は 100% に急上昇し、数千の偽のログイン Handshake を解析したり、存在しないセッションチケットを検証したり、レプリケートされた関数の複雑な文字列パラメータのためにメモリを割り当てようとしたりします。このトラフィックは標準的な Firewall にとって正当なプレイヤーアクティビティと同一に見えるため、基本的な DDoS 保護をバイパスします。これによりサーバーの Tick rate は即座に低下し、フリーズしたインスタンスを Watchdog プロセスが強制終了する直前にプレイヤーが経験する極端なテレポートや Rollback 挙動が発生します。
攻撃の診断:悪意か、それとも単なる Netcode のバグか?
サーバーが攻撃を受けていると断定する前に、壊滅的な Replication バグを排除する必要があります。単一のクライアントが RPC コールの無限ループをトリガーした場合、レイヤー 7 DDoS を模倣することがあります。パニックになる前に、クラッシュログとメトリクスを確認してください。Memory allocation が急増しているのにネットワークトラフィックが低い場合は、Replication の問題である可能性があります。その解決策については、Zero Ping Spikes Complete Freeze The Ultimate Uefn Server Crash Fix Protocol を参照してください。
しかし、外部モニタリングでインバウンドトラフィックがベースラインの ~50 Mbps から 5 Gbps に急増している場合や、サーバーログに数秒の間に一意の IP アドレスからの LogNet: NotifyAcceptingConnection メッセージが数千件表示されている場合は、組織的な攻撃に対処していることになります。
Netcode の強化:C++ での接続スロットリングの実装
真の帯域消費型 DDoS 対策はインフラレベルで行う必要がありますが(これについては後述します)、AGameModeBase でアグレッシブな Rate Limiting を直接実装することで、レイヤー 7 アプリケーション枯渇から Unreal Engine サーバーを保護できます。
PreLogin 関数をオーバーライドすることで、サーバーが完全な APlayerController を割り当て、プレイヤーをワールドにロードする高コストなプロセスを開始する前に、接続試行をインターセプトできます。
悪意のある IP アドレスからの急速な接続試行をスロットリングするための堅牢な C++ 実装は以下の通りです。
// In YourGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "YourGameMode.generated.h"
UCLASS()
class YOURGAME_API AYourGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;
private:
// Maps to track connection attempts per IP
TMap<FString, int32> ConnectionAttempts;
TMap<FString, float> LastConnectionTime;
// Configuration limits
const int32 MaxAttemptsPerMinute = 4;
const float LockoutTimeSeconds = 60.0f;
};
// In YourGameMode.cpp
#include "YourGameMode.h"
#include "Engine/World.h"
void AYourGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
// Always call super first to handle native bans and base logic
Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
// If an error was already generated (e.g., server full), exit early
if (!ErrorMessage.IsEmpty())
{ return;
}
// The Address string usually arrives in the format "IP:Port"
FString ClientIP;
FString PortStr;
if (!Address.Split(TEXT(":"), &ClientIP, &PortStr))
{ ClientIP = Address; // Fallback if no port is appended
}
float CurrentTime = GetWorld()->GetTimeSeconds();
// Check if this IP is currently in our tracking map
if (LastConnectionTime.Contains(ClientIP))
{ float TimeSinceLastAttempt = CurrentTime - LastConnectionTime[ClientIP];
// If they are connecting too fast and have exceeded the attempt limit
if (TimeSinceLastAttempt < LockoutTimeSeconds && ConnectionAttempts[ClientIP] >= MaxAttemptsPerMinute)
{ ErrorMessage = TEXT("Connection rate limit exceeded. Please wait 60 seconds.");
UE_LOG(LogGameMode, Warning, TEXT("DDoS Mitigation: Rejected rapid connection attempt from %s."), *ClientIP);
return;
}
// If the lockout window has passed, reset their counter
if (TimeSinceLastAttempt >= LockoutTimeSeconds)
{ ConnectionAttempts[ClientIP] = 0;
} }
// Increment the attempt counter and update the timestamp
int32 Attempts = ConnectionAttempts.FindOrAdd(ClientIP, 0);
ConnectionAttempts[ClientIP] = Attempts + 1;
LastConnectionTime.Add(ClientIP, CurrentTime);
UE_LOG(LogGameMode, Log, TEXT("Connection validation passed. Attempt %d from %s"), ConnectionAttempts[ClientIP], *ClientIP);
}
このコードが重要な理由
この実装は、着信するすべてのリクエストの IP アドレスを追跡します。単一の IP が 60 秒以内に 4 回を超えて接続しようとした場合、サーバーは PreLogin で接続を能動的に拒否します。ここでの接続拒否は、エンジンにアクターを生成させ、初期状態をレプリケートさせ、その後にプレイヤーをキックさせるよりも、CPU サイクルにおいて大幅に低コストです。このシンプルなコードブロックは、サーバーがレイヤー 7 のスクリプトキディによる攻撃を耐え抜くか、完全に応答不能な状態に陥るかの分かれ目になります。
Unreal Engine ネットワーク設定のチューニング
C++ のロジック以外に、DefaultEngine.ini ファイルには、サーバーが消費できる帯域幅を左右するいくつかの重要な構成パラメータが含まれています。これらをデフォルト設定のままにしておくことは、大きな脆弱性となります。攻撃者がサーバーを Flood し、Bandwidth の制限がない場合、サーバーはすべてを処理しようとし、即座に CPU を最大まで使い果たします。
ネットワークトラフィックに厳格な上限を設ける必要があります。DefaultEngine.ini を開き、IpNetDriver に以下の強化された制限を適用してください。
[/Script/Engine.Player]
; Limit maximum connection speed to 10 MB/s to prevent single-client bandwidth exhaustion
ConfiguredInternetSpeed=10485760
ConfiguredLanSpeed=10485760
[/Script/OnlineSubsystemUtils.IpNetDriver]
; Maximum data rate allowed per client (in bytes). 100kb/s is usually plenty for an FPS.
MaxClientRate=100000
MaxInternetClientRate=100000
; Cap the server tick rate to ensure predictable CPU load.
NetServerMaxTickRate=30
; Aggressively drop unresponsive clients. Defaults are often too long (60s+).
ConnectionTimeout=15.0
InitialConnectTimeout=15.0
; How often the server expects a keep-alive ping.
KeepAliveTime=0.2
; Limit the number of ports the server will try to bind to upon startup.
MaxPortCountToTry=512
ConnectionTimeout を 15.0 秒に短縮することで、なりすまし DDoS 攻撃によって生成されたハーフオープン接続やデッド接続を迅速にパージし、正当なプレイヤーのためにメモリとネットワークスロットを解放できます。
インフラの問題:既に到着したものをブロックすることはできない
上記の C++ スロットリングと INI 設定は、アプリケーション層の枯渇からは保護してくれますが、レイヤー 4 帯域消費型攻撃に関しては致命的な欠陥があります。それは、Unreal Engine サーバーがパケットのドロップを決定した時点で、Bandwidth は既に消費されているということです。
攻撃者が 10 Gbps の Botnet をサーバーに向け、ホスティングプロバイダーが 1 Gbps のネットワークインターフェースしか提供していない場合、C++ コードがいかに最適化されていても関係ありません。サーバーにつながるパイプは物理的に詰まっています。正当なプレイヤーのトラフィックは通り抜けることができず、最近のコミュニティ報告にあるような大規模なデシンクやテレポートが発生します。
レイヤー 4 攻撃を軽減するには、インフラレベルの防御戦略が必要です。
自前でのアプローチ
独自の専用ベアメタルサーバーや標準の EC2 インスタンスを実行している場合は、手動で対策パイプラインを構築する必要があります。これには通常、以下が含まれます。
- Reverse Proxy の設定: 実際の Unreal Engine サーバーの IP を公開してはいけません。UDP プロキシ(UDP フォワーディング用に
streamモジュールを設定した NGINX や HAProxy など)を介してトラフィックをルーティングする必要があります。これによりホップが増えてレイテンシが発生しますが、UE バイナリを実行しているコンピューティングインスタンスの真の IP を隠すことができます。 - iptables/nftables の設定: カーネルレベルで断片化された UDP パケットをドロップし、IP ごとの接続を制限する厳格なファイアウォールルールを作成する必要があります。
- エンタープライズ対策の購入: データセンターに到達する前に悪意のあるトラフィックをスクラブするために、高価なエンタープライズルーティングサービス(AWS Shield Advanced や Cloudflare Magic Transit など)を購入する必要があります。
この耐性のあるプロキシベースのアーキテクチャを自分で構築するには、フリートマネージャー、Load balancers、複雑なルーティングテーブルの設定が必要であり、4〜6 か月の専門的な DevOps 作業が容易にかかります。これは、ゲームの出荷を目指す Indie スタジオにとって、大きな金銭的および時間的損失です。
DevOps の罠からの脱出
これこそが、Backend-as-a-Service プラットフォームが解決するために設計されたインフラの悪夢です。horizOn を使用すれば、この強化されたバックエンドインフラが事前に構成された状態で提供されます。
iptables の設定に何ヶ月も費やしたり、帯域消費型 UDP Flood を心配したりする代わりに、私たちのプラットフォームが Edge network を管理します。あなたのゲームインスタンスはエンタープライズグレードのルーティングレイヤーによって保護され、悪意のあるレイヤー 4 およびレイヤー 7 トラフィックは、実際の Unreal Engine サーバースレッドに到達する前に自動的に識別・ドロップされます。つまり、Tick rate は安定し、正当なプレイヤーは接続を維持でき、Botnet の対策ではなくゲームプレイコードの作成に集中できるのです。
攻撃を受けている Indie 開発者のための 4 つのベストプラクティス
独自の対策スタックを構築する場合でも、マネージドインフラを利用する場合でも、ゲームを強化するために以下のコアなセキュリティ原則に従う必要があります。
1. サーバー IP をクライアントに直接公開しない: プレイヤーが Wireshark を開くだけでサーバーの IP アドレスを確認できるなら、攻撃者も確認できます。接続を仲介する、またはセッションチケットを使用する安全な Matchmaking サービスを利用する必要があります。
2. 厳格なセッション検証を実装する:
IP とポートを知っているだけでクライアントが接続できるようにしてはいけません。バックエンドで生成された短寿命の暗号化トークン(JWT など)を接続文字列のオプションとして渡すように要求します(例:open 127.0.0.1:7777?token=eyJhbG...)。このトークンを PreLogin で、マスターサーバーへの非同期 HTTP コールや、共有シークレットを使用したローカルでの署名検証によって即座に検証します。トークンが無効、欠落、または期限切れの場合は、即座に接続を切断します。これにより、攻撃者が IP アドレスをローテーションさせるだけで Rate Limiting をバイパスすることを防げます。なぜなら、接続トークンを生成するには、有効な認証済みユーザーアカウントが必要になるからです。
3. サーバーの Tick Rate を制限する:
上限のない NetServerMaxTickRate を実行しないでください。予期しないトラフィックの急増に備えて CPU のヘッドルームを確保するために、予測可能な値 (20、30、または 60 Hz) にロックしてください。
4. エンジンだけでなく、ネットワークエッジを監視する:
エンジンのクラッシュログは、ファイアウォールレベルでのパケットドロップについては教えてくれません。OS またはハイパーバイザーレベルで、インバウンドの帯域幅 (InBytes) とパケットレートを追跡するメトリクスが必要です。プレイヤー数の増加に対応しないインバウンド UDP トラフィックの突然の急増は、帯域消費型攻撃の主要な指標です。
悪意のあるアクターから Multiplayer ゲームを保護することは、終わりのない軍備拡張競争です。コードにアグレッシブなレート制限を実装し、エンジン構成を強化し、エッジで不正なトラフィックをドロップするインフラを利用することで、Lag や Rubber-banding、フラストレーションの溜まるクラッシュなしに、プレイヤーに設計通りのゲーム体験を提供できます。
サーバーインフラの心配をやめて、ゲーム制作に集中する準備はできましたか? horizOn を無料でお試しいただき、私たちの回復力のあるバックエンドアーキテクチャが、プレッシャーの下でどのようにゲームをオンラインに保つかを確認してください。
出典: [VERY CRITICAL] Organized DDoS Attacks Causing Server Crashes