Добавлена проверка и блокировка IP при превышении лимита необработанных сообщений; обновлены и дополнены CVar настройки для IP-блокировки.

This commit is contained in:
Vigers Ray 2026-01-17 23:22:05 +01:00
parent dd89700b26
commit b1b7f3eeab
4 changed files with 54 additions and 63 deletions

View file

@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Net;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
@ -7,34 +8,31 @@ using Content.Shared.Connection.IPBlocking;
namespace Content.Server.Connection.IPBlocking;
/// <summary>
/// Система блокировки IP-адресов для защиты от перегрузки памяти
/// при получении подозрительных запросов с некорректными длинами ответов.
/// </summary>
public sealed class IPBlockingSystem : IIPBlockingSystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private readonly ConcurrentDictionary<IPAddress, DateTime> _blockedIPs = new();
private readonly ConcurrentDictionary<IPAddress, object> _unhandledMessageLocks = new();
private readonly ConcurrentDictionary<IPAddress, List<DateTime>> _unhandledMessageTimestamps = new();
private ISawmill _sawmill = default!;
private bool _enabled;
private int _blockDurationSeconds;
private int _maxResponseLength;
private int _unhandledMessageRateLimit;
public void Initialize()
{
_sawmill = _logManager.GetSawmill("ipblocking");
_cfg.OnValueChanged(CCVars.GameIPBlockingEnabled, b => _enabled = b, true);
_cfg.OnValueChanged(CCVars.GameIPBlockingDuration, b => _blockDurationSeconds = b, true);
_cfg.OnValueChanged(CCVars.GameIPBlockingMaxResponseLength, b => _maxResponseLength = b, true);
_cfg.OnValueChanged(SunriseCCVars.GameIPBlockingEnabled, b => _enabled = b, true);
_cfg.OnValueChanged(SunriseCCVars.GameIPBlockingDuration, b => _blockDurationSeconds = b, true);
_cfg.OnValueChanged(SunriseCCVars.GameIPBlockingMaxResponseLength, b => _maxResponseLength = b, true);
_cfg.OnValueChanged(SunriseCCVars.GameIPBlockingUnhandledMessageRateLimit, b => _unhandledMessageRateLimit = b, true);
}
/// <summary>
/// Проверяет, заблокирован ли указанный IP-адрес.
/// </summary>
public bool IsBlocked(IPAddress ip)
{
if (!_enabled)
@ -43,7 +41,6 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
if (!_blockedIPs.TryGetValue(ip, out var unblockTime))
return false;
// Проверяем, не истекла ли блокировка
if (DateTime.UtcNow >= unblockTime)
{
_blockedIPs.TryRemove(ip, out _);
@ -53,9 +50,6 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
return true;
}
/// <summary>
/// Блокирует IP-адрес на указанное время с указанной причиной.
/// </summary>
public void BlockIP(IPAddress ip, TimeSpan duration, string reason)
{
if (!_enabled)
@ -67,19 +61,12 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
_sawmill.Warning($"Заблокирован IP {ip} на {duration.TotalMinutes:F1} минут. Причина: {reason}");
}
/// <summary>
/// Блокирует IP-адрес на время, указанное в CVar, с указанной причиной.
/// </summary>
public void BlockIP(IPAddress ip, string reason)
{
var duration = TimeSpan.FromSeconds(_blockDurationSeconds);
BlockIP(ip, duration, reason);
}
/// <summary>
/// Проверяет длину ответа и блокирует IP при обнаружении подозрительного значения.
/// </summary>
/// <returns>true, если длина подозрительная и IP был заблокирован</returns>
public bool CheckAndBlockSuspiciousLength(IPAddress ip, int length, string context)
{
if (!_enabled)
@ -88,7 +75,6 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
return false;
}
// Проверяем на отрицательные или слишком большие значения
if (length < 0 || length > _maxResponseLength)
{
var reason = $"Подозрительная длина ответа: {length} байт (контекст: {context})";
@ -100,9 +86,6 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
return false;
}
/// <summary>
/// Очищает истекшие блокировки. Должен вызываться периодически.
/// </summary>
public void Update()
{
if (!_enabled)
@ -125,15 +108,43 @@ public sealed class IPBlockingSystem : IIPBlockingSystem
}
}
/// <summary>
/// Разблокирует IP-адрес вручную.
/// </summary>
public void UnblockIP(IPAddress ip)
{
if (_blockedIPs.TryRemove(ip, out _))
{
_sawmill.Info($"IP {ip} разблокирован вручную");
_sawmill.Info($"IP {ip} unblocked");
}
}
public bool CheckAndBlockUnhandledMessageRate(IPAddress ip, string messageType)
{
if (!_enabled)
return false;
if (IsBlocked(ip))
return true;
var now = DateTime.UtcNow;
var lockObj = _unhandledMessageLocks.GetOrAdd(ip, _ => new object());
var timestamps = _unhandledMessageTimestamps.GetOrAdd(ip, _ => new List<DateTime>());
lock (lockObj)
{
timestamps.RemoveAll(t => (now - t).TotalSeconds > 1.0);
timestamps.Add(now);
if (timestamps.Count > _unhandledMessageRateLimit)
{
var reason = $"Превышен лимит необработанных библиотечных сообщений: {timestamps.Count} сообщений/сек (тип: {messageType})";
BlockIP(ip, reason);
_unhandledMessageTimestamps.TryRemove(ip, out _);
_unhandledMessageLocks.TryRemove(ip, out _);
return true;
}
}
return false;
}
}

