Throttle people trying to connect to a full server. (#20972)
* Throttle people trying to connect to a full server. To reduce spam/load on the server and connection logs table. Players are forced to wait 30 seconds after getting denied for "server full", before they can try connecting again. This code is an absolute nightmare mess. I tried to re-use the existing code for the redial timer but god everything here sucks so much. Requires https://github.com/space-wizards/RobustToolbox/pull/4487 * Use new NetDisconnectMessage API instead. * Add admin.bypass_max_players CVar. Just something to help with debugging the player cap on dev, I don't expect this to ever be disabled on real servers.
This commit is contained in:
committed by
GitHub
parent
3cb1c585c5
commit
0ecc5e8c96
@@ -54,6 +54,7 @@ namespace Content.Client.Launcher
|
|||||||
public event Action<Page>? PageChanged;
|
public event Action<Page>? PageChanged;
|
||||||
public event Action<string?>? ConnectFailReasonChanged;
|
public event Action<string?>? ConnectFailReasonChanged;
|
||||||
public event Action<ClientConnectionState>? ConnectionStateChanged;
|
public event Action<ClientConnectionState>? ConnectionStateChanged;
|
||||||
|
public event Action<NetConnectFailArgs>? ConnectFailed;
|
||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
{
|
{
|
||||||
@@ -85,6 +86,7 @@ namespace Content.Client.Launcher
|
|||||||
}
|
}
|
||||||
ConnectFailReason = args.Reason;
|
ConnectFailReason = args.Reason;
|
||||||
CurrentPage = Page.ConnectFailed;
|
CurrentPage = Page.ConnectFailed;
|
||||||
|
ConnectFailed?.Invoke(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConnectStateChanged(ClientConnectionState state)
|
private void OnConnectStateChanged(ClientConnectionState state)
|
||||||
|
|||||||
@@ -33,10 +33,6 @@
|
|||||||
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
|
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||||
<Button Name="RedialButton" Text="{Loc 'connecting-redial'}"
|
|
||||||
Disabled="True"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</Control>
|
</Control>
|
||||||
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using Robust.Client.UserInterface.XAML;
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
@@ -21,12 +20,15 @@ namespace Content.Client.Launcher
|
|||||||
{
|
{
|
||||||
private const float RedialWaitTimeSeconds = 15f;
|
private const float RedialWaitTimeSeconds = 15f;
|
||||||
private readonly LauncherConnecting _state;
|
private readonly LauncherConnecting _state;
|
||||||
|
private float _waitTime;
|
||||||
|
|
||||||
|
// Pressing reconnect will redial instead of simply reconnecting.
|
||||||
|
private bool _redial;
|
||||||
|
|
||||||
private readonly IRobustRandom _random;
|
private readonly IRobustRandom _random;
|
||||||
private readonly IPrototypeManager _prototype;
|
private readonly IPrototypeManager _prototype;
|
||||||
private readonly IConfigurationManager _cfg;
|
private readonly IConfigurationManager _cfg;
|
||||||
|
|
||||||
private float _redialWaitTime = RedialWaitTimeSeconds;
|
|
||||||
|
|
||||||
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
|
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
|
||||||
IPrototypeManager prototype, IConfigurationManager config)
|
IPrototypeManager prototype, IConfigurationManager config)
|
||||||
{
|
{
|
||||||
@@ -42,14 +44,8 @@ namespace Content.Client.Launcher
|
|||||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||||
|
|
||||||
ChangeLoginTip();
|
ChangeLoginTip();
|
||||||
ReconnectButton.OnPressed += _ => _state.RetryConnect();
|
ReconnectButton.OnPressed += ReconnectButtonPressed;
|
||||||
// Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug)
|
RetryButton.OnPressed += ReconnectButtonPressed;
|
||||||
RedialButton.OnPressed += _ =>
|
|
||||||
{
|
|
||||||
if (!_state.Redial())
|
|
||||||
_state.RetryConnect();
|
|
||||||
};
|
|
||||||
RetryButton.OnPressed += _ => _state.RetryConnect();
|
|
||||||
ExitButton.OnPressed += _ => _state.Exit();
|
ExitButton.OnPressed += _ => _state.Exit();
|
||||||
|
|
||||||
var addr = state.Address;
|
var addr = state.Address;
|
||||||
@@ -59,6 +55,7 @@ namespace Content.Client.Launcher
|
|||||||
state.PageChanged += OnPageChanged;
|
state.PageChanged += OnPageChanged;
|
||||||
state.ConnectFailReasonChanged += ConnectFailReasonChanged;
|
state.ConnectFailReasonChanged += ConnectFailReasonChanged;
|
||||||
state.ConnectionStateChanged += ConnectionStateChanged;
|
state.ConnectionStateChanged += ConnectionStateChanged;
|
||||||
|
state.ConnectFailed += HandleDisconnectReason;
|
||||||
|
|
||||||
ConnectionStateChanged(state.ConnectionState);
|
ConnectionStateChanged(state.ConnectionState);
|
||||||
|
|
||||||
@@ -68,6 +65,19 @@ namespace Content.Client.Launcher
|
|||||||
LastNetDisconnectedArgsChanged(edim.LastNetDisconnectedArgs);
|
LastNetDisconnectedArgsChanged(edim.LastNetDisconnectedArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just button, there's only one at once anyways :)
|
||||||
|
private void ReconnectButtonPressed(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (_redial)
|
||||||
|
{
|
||||||
|
// Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug)
|
||||||
|
if (_state.Redial())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.RetryConnect();
|
||||||
|
}
|
||||||
|
|
||||||
private void ConnectFailReasonChanged(string? reason)
|
private void ConnectFailReasonChanged(string? reason)
|
||||||
{
|
{
|
||||||
ConnectFailReason.SetMessage(reason == null
|
ConnectFailReason.SetMessage(reason == null
|
||||||
@@ -77,9 +87,30 @@ namespace Content.Client.Launcher
|
|||||||
|
|
||||||
private void LastNetDisconnectedArgsChanged(NetDisconnectedArgs? args)
|
private void LastNetDisconnectedArgsChanged(NetDisconnectedArgs? args)
|
||||||
{
|
{
|
||||||
var redialFlag = args?.RedialFlag ?? false;
|
HandleDisconnectReason(args);
|
||||||
RedialButton.Visible = redialFlag;
|
}
|
||||||
ReconnectButton.Visible = !redialFlag;
|
|
||||||
|
private void HandleDisconnectReason(INetStructuredReason? reason)
|
||||||
|
{
|
||||||
|
if (reason == null)
|
||||||
|
{
|
||||||
|
_waitTime = 0;
|
||||||
|
_redial = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_redial = reason.RedialFlag;
|
||||||
|
|
||||||
|
if (reason.Message.Int32Of("delay") is { } delay)
|
||||||
|
{
|
||||||
|
_waitTime = delay;
|
||||||
|
}
|
||||||
|
else if (_redial)
|
||||||
|
{
|
||||||
|
_waitTime = RedialWaitTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChangeLoginTip()
|
private void ChangeLoginTip()
|
||||||
@@ -108,16 +139,27 @@ namespace Content.Client.Launcher
|
|||||||
protected override void FrameUpdate(FrameEventArgs args)
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
_redialWaitTime -= args.DeltaSeconds;
|
|
||||||
if (_redialWaitTime <= 0)
|
var button = _state.CurrentPage == LauncherConnecting.Page.ConnectFailed
|
||||||
|
? RetryButton
|
||||||
|
: ReconnectButton;
|
||||||
|
|
||||||
|
_waitTime -= args.DeltaSeconds;
|
||||||
|
if (_waitTime <= 0)
|
||||||
{
|
{
|
||||||
RedialButton.Disabled = false;
|
button.Disabled = false;
|
||||||
RedialButton.Text = Loc.GetString("connecting-redial");
|
var key = _redial
|
||||||
|
? "connecting-redial"
|
||||||
|
: _state.CurrentPage == LauncherConnecting.Page.ConnectFailed
|
||||||
|
? "connecting-reconnect"
|
||||||
|
: "connecting-retry";
|
||||||
|
|
||||||
|
button.Text = Loc.GetString(key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RedialButton.Disabled = true;
|
button.Disabled = true;
|
||||||
RedialButton.Text = Loc.GetString("connecting-redial-wait", ("time", _redialWaitTime.ToString("00.000")));
|
button.Text = Loc.GetString("connecting-redial-wait", ("time", _waitTime.ToString("00.000")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
@@ -77,7 +78,11 @@ namespace Content.Server.Connection
|
|||||||
if (banHits is { Count: > 0 })
|
if (banHits is { Count: > 0 })
|
||||||
await _db.AddServerBanHitsAsync(id, banHits);
|
await _db.AddServerBanHitsAsync(id, banHits);
|
||||||
|
|
||||||
e.Deny(msg);
|
var properties = new Dictionary<string, object>();
|
||||||
|
if (reason == ConnectionDenyReason.Full)
|
||||||
|
properties["delay"] = _cfg.GetCVar(CCVars.GameServerFullReconnectDelay);
|
||||||
|
|
||||||
|
e.Deny(new NetDenyReason(msg, properties));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -156,7 +161,8 @@ namespace Content.Server.Connection
|
|||||||
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
||||||
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
||||||
status == PlayerGameStatus.JoinedGame;
|
status == PlayerGameStatus.JoinedGame;
|
||||||
if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && adminData is null) && !wasInGame)
|
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && adminData != null;
|
||||||
|
if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame)
|
||||||
{
|
{
|
||||||
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
|
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,13 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<int> SoftMaxPlayers =
|
public static readonly CVarDef<int> SoftMaxPlayers =
|
||||||
CVarDef.Create("game.soft_max_players", 30, CVar.SERVERONLY | CVar.ARCHIVE);
|
CVarDef.Create("game.soft_max_players", 30, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a player gets denied connection to the server,
|
||||||
|
/// how long they are forced to wait before attempting to reconnect.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<int> GameServerFullReconnectDelay =
|
||||||
|
CVarDef.Create("game.server_full_reconnect_delay", 30, CVar.SERVERONLY);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not panic bunker is currently enabled.
|
/// Whether or not panic bunker is currently enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -852,6 +859,13 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<float> AdminAfkTime =
|
public static readonly CVarDef<float> AdminAfkTime =
|
||||||
CVarDef.Create("admin.afk_time", 600f, CVar.SERVERONLY);
|
CVarDef.Create("admin.afk_time", 600f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, admins are able to connect even if
|
||||||
|
/// <see cref="SoftMaxPlayers"/> would otherwise block regular players.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<bool> AdminBypassMaxPlayers =
|
||||||
|
CVarDef.Create("admin.bypass_max_players", true, CVar.SERVERONLY);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Explosions
|
* Explosions
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user