Add anti-flood protection

This commit is contained in:
Gustas
2022-07-12 18:55:57 +03:00
committed by abcdefg30
parent ac623d784a
commit 58fcffa429
4 changed files with 109 additions and 19 deletions

View File

@@ -0,0 +1,86 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
namespace OpenRA.Server
{
class PlayerMessageTracker
{
[TranslationReference("remaining")]
static readonly string ChatDisabled = "chat-disabled";
readonly Dictionary<int, List<long>> messageTracker = new Dictionary<int, List<long>>();
readonly Server server;
readonly Action<Connection, int, int, byte[]> dispatchOrdersToClient;
readonly Action<Connection, string, Dictionary<string, object>> sendLocalizedMessageTo;
public PlayerMessageTracker(Server server, Action<Connection, int, int, byte[]> dispatchOrdersToClient, Action<Connection, string, Dictionary<string, object>> sendLocalizedMessageTo)
{
this.server = server;
this.dispatchOrdersToClient = dispatchOrdersToClient;
this.sendLocalizedMessageTo = sendLocalizedMessageTo;
}
public void DisableChatUI(Connection conn, int time)
{
dispatchOrdersToClient(conn, 0, 0, new Order("DisableChatEntry", null, false) { ExtraData = (uint)time }.Serialize());
}
public bool IsPlayerAtFloodLimit(Connection conn)
{
if (!messageTracker.ContainsKey(conn.PlayerIndex))
messageTracker.Add(conn.PlayerIndex, new List<long>());
var isAdmin = server.GetClient(conn)?.IsAdmin ?? false;
var settings = server.Settings;
var time = conn.ConnectionTimer.ElapsedMilliseconds;
var tracker = messageTracker[conn.PlayerIndex];
tracker.RemoveAll(t => t + settings.FloodLimitInterval < time);
long CalculateRemaining(long cooldown)
{
return (cooldown - time + 999) / 1000;
}
// Block messages until join cooldown times out
if (!isAdmin && time < settings.FloodLimitJoinCooldown)
{
var remaining = CalculateRemaining(settings.FloodLimitJoinCooldown);
sendLocalizedMessageTo(conn, ChatDisabled, Translation.Arguments("remaining", remaining));
return true;
}
// Block messages if above flood limit
if (tracker.Count >= settings.FloodLimitMessageCount)
{
var remaining = CalculateRemaining(tracker[0] + settings.FloodLimitInterval);
sendLocalizedMessageTo(conn, ChatDisabled, Translation.Arguments("remaining", remaining));
return true;
}
tracker.Add(time);
// Disable chat when player has reached the flood limit
if (tracker.Count >= settings.FloodLimitMessageCount)
{
var cooldownDelta = Math.Max(0, settings.FloodLimitCooldown - settings.FloodLimitInterval);
for (var i = 0; i < tracker.Count; i++)
tracker[i] = time + cooldownDelta;
DisableChatUI(conn, settings.FloodLimitCooldown);
}
return false;
}
}
}

View File

