Distinct-source floods of 50k-250k machines are seen in practice, which overflows a 156,437 cap. Past the cap Hold() refuses to track, so those addresses are still disconnected by whichever gate caught them but pay full detection cost on every reconnect instead of a cheap accept-gate deny -- the expensive path during exactly the event the list exists for. 324,449 is the next Dictionary capacity in the from-empty progression (36,353 -> 75,431 -> 156,437 -> 324,449), so it fills one exactly. ~17 MB. The hot-path cost barely moves: cap-triggered sweeps are throttled to one a second, so a flood pays 0.65ms/sec, 0.065% of the loop. Only the flood-end sweep grows, from ~2ms to ~6ms, once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
87 lines
3.8 KiB
C#
87 lines
3.8 KiB
C#
/*************************************************************************
|
||
* ModernUO *
|
||
* Copyright 2019-2026 - ModernUO Development Team *
|
||
* Email: hi@modernuo.com *
|
||
* File: AutoDenylistConfiguration.cs *
|
||
* *
|
||
* This program is free software: you can redistribute it and/or modify *
|
||
* it 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. *
|
||
* *
|
||
* You should have received a copy of the GNU General Public License *
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||
*************************************************************************/
|
||
|
||
using System;
|
||
using System.IO;
|
||
using System.Text.Json.Serialization;
|
||
using Server.Json;
|
||
|
||
namespace Server.Network;
|
||
|
||
/// <summary>
|
||
/// Loads the <see cref="AutoDenylistSettings"/> from <c>Configuration/auto-denylist.json</c>. Loaded once;
|
||
/// a missing file writes a template so operators have something to edit.
|
||
/// </summary>
|
||
public static class AutoDenylistConfiguration
|
||
{
|
||
private const string _path = "Configuration/auto-denylist.json";
|
||
|
||
public static AutoDenylistSettings Settings { get; private set; }
|
||
|
||
public static void Load()
|
||
{
|
||
var path = Path.Join(Core.BaseDirectory, _path);
|
||
|
||
if (File.Exists(path))
|
||
{
|
||
Settings = JsonConfig.Deserialize<AutoDenylistSettings>(path);
|
||
}
|
||
else
|
||
{
|
||
Settings = new AutoDenylistSettings();
|
||
Save();
|
||
}
|
||
}
|
||
|
||
private static void Save()
|
||
{
|
||
JsonConfig.Serialize(Path.Join(Core.BaseDirectory, _path), Settings);
|
||
}
|
||
}
|
||
|
||
/// <summary>Bound configuration for <see cref="AutoDenylist"/>.</summary>
|
||
public record AutoDenylistSettings
|
||
{
|
||
/// <summary>Whether behavioural detections are held locally. Disabled makes the filter inert.</summary>
|
||
[JsonPropertyName("enabled")]
|
||
public bool Enabled { get; set; } = true;
|
||
|
||
/// <summary>
|
||
/// How long an address is denied at accept after the shard catches it misbehaving. Deliberately
|
||
/// independent of the duration reported to external bouncers: this is a local holding pen, not a ban.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Short on purpose: it covers the gap before an OS bouncer reacts, and blunts a flood on shards running
|
||
/// none. An address still attacking is simply re-detected and re-added, so the list sustains itself while
|
||
/// a mistake clears on its own.
|
||
/// </remarks>
|
||
[JsonPropertyName("duration")]
|
||
public TimeSpan Duration { get; set; } = TimeSpan.FromMinutes(15);
|
||
|
||
/// <summary>
|
||
/// Hard cap on tracked addresses: a distinct-source flood is the case this exists for, so the cap is what
|
||
/// stops it becoming the exhaustion it prevents. At the cap new addresses are not tracked, but are still
|
||
/// disconnected by whichever gate detected them.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// A <c>Dictionary</c> capacity, not a round number. Grown from empty it steps 36,353 → 75,431 →
|
||
/// 156,437 → 324,449, so this fills one exactly instead of stranding slots: 65,536 sat just past a
|
||
/// resize and left 9,895 of them (52 bytes each) unusable. Sized to cover the 50k–250k distinct-source
|
||
/// floods seen in practice, for ~17 MB. Raising it is paid on the loop, where a full sweep runs ~6 ms
|
||
/// here and ~26 ms at 968,897.
|
||
/// </remarks>
|
||
[JsonPropertyName("maxEntries")]
|
||
public int MaxEntries { get; set; } = 324_449;
|
||
}
|