Skip to content

Commit

Permalink
Implement BlacklistedRange exempt flag (#29258)
Browse files Browse the repository at this point in the history
* Implement a new kind of ip range ban that only applies to new players

* Put determining whether a player record exists to its own function

* Make BlacklistedRange bans get bypassed by any ban exemption

* Stop trying to get another DbGuard while already having one

This does break with convention on the functions in that area but
considering the use of this function it's probably fine?
I could alternatively just move the place it's called from.

Also I was suppossed to wait for tests to finish locally just to be
sure, but nah. I am pushing this now
  • Loading branch information
nikthechampiongr authored Jun 21, 2024
1 parent 6b13f5f commit 76a65c8
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 8 deletions.
8 changes: 8 additions & 0 deletions Content.Server.Database/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,14 @@ public enum ServerBanExemptFlags
/// Intended use is for users with shared connections. This should not be used as an alternative to <see cref="Datacenter"/>.
/// </remarks>
IP = 1 << 1,

/// <summary>
/// Ban is an IP range that is only applied for first time joins.
/// </summary>
/// <remarks>
/// Intended for use with residential IP ranges that are often used maliciously.
/// </remarks>
BlacklistedRange = 1 << 2,
// @formatter:on
}

Expand Down
5 changes: 5 additions & 0 deletions Content.Server/Database/ServerDbBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,11 @@ public async Task UpdatePlayerRecord(
return record == null ? null : MakePlayerRecord(record);
}

protected async Task<bool> PlayerRecordExists(DbGuard db, NetUserId userId)
{
return await db.DbContext.Player.AnyAsync(p => p.UserId == userId);
}

[return: NotNullIfNotNull(nameof(player))]
protected PlayerRecord? MakePlayerRecord(Player? player)
{
Expand Down
16 changes: 12 additions & 4 deletions Content.Server/Database/ServerDbPostgres.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public ServerDbPostgres(
await using var db = await GetDbImpl();

var exempt = await GetBanExemptionCore(db, userId);
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned: false, exempt)
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned: false, exempt, newPlayer)
.OrderByDescending(b => b.BanTime);

var ban = await query.FirstOrDefaultAsync();
Expand All @@ -98,7 +99,8 @@ public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? add
await using var db = await GetDbImpl();

var exempt = await GetBanExemptionCore(db, userId);
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt);
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt, newPlayer);

var queryBans = await query.ToArrayAsync();
var bans = new List<ServerBanDef>(queryBans.Length);
Expand All @@ -122,7 +124,8 @@ private static IQueryable<ServerBan> MakeBanLookupQuery(
ImmutableArray<byte>? hwId,
DbGuardImpl db,
bool includeUnbanned,
ServerBanExemptFlags? exemptFlags)
ServerBanExemptFlags? exemptFlags,
bool newPlayer)
{
DebugTools.Assert(!(address == null && userId == null && hwId == null));

Expand All @@ -141,7 +144,9 @@ private static IQueryable<ServerBan> MakeBanLookupQuery(
{
var newQ = db.PgDbContext.Ban
.Include(p => p.Unban)
.Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address));
.Where(b => b.Address != null
&& EF.Functions.ContainsOrEqual(b.Address.Value, address)
&& !(b.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) && !newPlayer));

query = query == null ? newQ : query.Union(newQ);
}
Expand All @@ -167,6 +172,9 @@ private static IQueryable<ServerBan> MakeBanLookupQuery(

if (exemptFlags is { } exempt)
{
if (exempt != ServerBanExemptFlags.None)
exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange

query = query.Where(b => (b.ExemptFlags & exempt) == 0);
}

Expand Down
20 changes: 16 additions & 4 deletions Content.Server/Database/ServerDbSqlite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ public ServerDbSqlite(

var exempt = await GetBanExemptionCore(db, userId);

var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);

// SQLite can't do the net masking stuff we need to match IP address ranges.
// So just pull down the whole list into memory.
var bans = await GetAllBans(db.SqliteDbContext, includeUnbanned: false, exempt);

return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId, exempt)) is { } foundBan
return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId, exempt, newPlayer)) is { } foundBan
? ConvertBan(foundBan)
: null;
}
Expand All @@ -103,12 +105,14 @@ public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? add

var exempt = await GetBanExemptionCore(db, userId);

var newPlayer = !await db.SqliteDbContext.Player.AnyAsync(p => p.UserId == userId);

// SQLite can't do the net masking stuff we need to match IP address ranges.
// So just pull down the whole list into memory.
var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);

return queryBans
.Where(b => BanMatches(b, address, userId, hwId, exempt))
.Where(b => BanMatches(b, address, userId, hwId, exempt, newPlayer))
.Select(ConvertBan)
.ToList()!;
}
Expand Down Expand Up @@ -137,10 +141,18 @@ private static bool BanMatches(ServerBan ban,
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ServerBanExemptFlags? exemptFlags)
ServerBanExemptFlags? exemptFlags,
bool newPlayer)
{
// Any flag to bypass BlacklistedRange bans.
var exemptFromBlacklistedRange = exemptFlags != null && exemptFlags.Value != ServerBanExemptFlags.None;

if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)
&& address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value))
&& address != null
&& ban.Address is not null
&& address.IsInSubnet(ban.Address.ToTuple().Value)
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) ||
newPlayer && !exemptFromBlacklistedRange))
{
return true;
}
Expand Down

0 comments on commit 76a65c8

Please sign in to comment.