1.7.7-alpha

- Fixed steamid only bans
- Added missing multiservermode
- Better caching
This commit is contained in:
Dawid Bepierszcz
2025-05-25 01:00:14 +02:00
parent 3ab63c05db
commit b97426313b
6 changed files with 173 additions and 96 deletions

View File

@@ -1,3 +1,4 @@
UPDATE `sa_players_ips` SET `address` = INET_ATON(address); UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL; ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`; ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;
ALTER TABLE `sa_players_ips` ADD INDEX(`used_at`);

View File

@@ -137,7 +137,10 @@ public partial class CS2_SimpleAdmin
#if DEBUG #if DEBUG
Logger.LogCritical("[OnClientConnect]"); Logger.LogCritical("[OnClientConnect]");
#endif #endif
if (Config.OtherSettings.BanType == 1 && Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0])) if (Config.OtherSettings.BanType == 0)
return;
if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0]))
return; return;
Server.NextFrame((() => Server.NextFrame((() =>

View File

@@ -1001,4 +1001,4 @@ public static class IpHelper
var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray(); var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray();
return new System.Net.IPAddress(bytes).ToString(); return new System.Net.IPAddress(bytes).ToString();
} }
} }

View File

@@ -8,6 +8,9 @@ namespace CS2_SimpleAdmin.Managers;
internal class CacheManager: IDisposable internal class CacheManager: IDisposable
{ {
private readonly ConcurrentDictionary<int, BanRecord> _banCache = []; private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
private readonly ConcurrentDictionary<string, List<BanRecord>> _steamIdIndex = [];
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = []; private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
private HashSet<uint> _cachedIgnoredIps = []; private HashSet<uint> _cachedIgnoredIps = [];
@@ -29,24 +32,34 @@ internal class CacheManager: IDisposable
.Select(IpHelper.IpToUint)); .Select(IpHelper.IpToUint));
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
var bans = await connection.QueryAsync<BanRecord>( List<BanRecord> bans;
"""
SELECT if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
bans = (await connection.QueryAsync<BanRecord>(
"""
SELECT
id AS Id, id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId, player_steamid AS PlayerSteamId,
player_ip AS PlayerIp, player_ip AS PlayerIp,
admin_steamid AS AdminSteamId, status AS Status
admin_name AS AdminName, FROM sa_bans
reason AS Reason, """)).ToList();
duration AS Duration, }
ends AS Ends, else
created AS Created, {
server_id AS ServerId, bans = (await connection.QueryAsync<BanRecord>(
status AS Status, """
updated_at AS UpdatedAt SELECT
FROM sa_bans id AS Id,
"""); player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
}
var ipHistory = var ipHistory =
await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>( await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC");
@@ -89,7 +102,9 @@ internal class CacheManager: IDisposable
return existingSet; return existingSet;
}); });
} }
RebuildIndexes();
_lastUpdateTime = DateTime.Now.AddSeconds(-1); _lastUpdateTime = DateTime.Now.AddSeconds(-1);
_isInitialized = true; _isInitialized = true;
} }
@@ -119,10 +134,61 @@ internal class CacheManager: IDisposable
try try
{ {
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
var updatedBans = (await connection.QueryAsync<BanRecord>( List<BanRecord> updatedBans;
"SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC",
new { lastUpdate = _lastUpdateTime } var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
)).ToList();
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime }
)).ToList();
}
else
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
)).ToList();
}
foreach (var id in _banCache.Keys)
{
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
// Remove from steamIdIndex
if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId) &&
_steamIdIndex.TryGetValue(ban.PlayerSteamId, out var steamBans))
{
steamBans.RemoveAll(b => b.Id == id);
if (steamBans.Count == 0)
_steamIdIndex.TryRemove(ban.PlayerSteamId, out _);
}
// Remove from ipIndex
if (!string.IsNullOrWhiteSpace(ban.PlayerIp) &&
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) &&
_ipIndex.TryGetValue(ipUInt, out var ipBans))
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
}
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>( var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300", new {lastUpdate = _lastUpdateTime})).ToList(); "SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300", new {lastUpdate = _lastUpdateTime})).ToList();
@@ -166,7 +232,8 @@ internal class CacheManager: IDisposable
{ {
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
} }
RebuildIndexes();
_lastUpdateTime = DateTime.Now.AddSeconds(-1); _lastUpdateTime = DateTime.Now.AddSeconds(-1);
} }
catch (Exception e) catch (Exception e)
@@ -174,10 +241,48 @@ internal class CacheManager: IDisposable
// ignored // ignored
} }
} }
private void RebuildIndexes()
{
_steamIdIndex.Clear();
_ipIndex.Clear();
foreach (var ban in _banCache.Values)
{
if (ban.Status != "ACTIVE")
continue;
if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId))
{
var steamId = ban.PlayerSteamId;
_steamIdIndex.AddOrUpdate(
steamId,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
if (ban.PlayerIp != null &&
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
{
_ipIndex.AddOrUpdate(
ipUInt,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
}
}
public List<BanRecord> GetAllBans() => _banCache.Values.ToList(); public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList(); public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList();
public List<BanRecord> GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList(); public List<BanRecord> GetPlayerBansBySteamId(string steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : [];
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress) public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
{ {
var ipAsUint = IpHelper.IpToUint(ipAddress); var ipAsUint = IpHelper.IpToUint(ipAddress);
@@ -191,49 +296,37 @@ internal class CacheManager: IDisposable
private bool IsIpBanned(string ipAddress) private bool IsIpBanned(string ipAddress)
{ {
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
var ipUInt = IpHelper.IpToUint(ipAddress); var ipUInt = IpHelper.IpToUint(ipAddress);
return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
return _banCache.Values.Any(b =>
b is { Status: "ACTIVE", PlayerIp: not null } &&
IpHelper.IpToUint(b.PlayerIp) == ipUInt &&
!_cachedIgnoredIps.Contains(ipUInt));
} }
public bool IsPlayerBanned(string? steamId, string? ipAddress) public bool IsPlayerBanned(string? steamId, string? ipAddress)
{ {
if (steamId != null && _steamIdIndex.ContainsKey(steamId))
return true;
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
if (ipAddress == null) if (ipAddress == null)
return _banCache.Values.Any(b => return false;
b.Status == "ACTIVE" &&
steamId != null &&
b.PlayerSteamId != null &&
b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase));
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)) if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
return false; return false;
return _banCache.Values.Any(b => return !_cachedIgnoredIps.Contains(ipUInt) &&
b is { Status: "ACTIVE", PlayerIp: not null } && _ipIndex.ContainsKey(ipUInt);
(
(steamId != null &&
b.PlayerSteamId != null &&
b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase))
||
(IpHelper.TryConvertIpToUint(b.PlayerIp, out var bIpUint) &&
bIpUint == ipUInt &&
!_cachedIgnoredIps.Contains(ipUInt))
)
);
} }
public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress) public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
{ {
var steamIdStr = steamId.ToString(); var steamIdStr = steamId.ToString();
if (_banCache.Values.Any(b =>
b.Status == "ACTIVE" && if (_steamIdIndex.ContainsKey(steamIdStr))
b.PlayerSteamId?.Equals(steamIdStr, StringComparison.OrdinalIgnoreCase) == true))
{
return true; return true;
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
if (!_playerIpsCache.TryGetValue(steamId, out var ipData)) if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
return false; return false;
@@ -245,21 +338,26 @@ internal class CacheManager: IDisposable
{ {
var ipAsUint = IpHelper.IpToUint(ipAddress); var ipAsUint = IpHelper.IpToUint(ipAddress);
ipData.Add(new IpRecord( if (!_cachedIgnoredIps.Contains(ipAsUint))
ipAsUint, {
now.AddSeconds(-2), ipData.Add(new IpRecord(
CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" ipAsUint,
)); now.AddSeconds(-2), // artificially recent
CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
));
}
} }
return ipData.Any(x => foreach (var ipRecord in ipData)
x.UsedAt >= cutoff && {
!_cachedIgnoredIps.Contains(x.Ip) && if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
_banCache.Values.Any(b => continue;
b is { Status: "ACTIVE", PlayerIp: not null } &&
IpHelper.TryConvertIpToUint(b.PlayerIp, out var banIpUint) && if (_ipIndex.ContainsKey(ipRecord.Ip))
banIpUint == x.Ip return true;
)); }
return false;
} }
public bool HasIpForPlayer(ulong steamId, string ipAddress) public bool HasIpForPlayer(ulong steamId, string ipAddress)
@@ -273,6 +371,9 @@ internal class CacheManager: IDisposable
private void Clear() private void Clear()
{ {
_steamIdIndex.Clear();
_ipIndex.Clear();
_banCache.Clear(); _banCache.Clear();
_playerIpsCache.Clear(); _playerIpsCache.Clear();
_cachedIgnoredIps.Clear(); _cachedIgnoredIps.Clear();

View File

@@ -313,9 +313,8 @@ public class PlayerManager
return CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch return CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{ {
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null), 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp _ =>
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(player.SteamID, player.IpAddress?.Split(":")[0]) CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0])
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0])
}; };
}) })
.ToList(); .ToList();

View File

@@ -2,44 +2,17 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace CS2_SimpleAdmin.Models; namespace CS2_SimpleAdmin.Models;
public record struct BanRecord public record BanRecord
{ {
[Column("id")] [Column("id")]
public int Id { get; set; } public int Id { get; set; }
[Column("player_name")]
public string PlayerName { get; set; }
[Column("player_steamid")] [Column("player_steamid")]
public string? PlayerSteamId { get; set; } public string? PlayerSteamId { get; set; }
[Column("player_ip")] [Column("player_ip")]
public string? PlayerIp { get; set; } public string? PlayerIp { get; set; }
[Column("admin_steamid")]
public string AdminSteamId { get; set; }
[Column("admin_name")]
public string AdminName { get; set; }
[Column("reason")]
public string Reason { get; set; }
[Column("duration")]
public int Duration { get; set; }
[Column("ends")]
public DateTime? Ends { get; set; }
[Column("created")]
public DateTime Created { get; set; }
[Column("server_id")]
public int? ServerId { get; set; }
[Column("status")] [Column("status")]
public string Status { get; set; } public string Status { get; set; }
[Column("updated_at")]
public DateTime UpdatedAt { get; set; }
} }