fix rcon issue
This commit is contained in:
parent
3ad4aa2196
commit
ff0d22c142
@ -456,7 +456,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
||||||
{
|
{
|
||||||
Task.Run<Task>(async () =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
@ -464,14 +464,14 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
var success = true;
|
var success = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = (await server.GetDvarAsync<string>(dvarName, token: tokenSource.Token)).Value;
|
result = server.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _onProcessing.WaitAsync();
|
_onProcessing.Wait();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
@ -495,7 +495,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||||
{
|
{
|
||||||
Task.Run<Task>(async () =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
@ -503,14 +503,14 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token);
|
server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _onProcessing.WaitAsync();
|
_onProcessing.Wait();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
|
@ -33,7 +33,8 @@ namespace Integrations.Cod
|
|||||||
private readonly Encoding _gameEncoding;
|
private readonly Encoding _gameEncoding;
|
||||||
private readonly int _retryAttempts;
|
private readonly int _retryAttempts;
|
||||||
|
|
||||||
public CodRConConnection(IPEndPoint ipEndpoint, string password, ILogger<CodRConConnection> log, Encoding gameEncoding, int retryAttempts)
|
public CodRConConnection(IPEndPoint ipEndpoint, string password, ILogger<CodRConConnection> log,
|
||||||
|
Encoding gameEncoding, int retryAttempts)
|
||||||
{
|
{
|
||||||
RConPassword = password;
|
RConPassword = password;
|
||||||
_gameEncoding = gameEncoding;
|
_gameEncoding = gameEncoding;
|
||||||
@ -66,6 +67,11 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogDebug("Releasing OnComplete {Count}", ActiveQueries[Endpoint].OnComplete.CurrentCount);
|
||||||
|
}
|
||||||
|
|
||||||
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
|
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
|
||||||
{
|
{
|
||||||
ActiveQueries[Endpoint].OnComplete.Release();
|
ActiveQueries[Endpoint].OnComplete.Release();
|
||||||
@ -73,14 +79,19 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (!ActiveQueries.ContainsKey(Endpoint))
|
if (!ActiveQueries.ContainsKey(Endpoint))
|
||||||
{
|
{
|
||||||
ActiveQueries.TryAdd(Endpoint, new ConnectionState());
|
ActiveQueries.TryAdd(Endpoint, new ConnectionState());
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectionState = ActiveQueries[Endpoint];
|
if (!ActiveQueries.TryGetValue(Endpoint, out var connectionState))
|
||||||
|
{
|
||||||
|
_log.LogError("Could not retrieve connection state");
|
||||||
|
throw new InvalidOperationException("Could not get connection state");
|
||||||
|
}
|
||||||
|
|
||||||
_log.LogDebug("Waiting for semaphore to be released [{Endpoint}]", Endpoint);
|
_log.LogDebug("Waiting for semaphore to be released [{Endpoint}]", Endpoint);
|
||||||
|
|
||||||
@ -91,6 +102,8 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("OnComplete did not complete before timeout {Count}",
|
||||||
|
connectionState.OnComplete.CurrentCount);
|
||||||
throw new RConException("Timed out waiting for access to rcon socket");
|
throw new RConException("Timed out waiting for access to rcon socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,16 +113,20 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(_config.FloodProtectInterval - (int)timeSinceLastQuery, token);
|
var delay = _config.FloodProtectInterval - (int)timeSinceLastQuery;
|
||||||
|
_log.LogDebug("Delaying for {Delay}ms", delay);
|
||||||
|
await Task.Delay(delay, token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("Waiting for flood protect did not complete before timeout timeout {Count}",
|
||||||
|
connectionState.OnComplete.CurrentCount);
|
||||||
throw new RConException("Timed out waiting for flood protect to expire");
|
throw new RConException("Timed out waiting for flood protect to expire");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
|
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
|
||||||
_log.LogDebug("Query {@QueryInfo}", new { endpoint=Endpoint.ToString(), type, parameters });
|
_log.LogDebug("Query {@QueryInfo}", new { endpoint = Endpoint.ToString(), type, parameters });
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
var waitForResponse = _config.WaitForResponse;
|
var waitForResponse = _config.WaitForResponse;
|
||||||
@ -163,7 +180,6 @@ namespace Integrations.Cod
|
|||||||
// e.g: emoji -> windows-1252
|
// e.g: emoji -> windows-1252
|
||||||
catch (OverflowException ex)
|
catch (OverflowException ex)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {Encoding} {Params}",
|
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {Encoding} {Params}",
|
||||||
@ -201,6 +217,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("OnSent did not complete in time");
|
||||||
throw new RConException("Timed out waiting for access to RCon send socket");
|
throw new RConException("Timed out waiting for access to RCon send socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,14 +228,13 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
throw new RConException("Timed out waiting for access to RCon receive socket");
|
_log.LogDebug("OnReceived did not complete in time");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (connectionState.OnSentData.CurrentCount == 0)
|
if (connectionState.OnSentData.CurrentCount == 0)
|
||||||
{
|
{
|
||||||
connectionState.OnSentData.Release();
|
connectionState.OnSentData.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new RConException("Timed out waiting for access to RCon receive socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState.SendEventArgs.UserToken = new ConnectionUserToken
|
connectionState.SendEventArgs.UserToken = new ConnectionUserToken
|
||||||
@ -242,6 +258,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if ((response?.Length == 0 || response[0].Length == 0) && waitForResponse)
|
if ((response?.Length == 0 || response[0].Length == 0) && waitForResponse)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("0 bytes received from rcon request");
|
||||||
throw new RConException("Expected response but got 0 bytes back");
|
throw new RConException("Expected response but got 0 bytes back");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +269,7 @@ 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
|
||||||
|
_log.LogDebug("OperationCanceledException when waiting for payload send to complete");
|
||||||
connectionState.ConnectionAttempts = 0;
|
connectionState.ConnectionAttempts = 0;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -265,7 +283,8 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
_log.LogDebug("OperationCancelled while waiting for retry");
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
@ -311,11 +330,13 @@ namespace Integrations.Cod
|
|||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS
|
||||||
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
? ReassembleSegmentedStatus(response)
|
||||||
|
: RecombineMessages(response);
|
||||||
|
|
||||||
// note: not all games respond if the password is wrong or not set
|
// note: not all games respond if the password is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
|
responseString.Contains("rconpassword"))
|
||||||
{
|
{
|
||||||
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
||||||
}
|
}
|
||||||
@ -327,11 +348,14 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (responseString.Contains(_config.ServerNotRunningResponse))
|
if (responseString.Contains(_config.ServerNotRunningResponse))
|
||||||
{
|
{
|
||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"]
|
||||||
|
.FormatExt(Endpoint.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseHeaderMatch = Regex.Match(responseString, _config.CommandPrefixes.RConResponse).Value;
|
var responseHeaderMatch = Regex.Match(responseString, _config.CommandPrefixes.RConResponse).Value;
|
||||||
var headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? _config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
|
var headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO
|
||||||
|
? _config.CommandPrefixes.RconGetInfoResponseHeader
|
||||||
|
: responseHeaderMatch);
|
||||||
|
|
||||||
if (headerSplit.Length != 2)
|
if (headerSplit.Length != 2)
|
||||||
{
|
{
|
||||||
@ -369,7 +393,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
splitStatusStrings.Add(responseString.Replace(_config.CommandPrefixes.RConResponse, "").TrimEnd('\0'));
|
splitStatusStrings.Add(responseString.Replace(_config.CommandPrefixes.RConResponse, "")
|
||||||
|
.TrimEnd('\0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,8 +421,10 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
message = message.Replace(_config.CommandPrefixes.RConResponse, "");
|
message = message.Replace(_config.CommandPrefixes.RConResponse, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(message);
|
builder.Append(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append('\n');
|
builder.Append('\n');
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
@ -410,6 +437,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (rconSocket is null)
|
if (rconSocket is null)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("Invalid state");
|
||||||
throw new InvalidOperationException("State is not valid for socket operation");
|
throw new InvalidOperationException("State is not valid for socket operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +447,7 @@ 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.ReceiveEventArgs.UserToken = connectionState.SendEventArgs.UserToken;
|
||||||
connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
|
connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
|
||||||
connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
|
connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
|
||||||
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
||||||
@ -438,7 +467,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (!complete)
|
if (!complete)
|
||||||
{
|
{
|
||||||
using(LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogWarning("Socket timed out while sending RCon data on attempt {Attempt}",
|
_log.LogWarning("Socket timed out while sending RCon data on attempt {Attempt}",
|
||||||
connectionState.ConnectionAttempts);
|
connectionState.ConnectionAttempts);
|
||||||
@ -461,7 +490,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Waiting to asynchronously receive data on attempt #{ConnectionAttempts}", connectionState.ConnectionAttempts);
|
_log.LogDebug("Waiting to asynchronously receive data on attempt #{ConnectionAttempts}",
|
||||||
|
connectionState.ConnectionAttempts);
|
||||||
|
|
||||||
var completed = false;
|
var completed = false;
|
||||||
|
|
||||||
@ -493,6 +523,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
_log.LogDebug("OnDataReceived did not complete in allocated time");
|
||||||
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -521,7 +552,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Read {BytesTransferred} bytes from {Endpoint}", e.BytesTransferred, e.RemoteEndPoint?.ToString());
|
_log.LogDebug("Read {BytesTransferred} bytes from {Endpoint}", e.BytesTransferred,
|
||||||
|
e.RemoteEndPoint?.ToString());
|
||||||
|
|
||||||
// this occurs when we close the socket
|
// this occurs when we close the socket
|
||||||
if (e.BytesTransferred == 0)
|
if (e.BytesTransferred == 0)
|
||||||
@ -638,7 +670,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Sent {ByteCount} bytes to {Endpoint}", e.Buffer?.Length, e.ConnectSocket?.RemoteEndPoint?.ToString());
|
_log.LogDebug("Sent {ByteCount} bytes to {Endpoint}", e.Buffer?.Length,
|
||||||
|
e.ConnectSocket?.RemoteEndPoint?.ToString());
|
||||||
|
|
||||||
var semaphore = ActiveQueries[Endpoint].OnSentData;
|
var semaphore = ActiveQueries[Endpoint].OnSentData;
|
||||||
try
|
try
|
||||||
|
@ -20,7 +20,7 @@ namespace Integrations.Cod
|
|||||||
public int ConnectionAttempts { get; set; }
|
public int ConnectionAttempts { get; set; }
|
||||||
private const int BufferSize = 16384;
|
private const int BufferSize = 16384;
|
||||||
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
||||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
public readonly SemaphoreSlim OnComplete = new(1, 1);
|
||||||
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);
|
||||||
|
|
||||||
|
@ -482,7 +482,7 @@ const pollForEvents = server => {
|
|||||||
if (!state.waitingOnOutput) {
|
if (!state.waitingOnOutput) {
|
||||||
if (state.queuedMessages.length === 0) {
|
if (state.queuedMessages.length === 0) {
|
||||||
logger.WriteDebug('No messages in queue');
|
logger.WriteDebug('No messages in queue');
|
||||||
return;``
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.waitingOnOutput = true;
|
state.waitingOnOutput = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user