tweak cod rcon connection and fix max health for hide integration command

This commit is contained in:
RaidMax 2022-03-05 13:13:00 -06:00
parent 59ca399045
commit acf66da4ca
3 changed files with 107 additions and 118 deletions

View File

@ -128,6 +128,11 @@ DisplayWelcomeData()
PlayerConnectEvents() PlayerConnectEvents()
{ {
self endon( "disconnect" ); self endon( "disconnect" );
if ( IsDefined( self.isHidden ) && self.isHidden )
{
self HideImpl();
}
clientData = self.pers[level.clientDataKey]; clientData = self.pers[level.clientDataKey];
@ -591,18 +596,17 @@ HideImpl()
return; return;
} }
if ( IsDefined( self.isHidden ) && self.isHidden )
{
self IPrintLnBold( "You are already hidden" );
return;
}
self SetClientDvar( "sv_cheats", 1 ); self SetClientDvar( "sv_cheats", 1 );
self SetClientDvar( "cg_thirdperson", 1 ); self SetClientDvar( "cg_thirdperson", 1 );
self SetClientDvar( "sv_cheats", 0 ); self SetClientDvar( "sv_cheats", 0 );
self.savedHealth = self.health; if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
self.health = 9999; {
self.savedHealth = self.health;
}
self.maxhealth = 99999;
self.health = 99999;
self.isHidden = true; self.isHidden = true;
self Hide(); self Hide();

View File

