188 lines
6.3 KiB
C#
188 lines
6.3 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Integrations.Source.Extensions;
|
|
using Integrations.Source.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using RconSharp;
|
|
using Serilog.Context;
|
|
using SharedLibraryCore;
|
|
using SharedLibraryCore.Exceptions;
|
|
using SharedLibraryCore.Interfaces;
|
|
using SharedLibraryCore.RCon;
|
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
|
|
|
namespace Integrations.Source
|
|
{
|
|
public class SourceRConConnection : IRConConnection
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly string _password;
|
|
private readonly IPEndPoint _ipEndPoint;
|
|
private readonly IRConClientFactory _rconClientFactory;
|
|
private readonly SemaphoreSlim _activeQuery;
|
|
|
|
private static readonly TimeSpan FloodDelay = TimeSpan.FromMilliseconds(250);
|
|
private static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(30);
|
|
|
|
private DateTime _lastQuery = DateTime.Now;
|
|
private RconClient _rconClient;
|
|
private bool _authenticated;
|
|
private bool _needNewSocket = true;
|
|
|
|
public SourceRConConnection(ILogger<SourceRConConnection> logger, IRConClientFactory rconClientFactory,
|
|
IPEndPoint ipEndPoint, string password)
|
|
{
|
|
_rconClientFactory = rconClientFactory;
|
|
_password = password;
|
|
_logger = logger;
|
|
_ipEndPoint = ipEndPoint;
|
|
_activeQuery = new SemaphoreSlim(1, 1);
|
|
}
|
|
|
|
~SourceRConConnection()
|
|
{
|
|
_activeQuery.Dispose();
|
|
}
|
|
|
|
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default)
|
|
{
|
|
try
|
|
{
|
|
await _activeQuery.WaitAsync(token);
|
|
await WaitForAvailable(token);
|
|
|
|
if (_needNewSocket)
|
|
{
|
|
try
|
|
{
|
|
_rconClient?.Disconnect();
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
await Task.Delay(ConnectionTimeout, token);
|
|
_rconClient = _rconClientFactory.CreateClient(_ipEndPoint);
|
|
_authenticated = false;
|
|
_needNewSocket = false;
|
|
}
|
|
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogDebug("Connecting to RCon socket");
|
|
}
|
|
|
|
await TryConnectAndAuthenticate().WithTimeout(ConnectionTimeout);
|
|
|
|
var multiPacket = false;
|
|
|
|
if (type == StaticHelpers.QueryType.COMMAND_STATUS)
|
|
{
|
|
parameters = "status";
|
|
multiPacket = true;
|
|
}
|
|
|
|
parameters = parameters.ReplaceUnfriendlyCharacters();
|
|
parameters = parameters.StripColors();
|
|
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogDebug("Sending query {Type} with parameters \"{Parameters}\"", type, parameters);
|
|
}
|
|
|
|
var response = await _rconClient.ExecuteCommandAsync(parameters, multiPacket)
|
|
.WithTimeout(ConnectionTimeout);
|
|
|
|
using (LogContext.PushProperty("Server", $"{_ipEndPoint}"))
|
|
{
|
|
_logger.LogDebug("Received RCon response {Response}", response);
|
|
}
|
|
|
|
var split = response.TrimEnd('\n').Split('\n');
|
|
return split.Take(split.Length - 1).ToArray();
|
|
}
|
|
|
|
catch (TaskCanceledException)
|
|
{
|
|
_needNewSocket = true;
|
|
throw new NetworkException("Timeout while attempting to communicate with server");
|
|
}
|
|
|
|
catch (SocketException ex)
|
|
{
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogError(ex, "Socket exception encountered while attempting to communicate with server");
|
|
}
|
|
|
|
_needNewSocket = true;
|
|
|
|
throw new NetworkException("Socket exception encountered while attempting to communicate with server");
|
|
}
|
|
|
|
catch (Exception ex) when (ex.GetType() != typeof(NetworkException) &&
|
|
ex.GetType() != typeof(ServerException))
|
|
{
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogError(ex, "Could not execute RCon query {Parameters}", parameters);
|
|
}
|
|
|
|
throw new NetworkException("Unable to communicate with server");
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (_activeQuery.CurrentCount == 0)
|
|
{
|
|
_activeQuery.Release();
|
|
}
|
|
|
|
_lastQuery = DateTime.Now;
|
|
}
|
|
}
|
|
|
|
private async Task WaitForAvailable(CancellationToken token)
|
|
{
|
|
var diff = DateTime.Now - _lastQuery;
|
|
if (diff < FloodDelay)
|
|
{
|
|
await Task.Delay(FloodDelay - diff, token);
|
|
}
|
|
}
|
|
|
|
private async Task TryConnectAndAuthenticate()
|
|
{
|
|
if (!_authenticated)
|
|
{
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogDebug("Authenticating to RCon socket");
|
|
}
|
|
|
|
await _rconClient.ConnectAsync().WithTimeout(ConnectionTimeout);
|
|
_authenticated = await _rconClient.AuthenticateAsync(_password).WithTimeout(ConnectionTimeout);
|
|
|
|
if (!_authenticated)
|
|
{
|
|
using (LogContext.PushProperty("Server", _ipEndPoint.ToString()))
|
|
{
|
|
_logger.LogError("Could not login to server");
|
|
}
|
|
|
|
throw new ServerException("Could not authenticate to server with provided password");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetConfiguration(IRConParser config)
|
|
{
|
|
}
|
|
}
|
|
}
|