1
0

BanManager.Notification.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. using System.Text.Json.Serialization;
  2. namespace Content.Server.Administration.Managers;
  3. public sealed partial class BanManager
  4. {
  5. // Responsible for ban notification handling.
  6. // Ban notifications are sent through the database to notify the entire server group that a new ban has been added,
  7. // so that people will get kicked if they are banned on a different server than the one that placed the ban.
  8. //
  9. // Ban notifications are currently sent by a trigger in the database, automatically.
  10. /// <summary>
  11. /// The notification channel used to broadcast information about new bans.
  12. /// </summary>
  13. public const string BanNotificationChannel = "ban_notification";
  14. // Rate limit to avoid undue load from mass-ban imports.
  15. // Only process 10 bans per 30 second interval.
  16. //
  17. // I had the idea of maybe binning this by postgres transaction ID,
  18. // to avoid any possibility of dropping a normal ban by coincidence.
  19. // Didn't bother implementing this though.
  20. private static readonly TimeSpan BanNotificationRateLimitTime = TimeSpan.FromSeconds(30);
  21. private const int BanNotificationRateLimitCount = 10;
  22. private readonly object _banNotificationRateLimitStateLock = new();
  23. private TimeSpan _banNotificationRateLimitStart;
  24. private int _banNotificationRateLimitCount;
  25. private bool OnDatabaseNotificationEarlyFilter()
  26. {
  27. if (!CheckBanRateLimit())
  28. {
  29. _sawmill.Verbose("Not processing ban notification due to rate limit");
  30. return false;
  31. }
  32. return true;
  33. }
  34. private async void ProcessBanNotification(BanNotificationData data)
  35. {
  36. if ((await _entryManager.ServerEntity).Id == data.ServerId)
  37. {
  38. _sawmill.Verbose("Not processing ban notification: came from this server");
  39. return;
  40. }
  41. _sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
  42. var ban = await _db.GetServerBanAsync(data.BanId);
  43. if (ban == null)
  44. {
  45. _sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
  46. return;
  47. }
  48. KickMatchingConnectedPlayers(ban, "ban notification");
  49. }
  50. private bool CheckBanRateLimit()
  51. {
  52. lock (_banNotificationRateLimitStateLock)
  53. {
  54. var now = _gameTiming.RealTime;
  55. if (_banNotificationRateLimitStart + BanNotificationRateLimitTime < now)
  56. {
  57. // Rate limit period expired, restart it.
  58. _banNotificationRateLimitCount = 1;
  59. _banNotificationRateLimitStart = now;
  60. return true;
  61. }
  62. _banNotificationRateLimitCount += 1;
  63. return _banNotificationRateLimitCount <= BanNotificationRateLimitCount;
  64. }
  65. }
  66. /// <summary>
  67. /// Data sent along the notification channel for a single ban notification.
  68. /// </summary>
  69. private sealed class BanNotificationData
  70. {
  71. /// <summary>
  72. /// The ID of the new ban object in the database to check.
  73. /// </summary>
  74. [JsonRequired, JsonPropertyName("ban_id")]
  75. public int BanId { get; init; }
  76. /// <summary>
  77. /// The id of the server the ban was made on.
  78. /// This is used to avoid double work checking the ban on the originating server.
  79. /// </summary>
  80. /// <remarks>
  81. /// This is optional in case the ban was made outside a server (SS14.Admin)
  82. /// </remarks>
  83. [JsonPropertyName("server_id")]
  84. public int? ServerId { get; init; }
  85. }
  86. }