1
0

PlayerRateLimitManager.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. using System.Runtime.InteropServices;
  2. using Content.Server.Administration.Logs;
  3. using Content.Shared.Database;
  4. using Content.Shared.Players.RateLimiting;
  5. using Robust.Server.Player;
  6. using Robust.Shared.Configuration;
  7. using Robust.Shared.Enums;
  8. using Robust.Shared.Player;
  9. using Robust.Shared.Timing;
  10. using Robust.Shared.Utility;
  11. namespace Content.Server.Players.RateLimiting;
  12. public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
  13. {
  14. [Dependency] private readonly IAdminLogManager _adminLog = default!;
  15. [Dependency] private readonly IGameTiming _gameTiming = default!;
  16. [Dependency] private readonly IConfigurationManager _cfg = default!;
  17. [Dependency] private readonly IPlayerManager _playerManager = default!;
  18. private readonly Dictionary<string, RegistrationData> _registrations = new();
  19. private readonly Dictionary<ICommonSession, Dictionary<string, RateLimitDatum>> _rateLimitData = new();
  20. public override RateLimitStatus CountAction(ICommonSession player, string key)
  21. {
  22. if (player.Status == SessionStatus.Disconnected)
  23. throw new ArgumentException("Player is not connected");
  24. if (!_registrations.TryGetValue(key, out var registration))
  25. throw new ArgumentException($"Unregistered key: {key}");
  26. var playerData = _rateLimitData.GetOrNew(player);
  27. ref var datum = ref CollectionsMarshal.GetValueRefOrAddDefault(playerData, key, out _);
  28. var time = _gameTiming.RealTime;
  29. if (datum.CountExpires < time)
  30. {
  31. // Period expired, reset it.
  32. datum.CountExpires = time + registration.LimitPeriod;
  33. datum.Count = 0;
  34. datum.Announced = false;
  35. }
  36. datum.Count += 1;
  37. if (datum.Count <= registration.LimitCount)
  38. return RateLimitStatus.Allowed;
  39. // Breached rate limits, inform admins if configured.
  40. // Negative delays can be used to disable admin announcements.
  41. if (registration.AdminAnnounceDelay is {TotalSeconds: >= 0} cvarAnnounceDelay)
  42. {
  43. if (datum.NextAdminAnnounce < time)
  44. {
  45. registration.Registration.AdminAnnounceAction!(player);
  46. datum.NextAdminAnnounce = time + cvarAnnounceDelay;
  47. }
  48. }
  49. if (!datum.Announced)
  50. {
  51. registration.Registration.PlayerLimitedAction?.Invoke(player);
  52. _adminLog.Add(
  53. registration.Registration.AdminLogType,
  54. LogImpact.Medium,
  55. $"Player {player} breached '{key}' rate limit ");
  56. datum.Announced = true;
  57. }
  58. return RateLimitStatus.Blocked;
  59. }
  60. public override void Register(string key, RateLimitRegistration registration)
  61. {
  62. if (_registrations.ContainsKey(key))
  63. throw new InvalidOperationException($"Key already registered: {key}");
  64. var data = new RegistrationData
  65. {
  66. Registration = registration,
  67. };
  68. if ((registration.AdminAnnounceAction == null) != (registration.CVarAdminAnnounceDelay == null))
  69. {
  70. throw new ArgumentException(
  71. $"Must set either both {nameof(registration.AdminAnnounceAction)} and {nameof(registration.CVarAdminAnnounceDelay)} or neither");
  72. }
  73. _cfg.OnValueChanged(
  74. registration.CVarLimitCount,
  75. i => data.LimitCount = i,
  76. invokeImmediately: true);
  77. _cfg.OnValueChanged(
  78. registration.CVarLimitPeriodLength,
  79. i => data.LimitPeriod = TimeSpan.FromSeconds(i),
  80. invokeImmediately: true);
  81. if (registration.CVarAdminAnnounceDelay != null)
  82. {
  83. _cfg.OnValueChanged(
  84. registration.CVarAdminAnnounceDelay,
  85. i => data.AdminAnnounceDelay = TimeSpan.FromSeconds(i),
  86. invokeImmediately: true);
  87. }
  88. _registrations.Add(key, data);
  89. }
  90. public override void Initialize()
  91. {
  92. _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
  93. }
  94. private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
  95. {
  96. if (e.NewStatus == SessionStatus.Disconnected)
  97. _rateLimitData.Remove(e.Session);
  98. }
  99. private sealed class RegistrationData
  100. {
  101. public required RateLimitRegistration Registration { get; init; }
  102. public TimeSpan LimitPeriod { get; set; }
  103. public int LimitCount { get; set; }
  104. public TimeSpan? AdminAnnounceDelay { get; set; }
  105. }
  106. private struct RateLimitDatum
  107. {
  108. /// <summary>
  109. /// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) this rate limit period will expire at.
  110. /// </summary>
  111. public TimeSpan CountExpires;
  112. /// <summary>
  113. /// How many actions have been done in the current rate limit period.
  114. /// </summary>
  115. public int Count;
  116. /// <summary>
  117. /// Have we announced to the player that they've been blocked in this rate limit period?
  118. /// </summary>
  119. public bool Announced;
  120. /// <summary>
  121. /// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) of the
  122. /// next time we can send an announcement to admins about rate limit breach.
  123. /// </summary>
  124. public TimeSpan NextAdminAnnounce;
  125. }
  126. }