4 using System.Collections.Generic;
5 using System.Diagnostics;
10 internal sealed
class DoSProtection
17 private readonly
Client sender;
18 private readonly Action<Client> end;
33 private sealed
class OffenseData
38 public readonly Stopwatch Stopwatch =
new();
48 public int PacketCount;
53 public void ResetStrikes()
62 public void ResetTimer() => Stopwatch.Reset();
65 private readonly Dictionary<Client, OffenseData> clients =
new();
67 private float stopwatchResetTimer,
70 private const int StopwatchResetInterval = 1,
71 StrikesResetInterval = 60,
74 private const int MinPacketLimitMultipler = 1;
77 => (int)MathF.Ceiling(
78 settings.MaxPacketAmount *
81 MinPacketLimitMultipler));
109 public DoSAction
Start(
Client client) =>
new DoSAction(client, StartFor, EndFor);
130 public DoSAction Pause(
Client client) =>
new DoSAction(client, PauseFor, ResumeFor);
132 private void StartFor(
Client client)
134 clients.TryAdd(client,
new OffenseData());
135 clients[client].Stopwatch.Start();
138 private void EndFor(
Client client)
140 if (GetData(client) is not { } data) {
return; }
143 data.Stopwatch.Stop();
144 UpdateOffense(client, data);
148 private void PauseFor(
Client client) => GetData(client)?.Stopwatch.Stop();
150 private void ResumeFor(
Client client) => GetData(client)?.Stopwatch.Start();
152 private void UpdateOffense(
Client client, OffenseData data)
154 if (GameMain.Server?.ServerSettings is not { } settings) {
return; }
159 AttemptKickClient(client, TextManager.Get(
"PacketLimitKicked"));
160 clients.Remove(client);
165 if (data.Stopwatch.ElapsedMilliseconds < 100) {
return; }
173 if (data.Strikes < StrikeThreshold) {
return; }
175 if (settings.EnableDoSProtection)
177 AttemptKickClient(client, TextManager.Get(
"DoSProtectionKicked"));
180 clients.Remove(client);
182 static void AttemptKickClient(
Client client, LocalizedString reason)
185 bool doesRateLimitAffectClient =
189 !RateLimiter.IsExempt(client);
192 if (!doesRateLimitAffectClient)
197 GameMain.Server?.KickClient(client, reason.Value);
201 public void Update(
float deltaTime)
203 stopwatchResetTimer += deltaTime;
204 strikesResetTimer += deltaTime;
207 if (stopwatchResetTimer > StopwatchResetInterval)
209 stopwatchResetTimer = 0;
210 foreach (OffenseData data
in clients.Values)
217 if (strikesResetTimer > StrikesResetInterval)
219 strikesResetTimer = 0;
220 foreach (var (client, data) in clients)
222 if (GameMain.Server?.ServerSettings is { MaxPacketAmount: > ServerSettings.PacketLimitMin } settings)
224 if (data.PacketCount > GetMaxPacketLimit(settings) * 0.9f)
226 GameServer.
Log($
"{NetworkMember.ClientLogName(client)} is sending a lot of packets and almost got kicked! ({data.PacketCount}).",
ServerLog.
MessageType.DoSProtection);
235 private OffenseData? GetData(
Client client) => clients.TryGetValue(client, out OffenseData? data) ? data :
null;
static void Log(string line, ServerLog.MessageType messageType)
const int DefaultTickRate
A struct that executes an action when it's created and another one when it's disposed.
DoSAction(Client sender, Action< Client > start, Action< Client > end)