View file

@ -310,24 +310,6 @@ public sealed partial class CCVars
public static readonly CVarDef<float> GameIPIntelAlertAdminWarnRating =
CVarDef.Create("game.ipintel_alert_admin_warn_rating", 0f, CVar.SERVERONLY);
/// <summary>
/// Включить систему блокировки IP-адресов для защиты от перегрузки памяти.
/// </summary>
public static readonly CVarDef<bool> GameIPBlockingEnabled =
CVarDef.Create("game.ipblocking_enabled", true, CVar.SERVERONLY);
/// <summary>
/// Время блокировки IP-адреса в секундах при обнаружении подозрительного запроса.
/// </summary>
public static readonly CVarDef<int> GameIPBlockingDuration =
CVarDef.Create("game.ipblocking_duration", 900, CVar.SERVERONLY); // 15 минут по умолчанию
/// <summary>
/// Максимальная допустимая длина ответа в байтах. Запросы с большей длиной будут блокироваться.
/// </summary>
public static readonly CVarDef<int> GameIPBlockingMaxResponseLength =
CVarDef.Create("game.ipblocking_max_response_length", 10485760, CVar.SERVERONLY); // 10MB по умолчанию
/// <summary>
/// Make people bonk when trying to climb certain objects like tables.
/// </summary>

View file

@ -2,30 +2,16 @@ using System.Net;
namespace Content.Shared.Connection.IPBlocking;
/// <summary>
/// Интерфейс для системы блокировки IP-адресов.
/// </summary>
public interface IIPBlockingSystem
{
/// <summary>
/// Инициализирует систему блокировки IP.
/// </summary>
void Initialize();
/// <summary>
/// Проверяет, заблокирован ли указанный IP-адрес.
/// </summary>
bool IsBlocked(IPAddress ip);
/// <summary>
/// Проверяет длину ответа и блокирует IP при обнаружении подозрительного значения.
/// </summary>
/// <returns>true, если длина подозрительная и IP был заблокирован</returns>
bool CheckAndBlockSuspiciousLength(IPAddress ip, int length, string context);
/// <summary>
/// Очищает истекшие блокировки. Должен вызываться периодически.
/// </summary>
bool CheckAndBlockUnhandledMessageRate(IPAddress ip, string messageType);
void Update();
}

View file

@ -562,4 +562,16 @@ public sealed partial class SunriseCCVars : CVars
/// </summary>
public static readonly CVarDef<bool> MentorHelpAutoOpenOnNewMessage =
CVarDef.Create("mentor_help.auto_open_on_new_message", false, CVar.ARCHIVE | CVar.CLIENTONLY);
public static readonly CVarDef<bool> GameIPBlockingEnabled =
CVarDef.Create("game.ipblocking_enabled", true, CVar.SERVERONLY);
public static readonly CVarDef<int> GameIPBlockingDuration =
CVarDef.Create("game.ipblocking_duration", 900, CVar.SERVERONLY);
public static readonly CVarDef<int> GameIPBlockingMaxResponseLength =
CVarDef.Create("game.ipblocking_max_response_length", 10485760, CVar.SERVERONLY);
public static readonly CVarDef<int> GameIPBlockingUnhandledMessageRateLimit =
CVarDef.Create("game.ipblocking_unhandled_message_rate_limit", 10, CVar.SERVERONLY);
}