clean up rcon, fix a bunch of little things
This commit is contained in:
@ -13,145 +13,44 @@ namespace SharedLibraryCore.RCon
|
||||
{
|
||||
class ConnectionState
|
||||
{
|
||||
public Socket Client { get; private set; }
|
||||
public int BufferSize { get; private set; }
|
||||
public byte[] Buffer { get; private set; }
|
||||
|
||||
public StringBuilder ResponseString { get; }
|
||||
|
||||
public ConnectionState(Socket cl)
|
||||
{
|
||||
BufferSize = 8192;
|
||||
Buffer = new byte[BufferSize];
|
||||
Client = cl;
|
||||
ResponseString = new StringBuilder();
|
||||
}
|
||||
public int ConnectionAttempts { get; set; }
|
||||
const int BufferSize = 4096;
|
||||
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
public class Connection
|
||||
{
|
||||
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
|
||||
public IPEndPoint Endpoint { get; private set; }
|
||||
public string RConPassword { get; private set; }
|
||||
ILogger Log;
|
||||
int FailedSends;
|
||||
int FailedReceives;
|
||||
string response;
|
||||
|
||||
ManualResetEvent OnConnected;
|
||||
ManualResetEvent OnSent;
|
||||
ManualResetEvent OnReceived;
|
||||
|
||||
public Connection(string ipAddress, int port, string password, ILogger log)
|
||||
{
|
||||
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
||||
RConPassword = password;
|
||||
Log = log;
|
||||
|
||||
OnConnected = new ManualResetEvent(false);
|
||||
OnSent = new ManualResetEvent(false);
|
||||
OnReceived = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
private void OnConnectedCallback(IAsyncResult ar)
|
||||
{
|
||||
var serverSocket = (Socket)ar.AsyncState;
|
||||
|
||||
try
|
||||
{
|
||||
serverSocket.EndConnect(ar);
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"Successfully initialized socket to {serverSocket.RemoteEndPoint}");
|
||||
#endif
|
||||
OnConnected.Set();
|
||||
}
|
||||
|
||||
catch (SocketException e)
|
||||
{
|
||||
throw new NetworkException($"Could not initialize socket for RCon - {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSentCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket serverConnection = (Socket)ar.AsyncState;
|
||||
|
||||
try
|
||||
{
|
||||
int sentByteNum = serverConnection.EndSend(ar);
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"Sent {sentByteNum} bytes to {serverConnection.RemoteEndPoint}");
|
||||
#endif
|
||||
// this is where we override our await to make it
|
||||
OnSent.Set();
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceivedCallback(IAsyncResult ar)
|
||||
{
|
||||
var connectionState = (ConnectionState)ar.AsyncState;
|
||||
var serverConnection = connectionState.Client;
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = serverConnection.EndReceive(ar);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"Received {bytesRead} bytes from {serverConnection.RemoteEndPoint}");
|
||||
#endif
|
||||
FailedReceives = 0;
|
||||
connectionState.ResponseString.Append(Utilities.EncodingType.GetString(connectionState.Buffer, 0, bytesRead).TrimEnd('\0') + '\n');
|
||||
|
||||
if (!connectionState.Buffer.Take(4).ToArray().SequenceEqual(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }))
|
||||
throw new NetworkException("Unexpected packet received");
|
||||
|
||||
/* if (FailedReceives == 0 && serverConnection.Available > 0)
|
||||
{
|
||||
serverConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
|
||||
new AsyncCallback(OnReceivedCallback), connectionState);
|
||||
}
|
||||
else*/
|
||||
{
|
||||
response = connectionState.ResponseString.ToString();
|
||||
OnReceived.Set();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response = connectionState.ResponseString.ToString();
|
||||
OnReceived.Set();
|
||||
}
|
||||
}
|
||||
|
||||
catch (SocketException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Log.WriteWarning($"Tried to check for more available bytes for disposed socket on {Endpoint}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true)
|
||||
{
|
||||
//// will this really prevent flooding?
|
||||
//if ((DateTime.Now - LastQuery).TotalMilliseconds < 350)
|
||||
//{
|
||||
// Thread.Sleep(350);
|
||||
// //await Task.Delay(350);
|
||||
//}
|
||||
if (!ActiveQueries.ContainsKey(this.Endpoint))
|
||||
{
|
||||
ActiveQueries.TryAdd(this.Endpoint, new ConnectionState());
|
||||
}
|
||||
|
||||
// LastQuery = DateTime.Now;
|
||||
var connectionState = ActiveQueries[this.Endpoint];
|
||||
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Waiting for semaphore to be released [${this.Endpoint}]");
|
||||
#endif
|
||||
await connectionState.OnComplete.WaitAsync();
|
||||
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Semaphore has been released [${this.Endpoint}]");
|
||||
#endif
|
||||
|
||||
OnSent.Reset();
|
||||
OnReceived.Reset();
|
||||
byte[] payload = null;
|
||||
|
||||
switch (type)
|
||||
@ -171,130 +70,103 @@ namespace SharedLibraryCore.RCon
|
||||
break;
|
||||
}
|
||||
|
||||
using (var socketConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp) { ExclusiveAddressUse = true } )
|
||||
byte[] response = null;
|
||||
retrySend:
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Sending {payload.Length} bytes to [${this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})");
|
||||
#endif
|
||||
try
|
||||
{
|
||||
socketConnection.BeginConnect(Endpoint, new AsyncCallback(OnConnectedCallback), socketConnection);
|
||||
|
||||
retrySend:
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"Sending Command {parameters}");
|
||||
#endif
|
||||
if (!OnConnected.WaitOne(StaticHelpers.SocketTimeout))
|
||||
throw new SocketException((int)SocketError.TimedOut);
|
||||
|
||||
socketConnection.BeginSend(payload, 0, payload.Length, 0, new AsyncCallback(OnSentCallback), socketConnection);
|
||||
bool success = await Task.FromResult(OnSent.WaitOne(StaticHelpers.SocketTimeout));
|
||||
|
||||
if (!success)
|
||||
{
|
||||
FailedSends++;
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"{FailedSends} failed sends to {socketConnection.RemoteEndPoint.ToString()}");
|
||||
#endif
|
||||
if (FailedSends < 4)
|
||||
goto retrySend;
|
||||
else if (FailedSends == 4)
|
||||
Log.WriteError($"Failed to send data to {socketConnection.RemoteEndPoint}");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (FailedSends >= 4)
|
||||
{
|
||||
Log.WriteVerbose($"Resumed send RCon connection with {socketConnection.RemoteEndPoint}");
|
||||
FailedSends = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (SocketException e)
|
||||
{
|
||||
// this result is normal if the server is not listening
|
||||
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
|
||||
e.NativeErrorCode != (int)SocketError.TimedOut)
|
||||
throw new NetworkException($"Unexpected error while sending data to server - {e.Message}");
|
||||
}
|
||||
|
||||
if (!waitForResponse)
|
||||
return await Task.FromResult(new string[] { "" });
|
||||
|
||||
var connectionState = new ConnectionState(socketConnection);
|
||||
|
||||
retryReceive:
|
||||
try
|
||||
{
|
||||
socketConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
|
||||
new AsyncCallback(OnReceivedCallback), connectionState);
|
||||
bool success = await Task.FromResult(OnReceived.WaitOne(StaticHelpers.SocketTimeout));
|
||||
|
||||
if (!success)
|
||||
{
|
||||
|
||||
FailedReceives++;
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"{FailedReceives} failed receives from {socketConnection.RemoteEndPoint.ToString()}");
|
||||
#endif
|
||||
if (FailedReceives < 4)
|
||||
goto retrySend;
|
||||
else if (FailedReceives == 4)
|
||||
{
|
||||
// Log.WriteError($"Failed to receive data from {socketConnection.RemoteEndPoint} after {FailedReceives} tries");
|
||||
}
|
||||
|
||||
if (FailedReceives >= 4)
|
||||
{
|
||||
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} {socketConnection.RemoteEndPoint.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (FailedReceives >= 4)
|
||||
{
|
||||
Log.WriteVerbose($"Resumed receive RCon connection from {socketConnection.RemoteEndPoint.ToString()}");
|
||||
FailedReceives = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (SocketException e)
|
||||
{
|
||||
// this result is normal if the server is not listening
|
||||
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
|
||||
e.NativeErrorCode != (int)SocketError.TimedOut)
|
||||
throw new NetworkException($"Unexpected error while receiving data from server - {e.Message}");
|
||||
else if (FailedReceives < 4)
|
||||
{
|
||||
goto retryReceive;
|
||||
}
|
||||
|
||||
else if (FailedReceives == 4)
|
||||
{
|
||||
// Log.WriteError($"Failed to receive data from {socketConnection.RemoteEndPoint} after {FailedReceives} tries");
|
||||
}
|
||||
|
||||
if (FailedReceives >= 4)
|
||||
{
|
||||
throw new NetworkException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
string queryResponse = response;
|
||||
|
||||
if (queryResponse.Contains("Invalid password"))
|
||||
throw new NetworkException("RCON password is invalid");
|
||||
if (queryResponse.ToString().Contains("rcon_password"))
|
||||
throw new NetworkException("RCON password has not been set");
|
||||
|
||||
string[] splitResponse = queryResponse.Split(new char[]
|
||||
{
|
||||
'\n'
|
||||
}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim()).ToArray();
|
||||
return splitResponse;
|
||||
response = await SendPayloadAsync(payload);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
if(connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
||||
{
|
||||
connectionState.ConnectionAttempts++;
|
||||
Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts++}/{StaticHelpers.AllowedConnectionFails})");
|
||||
await Task.Delay(StaticHelpers.SocketTimeout.Milliseconds);
|
||||
goto retrySend;
|
||||
}
|
||||
ActiveQueries.TryRemove(this.Endpoint, out _);
|
||||
Log.WriteDebug(ex.GetExceptionInfo());
|
||||
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
|
||||
}
|
||||
|
||||
ActiveQueries.TryRemove(this.Endpoint, out _);
|
||||
|
||||
string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n';
|
||||
|
||||
if (responseString.Contains("Invalid password"))
|
||||
{
|
||||
// todo: localize this
|
||||
throw new NetworkException("RCON password is invalid");
|
||||
}
|
||||
|
||||
if (responseString.ToString().Contains("rcon_password"))
|
||||
{
|
||||
throw new NetworkException("RCON password has not been set");
|
||||
}
|
||||
|
||||
string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim()).ToArray();
|
||||
return splitResponse;
|
||||
}
|
||||
|
||||
private async Task<byte[]> SendPayloadAsync(byte[] payload)
|
||||
{
|
||||
var rconSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
||||
{
|
||||
DontFragment = true,
|
||||
Ttl = 42,
|
||||
ExclusiveAddressUse = true
|
||||
};
|
||||
|
||||
var connectionState = ActiveQueries[this.Endpoint];
|
||||
|
||||
var outgoingDataArgs = new SocketAsyncEventArgs()
|
||||
{
|
||||
RemoteEndPoint = this.Endpoint
|
||||
};
|
||||
outgoingDataArgs.SetBuffer(payload);
|
||||
outgoingDataArgs.Completed += OnDataSent;
|
||||
|
||||
// send the data to the server
|
||||
bool sendDataPending = rconSocket.SendToAsync(outgoingDataArgs);
|
||||
|
||||
var incomingDataArgs = new SocketAsyncEventArgs()
|
||||
{
|
||||
RemoteEndPoint = this.Endpoint
|
||||
};
|
||||
incomingDataArgs.SetBuffer(connectionState.ReceiveBuffer);
|
||||
incomingDataArgs.Completed += OnDataReceived;
|
||||
|
||||
// get our response back
|
||||
rconSocket.ReceiveFromAsync(incomingDataArgs);
|
||||
|
||||
if (!await connectionState.OnComplete.WaitAsync(StaticHelpers.SocketTimeout.Milliseconds))
|
||||
{
|
||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
||||
}
|
||||
|
||||
byte[] response = connectionState.ReceiveBuffer;
|
||||
return response;
|
||||
}
|
||||
|
||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
||||
#endif
|
||||
ActiveQueries[this.Endpoint].OnComplete.Release(1);
|
||||
}
|
||||
|
||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Sent {e.Buffer.Length} bytes to {e.ConnectSocket.RemoteEndPoint.ToString()}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,10 +39,11 @@ namespace SharedLibraryCore.RCon
|
||||
/// <summary>
|
||||
/// timeout in seconds to wait for a socket send or receive before giving up
|
||||
/// </summary>
|
||||
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10);
|
||||
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 0, 0, 150);
|
||||
/// <summary>
|
||||
/// interval in milliseconds to wait before sending the next RCon request
|
||||
/// </summary>
|
||||
public static readonly int FloodProtectionInterval = 350;
|
||||
public static readonly int AllowedConnectionFails = 3;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user