@ -44,14 +44,33 @@ namespace Integrations.Cod
public void SetConfiguration(IRConParser parser) public void SetConfiguration(IRConParser parser)
{ {
this._parser = parser; _parser = parser;
_config = parser.Configuration; _config = parser.Configuration;
} }
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "",
CancellationToken token = default) CancellationToken token = default)
{ {
return await SendQueryAsyncInternal(type, parameters, token); try
{
return await SendQueryAsyncInternal(type, parameters, token);
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", Endpoint.ToString()))
{
_log.LogWarning(ex, "Could not complete RCon request");
}
throw;
}
finally
{
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
{
ActiveQueries[Endpoint].OnComplete.Release();
}
}
} }
private async Task<string[]> SendQueryAsyncInternal(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default) private async Task<string[]> SendQueryAsyncInternal(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default)
@ -87,13 +106,6 @@ namespace Integrations.Cod
{ {
throw new RConException("Timed out waiting for flood protect to expire"); throw new RConException("Timed out waiting for flood protect to expire");
} }
finally
{
if (connectionState.OnComplete.CurrentCount == 0)
{
connectionState.OnComplete.Release();
}
}
} }
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint); _log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
@ -158,15 +170,7 @@ namespace Integrations.Cod
_gameEncoding.EncodingName, parameters); _gameEncoding.EncodingName, parameters);
} }
throw new RConException($"Invalid character encountered when converting encodings"); throw new RConException("Invalid character encountered when converting encodings");
}
finally
{
if (connectionState.OnComplete.CurrentCount == 0)
{
connectionState.OnComplete.Release();
}
} }
byte[][] response = null; byte[][] response = null;
@ -182,14 +186,15 @@ namespace Integrations.Cod
_retryAttempts, parameters); _retryAttempts, parameters);
} }
} }
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
DontFragment = false,
Ttl = 100,
ExclusiveAddressUse = true,
})
{ {
DontFragment = false, // wait for send to be ready
Ttl = 100,
ExclusiveAddressUse = true,
})
{
// wait for send to complete
try try
{ {
await connectionState.OnSentData.WaitAsync(token); await connectionState.OnSentData.WaitAsync(token);
@ -198,15 +203,8 @@ namespace Integrations.Cod
{ {
throw new RConException("Timed out waiting for access to RCon send socket"); throw new RConException("Timed out waiting for access to RCon send socket");
} }
finally
{
if (connectionState.OnComplete.CurrentCount == 0 )
{
connectionState.OnComplete.Release();
}
}
// wait for receive to complete // wait for receive to be ready
try try
{ {
await connectionState.OnReceivedData.WaitAsync(token); await connectionState.OnReceivedData.WaitAsync(token);
@ -217,25 +215,24 @@ namespace Integrations.Cod
} }
finally finally
{ {
if (connectionState.OnComplete.CurrentCount == 0 )
{
connectionState.OnComplete.Release();
}
if (connectionState.OnSentData.CurrentCount == 0) if (connectionState.OnSentData.CurrentCount == 0)
{ {
connectionState.OnSentData.Release(); connectionState.OnSentData.Release();
} }
} }
connectionState.SendEventArgs.UserToken = socket;
connectionState.ConnectionAttempts++;
connectionState.BytesReadPerSegment.Clear();
var exceptionCaught = false;
_log.LogDebug("Sending {PayloadLength} bytes to [{Endpoint}] ({ConnectionAttempts}/{AllowedConnectionFailures})", connectionState.SendEventArgs.UserToken = new ConnectionUserToken
payload.Length, Endpoint, connectionState.ConnectionAttempts, _retryAttempts); {
Socket = socket,
CancellationToken = token
};
connectionState.ConnectionAttempts++;
connectionState.BytesReadPerSegment.Clear();
_log.LogDebug(
"Sending {PayloadLength} bytes to [{Endpoint}] ({ConnectionAttempts}/{AllowedConnectionFailures})",
payload.Length, Endpoint, connectionState.ConnectionAttempts, _retryAttempts);
try try
{ {
@ -255,26 +252,22 @@ namespace Integrations.Cod
{ {
// if we timed out due to the cancellation token, // if we timed out due to the cancellation token,
// we don't want to count that as an attempt // we don't want to count that as an attempt
connectionState.ConnectionAttempts = 0; connectionState.ConnectionAttempts = 0;
} }
catch catch
{ {
// we want to retry with a delay // we want to retry with a delay
if (connectionState.ConnectionAttempts < _retryAttempts) if (connectionState.ConnectionAttempts < _retryAttempts)
{ {
exceptionCaught = true;
try try
{ {
await Task.Delay( await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts), token);
token != CancellationToken.None
? StaticHelpers.SocketTimeout(100) // if using cancellation token we don't care about attempt count
: StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts), token);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
throw new RConException("Timed out waiting on delay retry"); return Array.Empty<string>();
} }
goto retrySend; goto retrySend;
} }
@ -284,29 +277,33 @@ namespace Integrations.Cod
"Made {ConnectionAttempts} attempts to send RCon data to server, but received no response", "Made {ConnectionAttempts} attempts to send RCon data to server, but received no response",
connectionState.ConnectionAttempts); connectionState.ConnectionAttempts);
} }
connectionState.ConnectionAttempts = 0; connectionState.ConnectionAttempts = 0;
throw new NetworkException("Reached maximum retry attempts to send RCon data to server"); throw new NetworkException("Reached maximum retry attempts to send RCon data to server");
} }
finally finally
{ {
// we don't want to release if we're going to retry the query try
if (connectionState.OnComplete.CurrentCount == 0 && !exceptionCaught)
{ {
connectionState.OnComplete.Release(); if (connectionState.OnSentData.CurrentCount == 0)
} {
connectionState.OnSentData.Release();
}
if (connectionState.OnSentData.CurrentCount == 0) if (connectionState.OnReceivedData.CurrentCount == 0)
{ {
connectionState.OnSentData.Release(); connectionState.OnReceivedData.Release();
}
} }
catch
if (connectionState.OnReceivedData.CurrentCount == 0)
{ {
connectionState.OnReceivedData.Release(); // ignored because we can have the socket operation cancelled (which releases the semaphore) but
// this thread is not notified because it's an event
} }
} }
} }
// at this point we can run in parallel and the next request can start because we have our data
if (response.Length == 0) if (response.Length == 0)
{ {
_log.LogDebug("Received empty response for RCon request {@Query}", _log.LogDebug("Received empty response for RCon request {@Query}",
@ -357,7 +354,7 @@ namespace Integrations.Cod
/// </summary> /// </summary>
/// <param name="segments">array of segmented byte arrays</param> /// <param name="segments">array of segmented byte arrays</param>
/// <returns></returns> /// <returns></returns>
private string ReassembleSegmentedStatus(byte[][] segments) private string ReassembleSegmentedStatus(IEnumerable<byte[]> segments)
{ {
var splitStatusStrings = new List<string>(); var splitStatusStrings = new List<string>();
@ -391,28 +388,25 @@ namespace Integrations.Cod
return _gameEncoding.GetString(payload[0]).TrimEnd('\n') + '\n'; return _gameEncoding.GetString(payload[0]).TrimEnd('\n') + '\n';
} }
else var builder = new StringBuilder();
for (var i = 0; i < payload.Count; i++)
{ {
var builder = new StringBuilder(); var message = _gameEncoding.GetString(payload[i]).TrimEnd('\n') + '\n';
for (int i = 0; i < payload.Count; i++) if (i > 0)
{ {
string message = _gameEncoding.GetString(payload[i]).TrimEnd('\n') + '\n'; message = message.Replace(_config.CommandPrefixes.RConResponse, "");
if (i > 0)
{
message = message.Replace(_config.CommandPrefixes.RConResponse, "");
}
builder.Append(message);
} }
builder.Append('\n'); builder.Append(message);
return builder.ToString();
} }
builder.Append('\n');
return builder.ToString();
} }
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse, TimeSpan overrideTimeout, private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse, TimeSpan overrideTimeout,
CancellationToken token = default) CancellationToken token = default)
{ {
var connectionState = ActiveQueries[Endpoint]; var connectionState = ActiveQueries[Endpoint];
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken; var rconSocket = ((ConnectionUserToken)connectionState.SendEventArgs.UserToken)?.Socket;
if (rconSocket is null) if (rconSocket is null)
{ {
@ -425,8 +419,8 @@ namespace Integrations.Cod
// setup the event handlers only once because we're reusing the event args // setup the event handlers only once because we're reusing the event args
connectionState.SendEventArgs.Completed += OnDataSent; connectionState.SendEventArgs.Completed += OnDataSent;
connectionState.ReceiveEventArgs.Completed += OnDataReceived; connectionState.ReceiveEventArgs.Completed += OnDataReceived;
connectionState.SendEventArgs.RemoteEndPoint = this.Endpoint; connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
connectionState.ReceiveEventArgs.RemoteEndPoint = this.Endpoint; connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true; connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
connectionState.SendEventArgs.DisconnectReuseSocket = true; connectionState.SendEventArgs.DisconnectReuseSocket = true;
} }
@ -440,17 +434,9 @@ namespace Integrations.Cod
{ {
// the send has not been completed asynchronously // the send has not been completed asynchronously
// this really shouldn't ever happen because it's UDP // this really shouldn't ever happen because it's UDP
var complete = false; var complete = await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(4), token);
try
{
complete = await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(1), token);
}
catch (OperationCanceledException)
{
// ignored
}
if(!complete) if (!complete)
{ {
using(LogContext.PushProperty("Server", Endpoint.ToString())) using(LogContext.PushProperty("Server", Endpoint.ToString()))
{ {
@ -459,12 +445,6 @@ namespace Integrations.Cod
} }
rconSocket.Close(); rconSocket.Close();
if (connectionState.OnSentData.CurrentCount == 0)
{
connectionState.OnSentData.Release();
}
throw new NetworkException("Timed out sending RCon data", rconSocket); throw new NetworkException("Timed out sending RCon data", rconSocket);
} }
} }
@ -498,7 +478,7 @@ namespace Integrations.Cod
{ {
// ignored // ignored
} }
if (!completed) if (!completed)
{ {
if (connectionState.ConnectionAttempts > 1) // this reduces some spam for unstable connections if (connectionState.ConnectionAttempts > 1) // this reduces some spam for unstable connections
@ -511,11 +491,6 @@ namespace Integrations.Cod
StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts)); StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
} }
} }
if (connectionState.OnReceivedData.CurrentCount == 0)
{
connectionState.OnReceivedData.Release();
}
rconSocket.Close(); rconSocket.Close();
throw new NetworkException("Timed out receiving RCon response", rconSocket); throw new NetworkException("Timed out receiving RCon response", rconSocket);
@ -526,7 +501,7 @@ namespace Integrations.Cod
return GetResponseData(connectionState); return GetResponseData(connectionState);
} }
private byte[][] GetResponseData(ConnectionState connectionState) private static byte[][] GetResponseData(ConnectionState connectionState)
{ {
var responseList = new List<byte[]>(); var responseList = new List<byte[]>();
var totalBytesRead = 0; var totalBytesRead = 0;
@ -570,8 +545,12 @@ namespace Integrations.Cod
return; return;
} }
var state = ActiveQueries[Endpoint];
var cancellationRequested = ((ConnectionUserToken)e.UserToken)?.CancellationToken.IsCancellationRequested ??
false;
if (sender is not Socket sock) if (sender is not Socket sock || cancellationRequested)
{ {
var semaphore = ActiveQueries[Endpoint].OnReceivedData; var semaphore = ActiveQueries[Endpoint].OnReceivedData;
@ -591,9 +570,8 @@ namespace Integrations.Cod
return; return;
} }
var state = ActiveQueries[Endpoint];
state.BytesReadPerSegment.Add(e.BytesTransferred); state.BytesReadPerSegment.Add(e.BytesTransferred);
// I don't even want to know why this works for getting more data from Cod4x // I don't even want to know why this works for getting more data from Cod4x
// but I'm leaving it in here as long as it doesn't break anything. // but I'm leaving it in here as long as it doesn't break anything.
// it's very stupid... // it's very stupid...
@ -604,6 +582,7 @@ namespace Integrations.Cod
var totalBytesTransferred = e.BytesTransferred; var totalBytesTransferred = e.BytesTransferred;
_log.LogDebug("{Total} total bytes transferred with {Available} bytes remaining", totalBytesTransferred, _log.LogDebug("{Total} total bytes transferred with {Available} bytes remaining", totalBytesTransferred,
sock.Available); sock.Available);
// we still have available data so the payload was segmented // we still have available data so the payload was segmented
while (sock.Available > 0) while (sock.Available > 0)
{ {
@ -617,17 +596,17 @@ namespace Integrations.Cod
bufferSpaceAvailable); bufferSpaceAvailable);
continue; continue;
} }
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, totalBytesTransferred, sock.Available); state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, totalBytesTransferred, sock.Available);
if (sock.ReceiveAsync(state.ReceiveEventArgs)) if (sock.ReceiveAsync(state.ReceiveEventArgs))
{ {
_log.LogDebug("Remaining bytes are async"); _log.LogDebug("Remaining bytes are async");
continue; continue;
} }
_log.LogDebug("Read {BytesTransferred} synchronous bytes from {Endpoint}", _log.LogDebug("Read {BytesTransferred} synchronous bytes from {Endpoint}",
state.ReceiveEventArgs.BytesTransferred, e.RemoteEndPoint?.ToString()); state.ReceiveEventArgs.BytesTransferred, e.RemoteEndPoint?.ToString());
// we need to increment this here because the callback isn't executed if there's no pending IO // we need to increment this here because the callback isn't executed if there's no pending IO
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred); state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
totalBytesTransferred += state.ReceiveEventArgs.BytesTransferred; totalBytesTransferred += state.ReceiveEventArgs.BytesTransferred;

View File

@ -24,9 +24,15 @@ namespace Integrations.Cod
public readonly SemaphoreSlim OnSentData = new(1, 1); public readonly SemaphoreSlim OnSentData = new(1, 1);
public readonly SemaphoreSlim OnReceivedData = new (1, 1); public readonly SemaphoreSlim OnReceivedData = new (1, 1);
public List<int> BytesReadPerSegment { get; set; } = new List<int>(); public List<int> BytesReadPerSegment { get; set; } = new();
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs(); public SocketAsyncEventArgs SendEventArgs { get; set; } = new();
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs(); public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new();
public DateTime LastQuery { get; set; } = DateTime.Now; public DateTime LastQuery { get; set; } = DateTime.Now;
} }
internal class ConnectionUserToken
{
public Socket Socket { get; set; }
public CancellationToken CancellationToken { get; set; }
}
} }