Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        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);
        }
Exemplo n.º 3
0
        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;
            }
        }
Exemplo n.º 4
0
        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);
        }