156 lines
5 KiB
C#
156 lines
5 KiB
C#
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.Net;
|
||
using Content.Shared.CCVar;
|
||
using Robust.Shared.Configuration;
|
||
using Robust.Shared.IoC;
|
||
using Robust.Shared.Log;
|
||
using Robust.Shared.Timing;
|
||
|
||
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!;
|
||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||
|
||
private readonly ConcurrentDictionary<IPAddress, DateTime> _blockedIPs = new();
|
||
private ISawmill _sawmill = default!;
|
||
|
||
private bool _enabled;
|
||
private int _blockDurationSeconds;
|
||
private int _maxResponseLength;
|
||
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, заблокирован ли указанный IP-адрес.
|
||
/// </summary>
|
||
public bool IsBlocked(IPAddress ip)
|
||
{
|
||
if (!_enabled)
|
||
return false;
|
||
|
||
if (!_blockedIPs.TryGetValue(ip, out var unblockTime))
|
||
return false;
|
||
|
||
// Проверяем, не истекла ли блокировка
|
||
if (DateTime.UtcNow >= unblockTime)
|
||
{
|
||
_blockedIPs.TryRemove(ip, out _);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Блокирует IP-адрес на указанное время с указанной причиной.
|
||
/// </summary>
|
||
public void BlockIP(IPAddress ip, TimeSpan duration, string reason)
|
||
{
|
||
if (!_enabled)
|
||
return;
|
||
|
||
var unblockTime = DateTime.UtcNow + duration;
|
||
_blockedIPs.AddOrUpdate(ip, unblockTime, (_, _) => unblockTime);
|
||
|
||
_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)
|
||
return false;
|
||
|
||
// Проверяем на отрицательные или слишком большие значения
|
||
if (length < 0 || length > _maxResponseLength)
|
||
{
|
||
var reason = $"Подозрительная длина ответа: {length} байт (контекст: {context})";
|
||
BlockIP(ip, reason);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает максимально допустимую длину ответа.
|
||
/// </summary>
|
||
public int GetMaxResponseLength()
|
||
{
|
||
return _maxResponseLength;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Очищает истекшие блокировки. Должен вызываться периодически.
|
||
/// </summary>
|
||
public void Update()
|
||
{
|
||
if (!_enabled)
|
||
return;
|
||
|
||
var now = DateTime.UtcNow;
|
||
var keysToRemove = new List<IPAddress>();
|
||
|
||
foreach (var (ip, unblockTime) in _blockedIPs)
|
||
{
|
||
if (now >= unblockTime)
|
||
{
|
||
keysToRemove.Add(ip);
|
||
}
|
||
}
|
||
|
||
foreach (var ip in keysToRemove)
|
||
{
|
||
_blockedIPs.TryRemove(ip, out _);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Разблокирует IP-адрес вручную.
|
||
/// </summary>
|
||
public void UnblockIP(IPAddress ip)
|
||
{
|
||
if (_blockedIPs.TryRemove(ip, out _))
|
||
{
|
||
_sawmill.Info($"IP {ip} разблокирован вручную");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает количество заблокированных IP-адресов.
|
||
/// </summary>
|
||
public int GetBlockedCount()
|
||
{
|
||
return _blockedIPs.Count;
|
||
}
|
||
}
|
||
|