@@ -79,6 +79,7 @@ namespace OpenRA.Server
GameInformation gameInfo; GameInformation gameInfo;
readonly List<GameInformation.Player> worldPlayers = new List<GameInformation.Player>(); readonly List<GameInformation.Player> worldPlayers = new List<GameInformation.Player>();
readonly Stopwatch pingUpdated = Stopwatch.StartNew(); readonly Stopwatch pingUpdated = Stopwatch.StartNew();
readonly PlayerMessageTracker playerMessageTracker;
[TranslationReference] [TranslationReference]
static readonly string CustomRules = "custom-rules"; static readonly string CustomRules = "custom-rules";
@@ -128,9 +129,6 @@ namespace OpenRA.Server
[TranslationReference("command")] [TranslationReference("command")]
static readonly string UnknownServerCommand = "unknown-server-command"; static readonly string UnknownServerCommand = "unknown-server-command";
[TranslationReference("remaining")]
static readonly string ChatDisabled = "chat-disabled";
[TranslationReference("player")] [TranslationReference("player")]
static readonly string LobbyDisconnected = "lobby-disconnected"; static readonly string LobbyDisconnected = "lobby-disconnected";
@@ -316,6 +314,8 @@ namespace OpenRA.Server
Map = ModData.MapCache[settings.Map]; Map = ModData.MapCache[settings.Map];
MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks); MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks);
playerMessageTracker = new PlayerMessageTracker(this, DispatchOrdersToClient, SendLocalizedMessageTo);
LobbyInfo = new Session LobbyInfo = new Session
{ {
GlobalSettings = GlobalSettings =
@@ -558,8 +558,8 @@ namespace OpenRA.Server
newConn.Validated = true; newConn.Validated = true;
// Disable chat UI to stop the client sending messages that we know we will reject // Disable chat UI to stop the client sending messages that we know we will reject
if (!client.IsAdmin && Settings.JoinChatDelay > 0) if (!client.IsAdmin && Settings.FloodLimitJoinCooldown > 0)
DispatchOrdersToClient(newConn, 0, 0, new Order("DisableChatEntry", null, false) { ExtraData = (uint)Settings.JoinChatDelay }.Serialize()); playerMessageTracker.DisableChatUI(newConn, Settings.FloodLimitJoinCooldown);
Log.Write("server", $"Client {newConn.PlayerIndex}: Accepted connection from {newConn.EndPoint}."); Log.Write("server", $"Client {newConn.PlayerIndex}: Accepted connection from {newConn.EndPoint}.");
@@ -1006,14 +1006,7 @@ namespace OpenRA.Server
case "Chat": case "Chat":
{ {
var isAdmin = GetClient(conn)?.IsAdmin ?? false; if (Type == ServerType.Local || !playerMessageTracker.IsPlayerAtFloodLimit(conn))
var connected = conn.ConnectionTimer.ElapsedMilliseconds;
if (!isAdmin && connected < Settings.JoinChatDelay)
{
var remaining = (Settings.JoinChatDelay - connected + 999) / 1000;
SendLocalizedMessageTo(conn, ChatDisabled, Translation.Arguments("remaining", remaining));
}
else
DispatchOrdersToClients(conn, 0, o.Serialize()); DispatchOrdersToClients(conn, 0, o.Serialize());
break; break;

View File

@@ -103,7 +103,16 @@ namespace OpenRA
public bool EnableLintChecks = true; public bool EnableLintChecks = true;
[Desc("Delay in milliseconds before newly joined players can send chat messages.")] [Desc("Delay in milliseconds before newly joined players can send chat messages.")]
public int JoinChatDelay = 5000; public int FloodLimitJoinCooldown = 5000;
[Desc("Amount of miliseconds player chat messages are tracked for.")]
public int FloodLimitInterval = 5000;
[Desc("Amount of chat messages per FloodLimitInterval a players can send before flood is detected.")]
public int FloodLimitMessageCount = 5;
[Desc("Delay in milliseconds before players can send chat messages after flood was detected.")]
public int FloodLimitCooldown = 15000;
public ServerSettings Clone() public ServerSettings Clone()
{ {

View File

@@ -8,11 +8,6 @@ no-start-until-required-slots-full = Unable to start the game until required slo
no-start-without-players = Unable to start the game with no players. no-start-without-players = Unable to start the game with no players.
insufficient-enabled-spawnPoints = Unable to start the game until more spawn points are enabled. insufficient-enabled-spawnPoints = Unable to start the game until more spawn points are enabled.
malformed-command = Malformed { $command } command malformed-command = Malformed { $command } command
chat-disabled =
{ $remaining ->
[one] Chat is disabled. Please try again in { $remaining } second.
*[other] Chat is disabled. Please try again in { $remaining } seconds.
}
state-unchanged-ready = Cannot change state when marked as ready. state-unchanged-ready = Cannot change state when marked as ready.
invalid-faction-selected = Invalid faction selected: { $faction } invalid-faction-selected = Invalid faction selected: { $faction }
supported-factions = Supported values: { $factions } supported-factions = Supported values: { $factions }
@@ -91,6 +86,13 @@ game-started = Game started
## Server also LobbyUtils ## Server also LobbyUtils
bots-disabled = Bots Disabled bots-disabled = Bots Disabled
## PlayerMessageTracker
chat-disabled =
{ $remaining ->
[one] Chat is disabled. Please try again in { $remaining } second.
*[other] Chat is disabled. Please try again in { $remaining } seconds.
}
## ActorEditLogic ## ActorEditLogic
duplicate-actor-id = Duplicate Actor ID duplicate-actor-id = Duplicate Actor ID
enter-actor-id = Enter an Actor ID enter-actor-id = Enter an Actor ID