public async Task <string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") { if (!ActiveQueries.ContainsKey(this.Endpoint)) { ActiveQueries.TryAdd(this.Endpoint, new ConnectionState()); } var connectionState = ActiveQueries[this.Endpoint]; #if DEBUG == true Log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]"); #endif // enter the semaphore so only one query is sent at a time per server. await connectionState.OnComplete.WaitAsync(); var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds; if (timeSinceLastQuery < StaticHelpers.FloodProtectionInterval) { await Task.Delay(StaticHelpers.FloodProtectionInterval - (int)timeSinceLastQuery); } connectionState.LastQuery = DateTime.Now; #if DEBUG == true Log.WriteDebug($"Semaphore has been released [{this.Endpoint}]"); Log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]"); #endif byte[] payload = null; bool waitForResponse = Config.WaitForResponse; string convertEncoding(string text) { byte[] convertedBytes = Utilities.EncodingType.GetBytes(text); return(defaultEncoding.GetString(convertedBytes)); } string convertedRConPassword = convertEncoding(RConPassword); string convertedParameters = convertEncoding(parameters); switch (type) { case StaticHelpers.QueryType.GET_DVAR: waitForResponse |= true; payload = string.Format(Config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.SET_DVAR: payload = string.Format(Config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND: payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_STATUS: waitForResponse |= true; payload = (Config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_INFO: waitForResponse |= true; payload = (Config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND_STATUS: waitForResponse |= true; payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray(); break; } byte[] response = null; retrySend: using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) { DontFragment = true, Ttl = 100, ExclusiveAddressUse = true, }) { connectionState.SendEventArgs.UserToken = socket; connectionState.OnSentData.Reset(); connectionState.OnReceivedData.Reset(); connectionState.ConnectionAttempts++; #if DEBUG == true Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); #endif try { response = await SendPayloadAsync(payload, waitForResponse); if (response.Length == 0 && waitForResponse) { throw new NetworkException("Expected response but got 0 bytes back"); } connectionState.OnComplete.Release(1); connectionState.ConnectionAttempts = 0; } catch { if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) { await Task.Delay(StaticHelpers.FloodProtectionInterval); goto retrySend; } connectionState.OnComplete.Release(1); throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint)); } } string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n'; if (responseString.Contains("Invalid password")) { throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]); } if (responseString.ToString().Contains("rcon_password")) { throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]); } string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(line => line.Trim()) .ToArray(); return(splitResponse); }
public async Task <string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") { if (!ActiveQueries.ContainsKey(this.Endpoint)) { ActiveQueries.TryAdd(this.Endpoint, new ConnectionState()); } var connectionState = ActiveQueries[this.Endpoint]; #if DEBUG == true _log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]"); #endif // enter the semaphore so only one query is sent at a time per server. await connectionState.OnComplete.WaitAsync(); var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds; if (timeSinceLastQuery < StaticHelpers.FloodProtectionInterval) { await Task.Delay(StaticHelpers.FloodProtectionInterval - (int)timeSinceLastQuery); } connectionState.LastQuery = DateTime.Now; #if DEBUG == true _log.WriteDebug($"Semaphore has been released [{this.Endpoint}]"); _log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]"); #endif byte[] payload = null; bool waitForResponse = config.WaitForResponse; string convertEncoding(string text) { byte[] convertedBytes = Utilities.EncodingType.GetBytes(text); return(_gameEncoding.GetString(convertedBytes)); } try { string convertedRConPassword = convertEncoding(RConPassword); string convertedParameters = convertEncoding(parameters); switch (type) { case StaticHelpers.QueryType.GET_DVAR: waitForResponse |= true; payload = string.Format(config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.SET_DVAR: payload = string.Format(config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND: payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_STATUS: waitForResponse |= true; payload = (config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_INFO: waitForResponse |= true; payload = (config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND_STATUS: waitForResponse |= true; payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray(); break; } } // this happens when someone tries to send something that can't be converted into a 7 bit character set // e.g: emoji -> windows-1252 catch (OverflowException) { connectionState.OnComplete.Release(1); throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}"); } byte[][] response = null; retrySend: using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) { DontFragment = true, Ttl = 100, ExclusiveAddressUse = true, }) { connectionState.SendEventArgs.UserToken = socket; connectionState.OnSentData.Reset(); connectionState.OnReceivedData.Reset(); connectionState.ConnectionAttempts++; connectionState.BytesReadPerSegment.Clear(); #if DEBUG == true _log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); #endif try { response = await SendPayloadAsync(payload, waitForResponse); if ((response.Length == 0 || response[0].Length == 0) && waitForResponse) { throw new NetworkException("Expected response but got 0 bytes back"); } connectionState.ConnectionAttempts = 0; } catch { if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) { await Task.Delay(StaticHelpers.FloodProtectionInterval); goto retrySend; } throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint)); } finally { if (connectionState.OnComplete.CurrentCount == 0) { connectionState.OnComplete.Release(1); } } } if (response.Length == 0) { _log.WriteWarning($"Received empty response for request [{type.ToString()}, {parameters}, {Endpoint.ToString()}]"); return(new string[0]); } string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ? ReassembleSegmentedStatus(response) : _gameEncoding.GetString(response[0]) + '\n'; // note: not all games respond if the pasword is wrong or not set if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword")) { throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]); } if (responseString.Contains("rcon_password")) { throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]); } if (responseString.Contains(config.ServerNotRunningResponse)) { throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString())); } string responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value; string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch); if (headerSplit.Length != 2) { throw new NetworkException("Unexpected response header from server"); } string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); return(splitResponse); }
public async Task <string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") { try { await _activeQuery.WaitAsync(); await WaitForAvailable(); if (_needNewSocket) { try { _rconClient?.Disconnect(); } catch { // ignored } await Task.Delay(ConnectionTimeout); _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; } }
public async Task <string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") { if (!ActiveQueries.ContainsKey(this.Endpoint)) { ActiveQueries.TryAdd(this.Endpoint, new ConnectionState()); } var connectionState = ActiveQueries[this.Endpoint]; _log.LogDebug("Waiting for semaphore to be released [{endpoint}]", Endpoint); // enter the semaphore so only one query is sent at a time per server. await connectionState.OnComplete.WaitAsync(); var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds; if (timeSinceLastQuery < StaticHelpers.FloodProtectionInterval) { await Task.Delay(StaticHelpers.FloodProtectionInterval - (int)timeSinceLastQuery); } connectionState.LastQuery = DateTime.Now; _log.LogDebug("Semaphore has been released [{endpoint}]", Endpoint); _log.LogDebug("Query {@queryInfo}", new { endpoint = Endpoint.ToString(), type, parameters }); byte[] payload = null; bool waitForResponse = config.WaitForResponse; string convertEncoding(string text) { byte[] convertedBytes = Utilities.EncodingType.GetBytes(text); return(_gameEncoding.GetString(convertedBytes)); } try { string convertedRConPassword = convertEncoding(RConPassword); string convertedParameters = convertEncoding(parameters); switch (type) { case StaticHelpers.QueryType.GET_DVAR: waitForResponse |= true; payload = string.Format(config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.SET_DVAR: payload = string.Format(config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND: payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_STATUS: waitForResponse |= true; payload = (config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_INFO: waitForResponse |= true; payload = (config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.COMMAND_STATUS: waitForResponse |= true; payload = string.Format(config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray(); break; } } // this happens when someone tries to send something that can't be converted into a 7 bit character set // e.g: emoji -> windows-1252 catch (OverflowException ex) { connectionState.OnComplete.Release(1); using (LogContext.PushProperty("Server", Endpoint.ToString())) { _log.LogError(ex, "Could not convert RCon data payload to desired encoding {encoding} {params}", _gameEncoding.EncodingName, parameters); } throw new RConException($"Invalid character encountered when converting encodings"); } byte[][] response = null; retrySend: if (connectionState.ConnectionAttempts > 1) { using (LogContext.PushProperty("Server", Endpoint.ToString())) { _log.LogInformation( "Retrying RCon message ({connectionAttempts}/{allowedConnectionFailures} attempts) with parameters {payload}", connectionState.ConnectionAttempts, _retryAttempts, parameters); } } using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) { DontFragment = false, Ttl = 100, ExclusiveAddressUse = true, }) { connectionState.SendEventArgs.UserToken = socket; connectionState.OnSentData.Reset(); connectionState.OnReceivedData.Reset(); connectionState.ConnectionAttempts++; connectionState.BytesReadPerSegment.Clear(); bool exceptionCaught = false; _log.LogDebug("Sending {payloadLength} bytes to [{endpoint}] ({connectionAttempts}/{allowedConnectionFailures})", payload.Length, Endpoint, connectionState.ConnectionAttempts, _retryAttempts); try { response = await SendPayloadAsync(payload, waitForResponse, parser.OverrideTimeoutForCommand(parameters)); if ((response.Length == 0 || response[0].Length == 0) && waitForResponse) { throw new RConException("Expected response but got 0 bytes back"); } connectionState.ConnectionAttempts = 0; } catch { // we want to retry with a delay if (connectionState.ConnectionAttempts < _retryAttempts) { exceptionCaught = true; await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts)); goto retrySend; } using (LogContext.PushProperty("Server", Endpoint.ToString())) { _log.LogWarning( "Made {connectionAttempts} attempts to send RCon data to server, but received no response", connectionState.ConnectionAttempts); } connectionState.ConnectionAttempts = 0; throw new NetworkException("Reached maximum retry attempts to send RCon data to server"); } finally { // we don't want to release if we're going to retry the query if (connectionState.OnComplete.CurrentCount == 0 && !exceptionCaught) { connectionState.OnComplete.Release(1); } } } if (response.Length == 0) { _log.LogDebug("Received empty response for RCon request {@query}", new { endpoint = Endpoint.ToString(), type, parameters }); return(new string[0]); } string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ? ReassembleSegmentedStatus(response) : RecombineMessages(response); // note: not all games respond if the pasword is wrong or not set if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword")) { throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]); } if (responseString.Contains("rcon_password")) { throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]); } if (responseString.Contains(config.ServerNotRunningResponse)) { throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString())); } string responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value; string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch); if (headerSplit.Length != 2) { using (LogContext.PushProperty("Server", Endpoint.ToString())) { _log.LogWarning("Invalid response header from server. Expected {expected}, but got {response}", config.CommandPrefixes.RConResponse, headerSplit.FirstOrDefault()); } throw new RConException("Unexpected response header from server"); } string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); return(splitResponse); }