Initial large refactor of the RCON implementation

* Decoding and encoding is done into a single buffer, from raw buffers to avoid extraneous copies.
* Added base class holding all core logic for encoding, decoding, receiving and processing of the RCON protocol. This code was initially identical between all implementations of RCON, deduplicating this avoids bugs.
* Added more sophisticated error handling, stop right away when decoding for example fails.
* Added ability to have more than one active authenticated net console on the server. Controlled by cvar 'sv_rcon_maxconnections' (default 1).
* Max packet size for accepted, but not authenticated sockets is now controled by cvar 'sv_rcon_maxpacketsize' (default 1024).
This commit is contained in:
Kawe Mazidjatari 2023-04-19 01:35:31 +02:00
parent 437a54486d
commit 179bd31ee6
24 changed files with 853 additions and 790 deletions

View File

@ -10,6 +10,7 @@
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
#include "engine/client/cl_rcon.h"
#include "engine/shared/shared_rcon.h"
#include "engine/net.h"
#include "squirrel/sqvm.h"
#include "common/igameserverdata.h"
@ -20,7 +21,6 @@
//-----------------------------------------------------------------------------
CRConClient::CRConClient()
: m_bInitialized(false)
, m_bConnEstablished(false)
{
}
@ -44,10 +44,7 @@ void CRConClient::Init(void)
//-----------------------------------------------------------------------------
void CRConClient::Shutdown(void)
{
if (m_bConnEstablished)
{
this->Disconnect();
}
Disconnect();
}
//-----------------------------------------------------------------------------
@ -55,230 +52,89 @@ void CRConClient::Shutdown(void)
//-----------------------------------------------------------------------------
void CRConClient::RunFrame(void)
{
if (m_bInitialized && m_bConnEstablished)
if (IsInitialized() && IsConnected())
{
this->Recv();
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(0);
Recv(pData);
}
}
//-----------------------------------------------------------------------------
// Purpose: connect to address and port stored in 'rcon_address' cvar
// Output : true if connection succeeds, false otherwise
//-----------------------------------------------------------------------------
bool CRConClient::Connect(void)
{
if (strlen(rcon_address->GetString()) > 0)
{
return Connect(rcon_address->GetString());
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: connect to specified address and port
// Input : *szInAdr -
// Output : true if connection succeeds, false otherwise
//-----------------------------------------------------------------------------
bool CRConClient::Connect(const char* szInAdr)
{
string svLocalHost;
if (strcmp(szInAdr, "localhost") == 0)
{
char szHostName[512];
if (!gethostname(szHostName, sizeof(szHostName)))
{
svLocalHost = Format("[%s]:%i", szHostName, hostport->GetInt());
szInAdr = svLocalHost.c_str();
}
}
if (!m_Address.SetFromString(szInAdr, true))
{
Warning(eDLL_T::CLIENT, "Failed to set RCON address: %s\n", szInAdr);
return false;
}
if (m_Socket.ConnectSocket(m_Address, true) == SOCKET_ERROR)
{
Warning(eDLL_T::CLIENT, "Failed to connect to RCON server: (%s)\n", "SOCKET_ERROR");
return false;
}
DevMsg(eDLL_T::CLIENT, "Connected to: %s\n", m_Address.ToString());
m_bConnEstablished = true;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: disconnect from current session
//-----------------------------------------------------------------------------
void CRConClient::Disconnect(void)
void CRConClient::Disconnect(const char* szReason)
{
m_Socket.CloseAcceptedSocket(0);
m_bConnEstablished = false;
}
//-----------------------------------------------------------------------------
// Purpose: send message
// Input : *svMessage -
//-----------------------------------------------------------------------------
void CRConClient::Send(const string& svMessage) const
{
ostringstream ssSendBuf;
const u_long nLen = htonl(static_cast<u_long>(svMessage.size()));
ssSendBuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
ssSendBuf.write(svMessage.data(), svMessage.size());
int nSendResult = ::send(m_Socket.GetAcceptedSocketData(0)->m_hSocket,
ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
if (nSendResult == SOCKET_ERROR)
if (IsConnected())
{
Warning(eDLL_T::CLIENT, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
}
}
//-----------------------------------------------------------------------------
// Purpose: receive message
//-----------------------------------------------------------------------------
void CRConClient::Recv(void)
{
static char szRecvBuf[1024];
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(0);
{//////////////////////////////////////////////
const int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_Socket.IsSocketBlocking())
if (szReason)
{
return;
}
if (nPendingLen <= 0 && m_bConnEstablished) // EOF or error.
{
this->Disconnect();
DevMsg(eDLL_T::CLIENT, "Server closed RCON connection\n");
return;
}
}//////////////////////////////////////////////
u_long nReadLen; // Find out how much we have to read.
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
while (nReadLen > 0)
{
const int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0 && m_bConnEstablished) // Socket was closed.
{
this->Disconnect();
DevMsg(eDLL_T::CLIENT, "Server closed RCON connection\n");
break;
}
if (nRecvLen < 0 && !m_Socket.IsSocketBlocking())
{
Error(eDLL_T::CLIENT, NO_ERROR, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
DevMsg(eDLL_T::CLIENT, "%s", szReason);
}
nReadLen -= nRecvLen; // Process what we've got.
this->ProcessBuffer(szRecvBuf, nRecvLen, pData);
}
}
//-----------------------------------------------------------------------------
// Purpose: parses input response buffer using length-prefix framing
// Input : *pszIn -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CRConClient::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
while (nRecvLen > 0)
{
if (pData->m_nPayloadLen)
{
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
this->ProcessMessage(this->Deserialize(string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
}
else if (pData->m_nPayloadRead < sizeof(u_long)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = ntohl(*reinterpret_cast<u_long*>(&pData->m_RecvBuffer[0]));
pData->m_nPayloadRead = 0;
if (pData->m_nPayloadLen < 0 ||
pData->m_nPayloadLen > pData->m_RecvBuffer.max_size())
{
Error(eDLL_T::CLIENT, NO_ERROR, "RCON Cmd: sync error (%d)\n", pData->m_nPayloadLen);
this->Disconnect(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
m_Socket.CloseAcceptedSocket(0);
}
}
//-----------------------------------------------------------------------------
// Purpose: processes received message
// Input : *sv_response -
// Input : *pMsgBug -
// nMsgLen -
//-----------------------------------------------------------------------------
void CRConClient::ProcessMessage(const sv_rcon::response& sv_response) const
bool CRConClient::ProcessMessage(const char* pMsgBuf, int nMsgLen)
{
switch (sv_response.responsetype())
sv_rcon::response response;
bool bSuccess = Decode(&response, pMsgBuf, nMsgLen);
if (!bSuccess)
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to decode RCON buffer\n");
return false;
}
switch (response.responsetype())
{
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
{
if (!sv_response.responseval().empty())
if (!response.responseval().empty())
{
bool bLocalHost = (g_pNetAdr->ComparePort(m_Address) && g_pNetAdr->CompareAdr(m_Address));
const long i = strtol(sv_response.responseval().c_str(), NULL, NULL);
const long i = strtol(response.responseval().c_str(), NULL, NULL);
const bool bLocalHost = (g_pNetAdr->ComparePort(m_Address) && g_pNetAdr->CompareAdr(m_Address));
const SocketHandle_t hSocket = GetSocket();
if (!i) // sv_rcon_sendlogs is not set.
{
if (!bLocalHost && cl_rcon_request_sendlogs->GetBool())
{
string svLogQuery = this->Serialize("", "1", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
this->Send(svLogQuery);
vector<char> vecMsg;
bool ret = Serialize(vecMsg, "", "1", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
if (ret && !Send(hSocket, vecMsg.data(), int(vecMsg.size())))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
}
}
}
else if (bLocalHost)
{
// Don't send logs to local host, it already gets logged to the same console.
string svLogQuery = this->Serialize("", "0", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
this->Send(svLogQuery);
vector<char> vecMsg;
bool ret = Serialize(vecMsg, "", "0", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
if (ret && !Send(hSocket, vecMsg.data(), int(vecMsg.size())))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
}
}
}
DevMsg(eDLL_T::NETCON, "%s", sv_response.responsemsg().c_str());
DevMsg(eDLL_T::NETCON, "%s", response.responsemsg().c_str());
break;
}
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
{
NetMsg(static_cast<LogType_t>(sv_response.messagetype()),
static_cast<eDLL_T>(sv_response.messageid()),
sv_response.responseval().c_str(), "%s", sv_response.responsemsg().c_str());
NetMsg(static_cast<LogType_t>(response.messagetype()),
static_cast<eDLL_T>(response.messageid()),
response.responseval().c_str(), "%s", response.responsemsg().c_str());
break;
}
default:
@ -286,6 +142,8 @@ void CRConClient::ProcessMessage(const sv_rcon::response& sv_response) const
break;
}
}
return true;
}
//-----------------------------------------------------------------------------
@ -295,34 +153,24 @@ void CRConClient::ProcessMessage(const sv_rcon::response& sv_response) const
// request_t -
// Output : serialized results as string
//-----------------------------------------------------------------------------
string CRConClient::Serialize(const string& svReqBuf, const string& svReqVal, const cl_rcon::request_t request_t) const
bool CRConClient::Serialize(vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType) const
{
cl_rcon::request cl_request;
cl_request.set_messageid(-1);
cl_request.set_requesttype(request_t);
cl_request.set_requestmsg(svReqBuf);
cl_request.set_requestval(svReqVal);
return cl_request.SerializeAsString();
return CL_NetConSerialize(this, vecBuf, szReqBuf, szReqVal, requestType);
}
//-----------------------------------------------------------------------------
// Purpose: de-serializes input
// Input : *svBuf -
// Output : de-serialized object
// Purpose: retrieves the remote socket
// Output : SOCKET_ERROR (-1) on failure
//-----------------------------------------------------------------------------
sv_rcon::response CRConClient::Deserialize(const string& svBuf) const
SocketHandle_t CRConClient::GetSocket(void)
{
sv_rcon::response sv_response;
sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return sv_response;
return SH_GetNetConSocketHandle(this, 0);
}
//-----------------------------------------------------------------------------
// Purpose: checks if client rcon is initialized
// Output : true if initialized, false otherwise
//-----------------------------------------------------------------------------
bool CRConClient::IsInitialized(void) const
{
@ -330,13 +178,13 @@ bool CRConClient::IsInitialized(void) const
}
//-----------------------------------------------------------------------------
// Purpose: checks if client rcon is connected
// Output : true if connected, false otherwise
// Purpose: returns whether the rcon client is connected
//-----------------------------------------------------------------------------
bool CRConClient::IsConnected(void) const
bool CRConClient::IsConnected(void)
{
return m_bConnEstablished;
return (GetSocket() != SOCKET_ERROR);
}
///////////////////////////////////////////////////////////////////////////////
CRConClient g_RCONClient;
CRConClient* RCONClient() // Singleton RCON Client.

View File

@ -3,8 +3,9 @@
#include "tier2/socketcreator.h"
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
#include "engine/shared/base_rcon.h"
class CRConClient
class CRConClient : public CNetConBase
{
public:
CRConClient(void);
@ -15,28 +16,20 @@ public:
void RunFrame(void);
bool Connect(void);
bool Connect(const char* szInAdr);
void Disconnect(void);
virtual void Disconnect(const char* szReason = nullptr) override;
void Send(const string& svMessage) const;
void Recv(void);
virtual bool ProcessMessage(const char* pMsgBuf, int nMsgLen) override;
void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const sv_rcon::response& sv_response) const;
string Serialize(const string& svReqBuf, const string& svReqVal, const cl_rcon::request_t request_t) const;
sv_rcon::response Deserialize(const string& svBuf) const;
bool Serialize(vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType) const;
bool IsInitialized(void) const;
bool IsConnected(void) const;
bool IsConnected(void);
SocketHandle_t GetSocket(void);
private:
bool m_bInitialized = false;
bool m_bConnEstablished = false;
netadr_t m_Address;
CSocketCreator m_Socket;
bool m_bInitialized;
};
CRConClient* RCONClient();

View File

@ -16,13 +16,21 @@
#include "mathlib/sha256.h"
#include "common/igameserverdata.h"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static const char s_NoAuthMessage[] = "This server is password protected for console access; authenticate with 'PASS <password>' command.\n";
static const char s_WrongPwMessage[] = "Admin password incorrect.\n";
static const char s_AuthMessage[] = "Authentication successful.\n";
static const char s_BannedMessage[] = "Go away.\n";
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRConServer::CRConServer(void)
: m_bInitialized(false)
, m_nConnIndex(0)
: m_nConnIndex(0)
, m_nAuthConnections(0)
, m_bInitialized(false)
{
}
@ -40,7 +48,7 @@ void CRConServer::Init(void)
{
if (!m_bInitialized)
{
if (!this->SetPassword(rcon_password->GetString()))
if (!SetPassword(rcon_password->GetString()))
{
return;
}
@ -85,7 +93,7 @@ void CRConServer::Think(void)
const CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(m_nConnIndex);
if (!pData->m_bAuthorized)
{
this->CloseConnection();
Disconnect();
}
}
}
@ -120,7 +128,7 @@ bool CRConServer::SetPassword(const char* pszPassword)
RCON_MIN_PASSWORD_LEN);
}
this->Shutdown();
Shutdown();
return false;
}
@ -149,22 +157,42 @@ void CRConServer::RunFrame(void)
if (m_bInitialized)
{
m_Socket.RunFrame();
this->Think();
this->Recv();
Think();
const int nCount = m_Socket.GetAcceptedSocketCount();
for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--)
{
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(m_nConnIndex);
if (CheckForBan(pData))
{
SendEncode(pData->m_hSocket, s_BannedMessage, "",
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, int(eDLL_T::NETCON));
Disconnect();
continue;
}
Recv(pData, sv_rcon_maxpacketsize->GetInt());
}
}
}
//-----------------------------------------------------------------------------
// Purpose: send message to all connected sockets
// Input : *svMessage -
// Input : *pMsgBuf -
// nMsgLen -
// Output: true on success, false otherwise
//-----------------------------------------------------------------------------
void CRConServer::Send(const std::string& svMessage) const
bool CRConServer::SendToAll(const char* pMsgBuf, int nMsgLen) const
{
std::ostringstream ssSendBuf;
const u_long nLen = htonl(static_cast<u_long>(svMessage.size()));
std::ostringstream sendbuf;
const u_long nLen = htonl(u_long(nMsgLen));
ssSendBuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
ssSendBuf.write(svMessage.data(), svMessage.size());
bool bSuccess = true;
sendbuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
sendbuf.write(pMsgBuf, nMsgLen);
const int nCount = m_Socket.GetAcceptedSocketCount();
for (int i = nCount - 1; i >= 0; i--)
@ -173,115 +201,75 @@ void CRConServer::Send(const std::string& svMessage) const
if (pData->m_bAuthorized)
{
::send(pData->m_hSocket, ssSendBuf.str().data(),
static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
int ret = ::send(pData->m_hSocket, sendbuf.str().data(),
int(sendbuf.str().size()), MSG_NOSIGNAL);
if (ret == SOCKET_ERROR)
{
if (!bSuccess)
{
bSuccess = false;
}
}
}
}
return bSuccess;
}
//-----------------------------------------------------------------------------
// Purpose: send message to specific connected socket
// Input : hSocket -
// *svMessage -
//-----------------------------------------------------------------------------
void CRConServer::Send(const SocketHandle_t hSocket, const std::string& svMessage) const
{
std::ostringstream ssSendBuf;
const u_long nLen = htonl(static_cast<u_long>(svMessage.size()));
ssSendBuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
ssSendBuf.write(svMessage.data(), svMessage.size());
::send(hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
}
//-----------------------------------------------------------------------------
// Purpose: send serialized message to all connected sockets
// Input : *responseMsg -
// *responseVal -
// Purpose: encode and send message to all connected sockets
// Input : *pResponseMsg -
// *pResponseVal -
// responseType -
// nMessageId -
// nMessageType -
// Output: true on success, false otherwise
//-----------------------------------------------------------------------------
void CRConServer::Send(const std::string& responseMsg, const std::string& responseVal,
const sv_rcon::response_t responseType, const int nMessageId, const int nMessageType)
bool CRConServer::SendEncode(const char* pResponseMsg, const char* pResponseVal,
const sv_rcon::response_t responseType, const int nMessageId, const int nMessageType) const
{
if (this->ShouldSend(responseType))
vector<char> vecMsg;
if (!Serialize(vecMsg, pResponseMsg, pResponseVal,
responseType, nMessageId, nMessageType))
{
this->Send(this->Serialize(responseMsg, responseVal, responseType, nMessageId, nMessageType));
return false;
}
if (!SendToAll(vecMsg.data(), int(vecMsg.size())))
{
Error(eDLL_T::SERVER, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: send serialized message to specific connected socket
// Purpose: encode and send message to specific socket
// Input : hSocket -
// *responseMsg -
// *responseVal -
// *pResponseMsg -
// *pResponseVal -
// responseType -
// nMessageId -
// nMessageType -
// Output: true on success, false otherwise
//-----------------------------------------------------------------------------
void CRConServer::Send(const SocketHandle_t hSocket, const std::string& responseMsg,
const std::string& responseVal, const sv_rcon::response_t responseType, const int nMessageId, const int nMessageType)
bool CRConServer::SendEncode(const SocketHandle_t hSocket, const char* pResponseMsg, const char* pResponseVal,
const sv_rcon::response_t responseType, const int nMessageId, const int nMessageType) const
{
if (this->ShouldSend(responseType))
vector<char> vecMsg;
if (!Serialize(vecMsg, pResponseMsg, pResponseVal,
responseType, nMessageId, nMessageType))
{
this->Send(hSocket, this->Serialize(responseMsg, responseVal, responseType, nMessageId, nMessageType));
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose: receive message
//-----------------------------------------------------------------------------
void CRConServer::Recv(void)
{
const int nCount = m_Socket.GetAcceptedSocketCount();
static char szRecvBuf[1024];
for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--)
if (!Send(hSocket, vecMsg.data(), int(vecMsg.size())))
{
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(m_nConnIndex);
{//////////////////////////////////////////////
if (this->CheckForBan(pData))
{
this->Send(pData->m_hSocket, this->Serialize(s_pszBannedMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON)));
this->CloseConnection();
continue;
}
const int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_Socket.IsSocketBlocking())
{
continue;
}
if (nPendingLen <= 0) // EOF or error.
{
this->CloseConnection();
continue;
}
}//////////////////////////////////////////////
u_long nReadLen; // Find out how much we have to read.
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
while (nReadLen > 0)
{
const int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0) // Socket was closed.
{
this->CloseConnection();
break;
}
if (nRecvLen < 0 && !m_Socket.IsSocketBlocking())
{
Error(eDLL_T::SERVER, NO_ERROR, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
}
nReadLen -= nRecvLen; // Process what we've got.
this->ProcessBuffer(szRecvBuf, nRecvLen, pData);
}
Error(eDLL_T::SERVER, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
return false;
}
return true;
}
//-----------------------------------------------------------------------------
@ -293,43 +281,27 @@ void CRConServer::Recv(void)
// nMessageType -
// Output : serialized results as string
//-----------------------------------------------------------------------------
std::string CRConServer::Serialize(const std::string& responseMsg, const std::string& responseVal,
bool CRConServer::Serialize(vector<char>& vecBuf, const char* pResponseMsg, const char* pResponseVal,
const sv_rcon::response_t responseType, const int nMessageId, const int nMessageType) const
{
sv_rcon::response sv_response;
sv_rcon::response response;
sv_response.set_messageid(nMessageId);
sv_response.set_messagetype(nMessageType);
sv_response.set_responsetype(responseType);
response.set_messageid(nMessageId);
response.set_messagetype(nMessageType);
response.set_responsetype(responseType);
response.set_responsemsg(pResponseMsg);
response.set_responseval(pResponseVal);
switch (responseType)
const size_t msgLen = response.ByteSizeLong();
vecBuf.resize(msgLen);
if (!Encode(&response, &vecBuf[0], msgLen))
{
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
{
sv_response.set_responsemsg(responseMsg);
sv_response.set_responseval(responseVal);
break;
}
default:
{
break;
}
Error(eDLL_T::SERVER, NO_ERROR, "Failed to encode RCON buffer\n");
return false;
}
return sv_response.SerializeAsString();
}
//-----------------------------------------------------------------------------
// Purpose: de-serializes input
// Input : *svBuf -
// Output : de-serialized object
//-----------------------------------------------------------------------------
cl_rcon::request CRConServer::Deserialize(const std::string& svBuf) const
{
cl_rcon::request cl_request;
cl_request.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return cl_request;
return true;
}
//-----------------------------------------------------------------------------
@ -345,14 +317,17 @@ void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNet
}
else // Authorize.
{
if (this->Comparator(cl_request.requestmsg()))
if (Comparator(cl_request.requestmsg()))
{
pData->m_bAuthorized = true;
m_Socket.CloseListenSocket();
if (++m_nAuthConnections >= sv_rcon_maxconnections->GetInt())
{
m_Socket.CloseListenSocket();
CloseNonAuthConnection();
}
this->CloseNonAuthConnection();
this->Send(pData->m_hSocket, this->Serialize(s_pszAuthMessage, sv_rcon_sendlogs->GetString(),
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON)));
SendEncode(pData->m_hSocket, s_AuthMessage, sv_rcon_sendlogs->GetString(),
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON));
}
else // Bad password.
{
@ -362,8 +337,8 @@ void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNet
DevMsg(eDLL_T::SERVER, "Bad RCON password attempt from '%s'\n", netAdr.ToString());
}
this->Send(pData->m_hSocket, this->Serialize(s_pszWrongPwMessage, "",
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON)));
SendEncode(pData->m_hSocket, s_WrongPwMessage, "",
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON));
pData->m_bAuthorized = false;
pData->m_bValidated = false;
@ -394,101 +369,46 @@ bool CRConServer::Comparator(std::string svPassword) const
return false;
}
//-----------------------------------------------------------------------------
// Purpose: parses input response buffer using length-prefix framing
// Input : *pRecvBuf -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CRConServer::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
while (nRecvLen > 0)
{
if (pData->m_nPayloadLen)
{
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
this->ProcessMessage(this->Deserialize(std::string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
}
else if (pData->m_nPayloadRead+1 <= sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = int(ntohl(*reinterpret_cast<u_long*>(&pData->m_RecvBuffer[0])));
pData->m_nPayloadRead = 0;
if (!pData->m_bAuthorized)
{
if (pData->m_nPayloadLen > MAX_NETCONSOLE_INPUT_LEN)
{
this->CloseConnection(); // Sending large messages while not authenticated.
break;
}
}
if (pData->m_nPayloadLen < 0 ||
pData->m_nPayloadLen > pData->m_RecvBuffer.max_size())
{
Error(eDLL_T::SERVER, NO_ERROR, "RCON Cmd: sync error (%d)\n", pData->m_nPayloadLen);
this->CloseConnection(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: processes received message
// Input : *cl_request -
// Input : *pMsgBuf -
// nMsgLen -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
bool CRConServer::ProcessMessage(const char* pMsgBuf, int nMsgLen)
{
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(m_nConnIndex);
if (!pData->m_bAuthorized
&& cl_request.requesttype() != cl_rcon::request_t::SERVERDATA_REQUEST_AUTH)
cl_rcon::request request;
if (!Decode(&request, pMsgBuf, nMsgLen))
{
Error(eDLL_T::SERVER, NO_ERROR, "Failed to decode RCON buffer\n");
return false;
}
if (!pData->m_bAuthorized &&
request.requesttype() != cl_rcon::request_t::SERVERDATA_REQUEST_AUTH)
{
// Notify net console that authentication is required.
this->Send(pData->m_hSocket, this->Serialize(s_pszNoAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON)));
SendEncode(pData->m_hSocket, s_NoAuthMessage, "",
sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH, static_cast<int>(eDLL_T::NETCON));
pData->m_bValidated = false;
pData->m_nIgnoredMessage++;
return;
return true;
}
switch (cl_request.requesttype())
switch (request.requesttype())
{
case cl_rcon::request_t::SERVERDATA_REQUEST_AUTH:
{
this->Authenticate(cl_request, pData);
Authenticate(request, pData);
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND:
{
if (pData->m_bAuthorized) // Only execute if auth was successful.
{
this->Execute(cl_request, false);
Execute(request, false);
}
break;
}
@ -496,7 +416,7 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
{
if (pData->m_bAuthorized)
{
this->Execute(cl_request, true);
Execute(request, true);
}
break;
}
@ -504,7 +424,7 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
{
if (pData->m_bAuthorized)
{
sv_rcon_sendlogs->SetValue(cl_request.requestval().c_str());
sv_rcon_sendlogs->SetValue(request.requestval().c_str());
}
break;
}
@ -513,6 +433,8 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
break;
}
}
return true;
}
//-----------------------------------------------------------------------------
@ -555,8 +477,8 @@ bool CRConServer::CheckForBan(CConnectedNetConsoleData* pData)
const char* pszWhiteListAddress = sv_rcon_whitelist_address->GetString();
if (!pszWhiteListAddress[0])
{
DevMsg(eDLL_T::SERVER, "Banned list overflowed; please use a whitelist address. RCON shutting down...\n");
this->Shutdown();
Warning(eDLL_T::SERVER, "Banned list overflowed; please use a whitelist address. RCON shutting down...\n");
Shutdown();
return true;
}
@ -586,24 +508,26 @@ bool CRConServer::CheckForBan(CConnectedNetConsoleData* pData)
|| pData->m_nIgnoredMessage >= sv_rcon_maxignores->GetInt())
{
// Don't add white listed address to banned list.
if (szNetAdr == sv_rcon_whitelist_address->GetString())
if (m_WhiteListAddress.CompareAdr(netAdr))
{
pData->m_nFailedAttempts = 0;
pData->m_nIgnoredMessage = 0;
return false;
}
DevMsg(eDLL_T::SERVER, "Banned '%s' for RCON hacking attempts\n", szNetAdr);
Warning(eDLL_T::SERVER, "Banned '%s' for RCON hacking attempts\n", szNetAdr);
m_BannedList.insert(szNetAdr);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: close specific connection
//-----------------------------------------------------------------------------
void CRConServer::CloseConnection(void) // NETMGR
void CRConServer::Disconnect(const char* /*szReason*/) // NETMGR
{
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(m_nConnIndex);
if (pData->m_bAuthorized)
@ -611,7 +535,10 @@ void CRConServer::CloseConnection(void) // NETMGR
// Inform server owner when authenticated connection has been closed.
netadr_t netAdr = m_Socket.GetAcceptedSocketAddress(m_nConnIndex);
DevMsg(eDLL_T::SERVER, "Net console '%s' closed RCON connection\n", netAdr.ToString());
m_nAuthConnections--;
}
m_Socket.CloseAcceptedSocket(m_nConnIndex);
}
@ -634,11 +561,12 @@ void CRConServer::CloseNonAuthConnection(void)
//-----------------------------------------------------------------------------
// Purpose: checks if this message should be send or not
// Input : responseType -
// Output : true if it should send, false otherwise
//-----------------------------------------------------------------------------
bool CRConServer::ShouldSend(const sv_rcon::response_t responseType) const
{
if (!this->IsInitialized() || !m_Socket.GetAcceptedSocketCount())
if (!IsInitialized() || !m_Socket.GetAcceptedSocketCount())
{
// Not initialized or no sockets...
return false;
@ -657,8 +585,7 @@ bool CRConServer::ShouldSend(const sv_rcon::response_t responseType) const
}
//-----------------------------------------------------------------------------
// Purpose: checks if server rcon is initialized
// Output : true if initialized, false otherwise
// Purpose: returns whether the rcon server is initialized
//-----------------------------------------------------------------------------
bool CRConServer::IsInitialized(void) const
{

View File

@ -3,16 +3,12 @@
#include "tier2/socketcreator.h"
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
constexpr char s_pszNoAuthMessage[] = "This server is password protected for console access; authenticate with 'PASS <password>' command.\n";
constexpr char s_pszWrongPwMessage[] = "Admin password incorrect.\n";
constexpr char s_pszBannedMessage[] = "Go away.\n";
constexpr char s_pszAuthMessage[] = "Authentication successful.\n";
#include "engine/shared/base_rcon.h"
#define RCON_MIN_PASSWORD_LEN 8
#define RCON_MAX_BANNEDLIST_SIZE 512
class CRConServer
class CRConServer : public CNetConBase
{
public:
CRConServer(void);
@ -27,42 +23,37 @@ public:
void Think(void);
void RunFrame(void);
void Send(const std::string& responseMsg, const std::string& responseVal, const sv_rcon::response_t responseType,
const int nMessageId = static_cast<int>(eDLL_T::NETCON), const int nMessageType = static_cast<int>(LogType_t::LOG_NET));
void Send(const SocketHandle_t hSocket, const std::string& responseMsg, const std::string& responseVal,
const sv_rcon::response_t responseType, const int nMessageId = static_cast<int>(eDLL_T::NETCON), const int nMessageType = static_cast<int>(LogType_t::LOG_NET));
void Recv(void);
std::string Serialize(const std::string& responseMsg, const std::string& responseVal, const sv_rcon::response_t responseType,
bool SendEncode(const char* pResponseMsg, const char* pResponseVal, const sv_rcon::response_t responseType,
const int nMessageId = static_cast<int>(eDLL_T::NETCON), const int nMessageType = static_cast<int>(LogType_t::LOG_NET)) const;
bool SendEncode(const SocketHandle_t hSocket, const char* pResponseMsg, const char* pResponseVal, const sv_rcon::response_t responseType,
const int nMessageId = static_cast<int>(eDLL_T::NETCON), const int nMessageType = static_cast<int>(LogType_t::LOG_NET)) const;
bool SendToAll(const char* pMsgBuf, int nMsgLen) const;
bool Serialize(vector<char>& vecBuf, const char* pResponseMsg, const char* pResponseVal, const sv_rcon::response_t responseType,
const int nMessageId = static_cast<int>(eDLL_T::NETCON), const int nMessageType = static_cast<int>(LogType_t::LOG_NET)) const;
cl_rcon::request Deserialize(const std::string& svBuf) const;
void Authenticate(const cl_rcon::request& cl_request, CConnectedNetConsoleData* pData);
bool Comparator(std::string svPassword) const;
void ProcessBuffer(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const cl_rcon::request& cl_request);
virtual bool ProcessMessage(const char* pMsgBug, int nMsgLen) override;
void Execute(const cl_rcon::request& cl_request, const bool bConVar) const;
bool CheckForBan(CConnectedNetConsoleData* pData);
void CloseConnection(void);
virtual void Disconnect(const char* szReason = nullptr) override;
void CloseNonAuthConnection(void);
bool ShouldSend(const sv_rcon::response_t responseType) const;
bool IsInitialized(void) const;
private:
void Send(const std::string& svMessage) const;
void Send(const SocketHandle_t hSocket, const std::string& svMessage) const;
bool m_bInitialized;
int m_nConnIndex;
int m_nAuthConnections;
bool m_bInitialized;
std::unordered_set<std::string> m_BannedList;
std::string m_svPasswordHash;
netadr_t m_Address;
netadr_t m_WhiteListAddress;
CSocketCreator m_Socket;
};
CRConServer* RCONServer();

View File

@ -0,0 +1,180 @@
//===========================================================================//
//
// Purpose: Base rcon implementation.
//
//===========================================================================//
#include "core/stdafx.h"
#include "base_rcon.h"
#include "engine/net.h"
#include "shared_rcon.h"
//-----------------------------------------------------------------------------
// Purpose: encode message to buffer
// Input : *pMsg -
// *pMsgBuf -
// nMsgLen -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CNetConBase::Encode(google::protobuf::MessageLite* pMsg, char* pMsgBuf, size_t nMsgLen) const
{
return pMsg->SerializeToArray(pMsgBuf, int(nMsgLen));
}
//-----------------------------------------------------------------------------
// Purpose: decode message from buffer
// Input : *pMsg -
// *pMsgBuf -
// nMsgLen -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CNetConBase::Decode(google::protobuf::MessageLite* pMsg, const char* pMsgBuf, size_t nMsgLen) const
{
return pMsg->ParseFromArray(pMsgBuf, int(nMsgLen));
}
//-----------------------------------------------------------------------------
// Purpose: connect to remote
// Input : *pHostName -
// nPort -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CNetConBase::Connect(const char* pHostName, const int nPort)
{
if (CL_NetConConnect(this, pHostName, nPort))
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: send message to specific connected socket
// Input : hSocket -
// *pMsgBuf -
// nMsgLen -
// Output: true on success, false otherwise
//-----------------------------------------------------------------------------
bool CNetConBase::Send(const SocketHandle_t hSocket, const char* pMsgBuf, int nMsgLen) const
{
std::ostringstream sendbuf;
const u_long nLen = htonl(u_long(nMsgLen));
sendbuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
sendbuf.write(pMsgBuf, nMsgLen);
int ret = ::send(hSocket, sendbuf.str().data(), int(sendbuf.str().size()), MSG_NOSIGNAL);
return (ret != SOCKET_ERROR);
}
//-----------------------------------------------------------------------------
// Purpose: receive message
// Input : *pData -
// nMaxLen -
//-----------------------------------------------------------------------------
void CNetConBase::Recv(CConnectedNetConsoleData* pData, const int nMaxLen)
{
static char szRecvBuf[1024];
{//////////////////////////////////////////////
const int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_Socket.IsSocketBlocking())
{
return;
}
if (nPendingLen <= 0) // EOF or error.
{
Disconnect("Server closed RCON connection\n");
return;
}
}//////////////////////////////////////////////
int nReadLen = 0; // Find out how much we have to read.
::ioctlsocket(pData->m_hSocket, FIONREAD, reinterpret_cast<u_long*>(&nReadLen));
while (nReadLen > 0)
{
const int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0) // Socket was closed.
{
Disconnect("Server closed RCON connection\n");
break;
}
if (nRecvLen < 0 && !m_Socket.IsSocketBlocking())
{
Error(eDLL_T::ENGINE, NO_ERROR, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
}
nReadLen -= nRecvLen; // Process what we've got.
ProcessBuffer(pData, szRecvBuf, nRecvLen, nMaxLen);
}
}
//-----------------------------------------------------------------------------
// Purpose: parses input response buffer using length-prefix framing
// Input : *pRecvBuf -
// nRecvLen -
// *pData -
// Output: true on success, false otherwise
//-----------------------------------------------------------------------------
bool CNetConBase::ProcessBuffer(CConnectedNetConsoleData* pData, const char* pRecvBuf, int nRecvLen, int nMaxLen)
{
while (nRecvLen > 0)
{
if (pData->m_nPayloadLen)
{
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
ProcessMessage(
reinterpret_cast<const char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen);
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
}
else if (pData->m_nPayloadRead+1 <= sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = int(ntohl(*reinterpret_cast<u_long*>(&pData->m_RecvBuffer[0])));
pData->m_nPayloadRead = 0;
if (!pData->m_bAuthorized && nMaxLen > -1)
{
if (pData->m_nPayloadLen > nMaxLen)
{
Disconnect(); // Sending large messages while not authenticated.
return false;
}
}
if (pData->m_nPayloadLen < 0 ||
pData->m_nPayloadLen > pData->m_RecvBuffer.max_size())
{
Error(eDLL_T::ENGINE, NO_ERROR, "RCON Cmd: sync error (%d)\n", pData->m_nPayloadLen);
Disconnect(); // Out of sync (irrecoverable).
return false;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
}
return true;
}

View File

@ -0,0 +1,34 @@
#ifndef BASE_RCON_H
#define BASE_RCON_H
#include "tier1/NetAdr.h"
#include "tier2/socketcreator.h"
#include "protobuf/message_lite.h"
class CNetConBase
{
public:
CNetConBase(void)
{}
virtual bool Encode(google::protobuf::MessageLite* pMsg, char* pMsgBuf, size_t nMsgLen) const;
virtual bool Decode(google::protobuf::MessageLite* pMsg, const char* pMsgBuf, size_t nMsgLen) const;
virtual bool Connect(const char* pHostAdr, const int nHostPort = -1);
virtual void Disconnect(const char* szReason = nullptr) { NOTE_UNUSED(szReason); };
virtual bool Send(const SocketHandle_t hSocket, const char* pMsgBuf, int nMsgLen) const;
virtual void Recv(CConnectedNetConsoleData* pData, const int nMaxLen = -1);
virtual bool ProcessBuffer(CConnectedNetConsoleData* pData, const char* pRecvBuf, int nRecvLen, int nMaxLen = -1);
virtual bool ProcessMessage(const char* /*pMsgBuf*/, int /*nMsgLen*/) { return true; };
CSocketCreator* GetSocketCreator(void) { return &m_Socket; }
netadr_t* GetNetAddress(void) { return &m_Address; }
protected:
CSocketCreator m_Socket;
netadr_t m_Address;
};
#endif // BASE_RCON_H

View File

@ -0,0 +1,114 @@
//===========================================================================//
//
// Purpose: Shared rcon utilities.
//
//===========================================================================//
#include "core/stdafx.h"
#include "base_rcon.h"
#include "shared_rcon.h"
//-----------------------------------------------------------------------------
// Purpose: serialize message to vector
// Input : *pBase -
// &vecBuf -
// *szReqBuf -
// *szReqVal -
// *requestType -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CL_NetConSerialize(const CNetConBase* pBase, vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType)
{
cl_rcon::request request;
request.set_messageid(-1);
request.set_requesttype(requestType);
request.set_requestmsg(szReqBuf);
request.set_requestval(szReqVal);
const size_t msgLen = request.ByteSizeLong();
vecBuf.resize(msgLen);
if (!pBase->Encode(&request, &vecBuf[0], msgLen))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to encode RCON buffer\n");
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: attempt to connect to remote
// Input : *pBase -
// *pHostAdr -
// nHostPort -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CL_NetConConnect(CNetConBase* pBase, const char* pHostAdr, const int nHostPort)
{
string svLocalHost;
if (nHostPort != -1 && strcmp(pHostAdr, "localhost") == 0)
{
char szHostName[512];
if (!gethostname(szHostName, sizeof(szHostName)))
{
svLocalHost = Format("[%s]:%i", szHostName, nHostPort);
pHostAdr = svLocalHost.c_str();
}
}
CNetAdr* pNetAdr = pBase->GetNetAddress();
if (!pNetAdr->SetFromString(pHostAdr, true))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to set RCON address: %s\n", pHostAdr);
return false;
}
pNetAdr->SetPort(htons(u_short(nHostPort)));
CSocketCreator* pCreator = pBase->GetSocketCreator();
if (pCreator->ConnectSocket(*pNetAdr, true) == SOCKET_ERROR)
{
return false;
}
DevMsg(eDLL_T::CLIENT, "Connected to: %s\n", pNetAdr->ToString());
return true;
}
//-----------------------------------------------------------------------------
// Purpose: gets the netconsole data
// Input : *pBase -
// iSocket -
// Output : nullptr on failure
//-----------------------------------------------------------------------------
CConnectedNetConsoleData* SH_GetNetConData(CNetConBase* pBase, int iSocket)
{
const CSocketCreator* pCreator = pBase->GetSocketCreator();
Assert(iSocket >= 0 && iSocket < pCreator->GetAcceptedSocketCount());
if (!pCreator->GetAcceptedSocketCount())
{
return nullptr;
}
return pCreator->GetAcceptedSocketData(iSocket);
}
//-----------------------------------------------------------------------------
// Purpose: gets the netconsole socket
// Input : *pBase -
// iSocket -
// Output : SOCKET_ERROR (-1) on failure
//-----------------------------------------------------------------------------
SocketHandle_t SH_GetNetConSocketHandle(CNetConBase* pBase, int iSocket)
{
const CConnectedNetConsoleData* pData = SH_GetNetConData(pBase, iSocket);
if (!pData)
{
return SOCKET_ERROR;
}
return pData->m_hSocket;
}

View File

@ -0,0 +1,14 @@
#ifndef SHARED_RCON_H
#define SHARED_RCON_H
#include "base_rcon.h"
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
bool CL_NetConSerialize(const CNetConBase* pBase, vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType);
bool CL_NetConConnect(CNetConBase* pBase, const char* pHostAdr, const int nHostPort);
CConnectedNetConsoleData* SH_GetNetConData(CNetConBase* pBase, int iSocket);
SocketHandle_t SH_GetNetConSocketHandle(CNetConBase* pBase, int iSocket);
#endif // SHARED_RCON_H

View File

@ -14,6 +14,7 @@
#include "protoc/cl_rcon.pb.h"
#include "public/utility/utility.h"
#include "engine/net.h"
#include "engine/shared/shared_rcon.h"
#include "netconsole/netconsole.h"
//-----------------------------------------------------------------------------
@ -23,7 +24,6 @@ CNetCon::CNetCon(void)
: m_bInitialized(false)
, m_bQuitApplication(false)
, m_bPromptConnect(true)
, m_bConnEstablished(false)
, m_flTickInterval(0.05f)
{
}
@ -46,18 +46,20 @@ bool CNetCon::Init(void)
if (nError != 0)
{
Error(eDLL_T::NONE, NO_ERROR, "%s - Failed to start Winsock: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError()));
Error(eDLL_T::CLIENT, NO_ERROR, "%s - Failed to start Winsock: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError()));
return false;
}
this->TermSetup();
m_bInitialized = true;
TermSetup();
DevMsg(eDLL_T::NONE, "R5 TCP net console [Version %s]\n", NETCON_VERSION);
static std::thread frame([this]()
{
for (;;)
{
this->RunFrame();
RunFrame();
}
});
frame.detach();
@ -74,7 +76,6 @@ bool CNetCon::Shutdown(void)
bool bResult = false;
m_Socket.CloseAllAcceptedSockets();
m_bConnEstablished = false;
const int nError = ::WSACleanup();
if (nError == 0)
@ -83,7 +84,7 @@ bool CNetCon::Shutdown(void)
}
else // WSACleanup() failed.
{
Error(eDLL_T::NONE, NO_ERROR, "%s - Failed to stop Winsock: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError()));
Error(eDLL_T::CLIENT, NO_ERROR, "%s - Failed to stop Winsock: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError()));
}
SpdLog_Shutdown();
@ -118,40 +119,52 @@ void CNetCon::UserInput(void)
}
std::lock_guard<std::mutex> l(m_Mutex);
if (m_bConnEstablished)
if (IsConnected())
{
if (m_Input.compare("disconnect") == 0)
{
this->Disconnect();
Disconnect();
return;
}
const std::vector<std::string> vSubStrings = StringSplit(m_Input, ' ', 2);
vector<char> vecMsg;
const SocketHandle_t hSocket = GetSocket();
bool bSend = false;
if (vSubStrings.size() > 1)
{
if (vSubStrings[0].compare("PASS") == 0) // Auth with RCON server.
{
std::string svSerialized = this->Serialize(vSubStrings[1], "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
this->Send(svSerialized);
bSend = Serialize(vecMsg, vSubStrings[1].c_str(), "",
cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
}
else if (vSubStrings[0].compare("SET") == 0) // Set value query.
{
if (vSubStrings.size() > 2)
{
std::string svSerialized = this->Serialize(vSubStrings[1], vSubStrings[2], cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE);
this->Send(svSerialized);
bSend = Serialize(vecMsg, vSubStrings[1].c_str(), vSubStrings[2].c_str(),
cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE);
}
}
else // Execute command query.
{
std::string svSerialized = this->Serialize(m_Input, "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
this->Send(svSerialized);
bSend = Serialize(vecMsg, m_Input.c_str(), "",
cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
}
}
else if (!m_Input.empty()) // Single arg command query.
{
std::string svSerialized = this->Serialize(m_Input, "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
this->Send(svSerialized);
bSend = Serialize(vecMsg, m_Input.c_str(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
}
if (bSend) // Only send if serialization process was successful.
{
if (!Send(hSocket, vecMsg.data(), int(vecMsg.size())))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
}
}
}
else // Setup connection from input.
@ -167,7 +180,14 @@ void CNetCon::UserInput(void)
std::string svInPort = m_Input.substr(nPos + 1);
std::string svInAdr = m_Input.erase(m_Input.find(' '));
if (!this->Connect(svInAdr, svInPort))
if (svInPort.empty() || svInAdr.empty())
{
Warning(eDLL_T::CLIENT, "No IP address or port provided\n");
m_bPromptConnect = true;
return;
}
if (!Connect(svInAdr.c_str(), atoi(svInPort.c_str())))
{
m_bPromptConnect = true;
return;
@ -176,7 +196,7 @@ void CNetCon::UserInput(void)
}
else // Initialize as [ip]:port.
{
if (!this->Connect(m_Input, ""))
if (m_Input.empty() || !Connect(m_Input.c_str()))
{
m_bPromptConnect = true;
return;
@ -199,10 +219,12 @@ void CNetCon::ClearInput(void)
//-----------------------------------------------------------------------------
void CNetCon::RunFrame(void)
{
if (m_bConnEstablished)
if (IsInitialized() && IsConnected())
{
std::lock_guard<std::mutex> l(m_Mutex);
this->Recv();
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(0);
Recv(pData);
}
else if (m_bPromptConnect)
{
@ -219,219 +241,69 @@ void CNetCon::RunFrame(void)
//-----------------------------------------------------------------------------
bool CNetCon::ShouldQuit(void) const
{
return this->m_bQuitApplication;
}
//-----------------------------------------------------------------------------
// Purpose: connect to specified address and port
// Input : *svInAdr -
// *svInPort -
// Output : true if connection succeeds, false otherwise
//-----------------------------------------------------------------------------
bool CNetCon::Connect(const std::string& svInAdr, const std::string& svInPort)
{
if (!svInAdr.empty() && !svInPort.empty()) // Construct from ip port
{
string svLocalHost;
if (svInAdr.compare("localhost") == 0)
{
char szHostName[512];
if (!gethostname(szHostName, sizeof(szHostName)))
{
svLocalHost = szHostName;
}
}
const string svFull = Format("[%s]:%s", svLocalHost.empty() ? svInAdr.c_str() : svLocalHost.c_str(), svInPort.c_str());
if (!m_Address.SetFromString(svFull.c_str(), true))
{
Warning(eDLL_T::NONE, "Failed to set RCON address: %s\n", svFull.c_str());
}
}
else if (!svInAdr.empty()) // construct from [ip]:port
{
if (!m_Address.SetFromString(svInAdr.c_str(), true))
{
Warning(eDLL_T::NONE, "Failed to set RCON address: %s\n", svInAdr.c_str());
}
}
else
{
Warning(eDLL_T::NONE, "No IP address provided\n");
return false;
}
if (m_Socket.ConnectSocket(m_Address, true) == SOCKET_ERROR)
{
Error(eDLL_T::NONE, NO_ERROR, "Failed to connect: error(%s); verify IP and PORT\n", "SOCKET_ERROR");
return false;
}
DevMsg(eDLL_T::NONE, "Connected to: %s\n", m_Address.ToString());
m_bConnEstablished = true;
return true;
return m_bQuitApplication;
}
//-----------------------------------------------------------------------------
// Purpose: disconnect from current session
//-----------------------------------------------------------------------------
void CNetCon::Disconnect(void)
void CNetCon::Disconnect(const char* szReason)
{
m_Socket.CloseAcceptedSocket(0);
if (IsConnected())
{
if (szReason)
{
DevMsg(eDLL_T::CLIENT, "%s", szReason);
}
m_Socket.CloseAcceptedSocket(0);
}
m_bPromptConnect = true;
m_bConnEstablished = false;
}
//-----------------------------------------------------------------------------
// Purpose: send message
// Input : *svMessage -
//-----------------------------------------------------------------------------
void CNetCon::Send(const std::string& svMessage) const
{
std::ostringstream ssSendBuf;
const u_long nLen = htonl(u_long(svMessage.size()));
ssSendBuf.write(reinterpret_cast<const char*>(&nLen), sizeof(u_long));
ssSendBuf.write(svMessage.data(), svMessage.size());
const int nSendResult = ::send(m_Socket.GetAcceptedSocketData(0)->m_hSocket,
ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
if (nSendResult == SOCKET_ERROR)
{
Error(eDLL_T::NONE, NO_ERROR, "Failed to send message (%s)\n", "SOCKET_ERROR");
}
}
//-----------------------------------------------------------------------------
// Purpose: receive message
//-----------------------------------------------------------------------------
void CNetCon::Recv(void)
{
static char szRecvBuf[1024];
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(0);
{//////////////////////////////////////////////
const int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_Socket.IsSocketBlocking())
{
return;
}
if (nPendingLen <= 0 && m_bConnEstablished) // EOF or error.
{
this->Disconnect();
DevMsg(eDLL_T::NONE, "Server closed connection\n");
return;
}
}//////////////////////////////////////////////
u_long nReadLen; // Find out how much we have to read.
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
while (nReadLen > 0)
{
const int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0 && m_bConnEstablished) // Socket was closed.
{
this->Disconnect();
DevMsg(eDLL_T::NONE, "Server closed connection\n");
break;
}
if (nRecvLen < 0 && !m_Socket.IsSocketBlocking())
{
Error(eDLL_T::NONE, NO_ERROR, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
}
nReadLen -= nRecvLen; // Process what we've got.
this->ProcessBuffer(szRecvBuf, nRecvLen, pData);
}
}
//-----------------------------------------------------------------------------
// Purpose: parses input response buffer using length-prefix framing
// Input : *pRecvBuf -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CNetCon::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
while (nRecvLen > 0)
{
if (pData->m_nPayloadLen)
{
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
this->ProcessMessage(this->Deserialize(std::string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
}
else if (pData->m_nPayloadRead < sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = ntohl(*reinterpret_cast<u_long*>(&pData->m_RecvBuffer[0]));
pData->m_nPayloadRead = 0;
if (pData->m_nPayloadLen < 0 ||
pData->m_nPayloadLen > pData->m_RecvBuffer.max_size())
{
Error(eDLL_T::NONE, NO_ERROR, "RCON Cmd: sync error (%zu)\n", pData->m_nPayloadLen);
this->Disconnect(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: processes received message
// Input : *sv_response -
//-----------------------------------------------------------------------------
void CNetCon::ProcessMessage(const sv_rcon::response& sv_response) const
bool CNetCon::ProcessMessage(const char* pMsgBuf, int nMsgLen)
{
switch (sv_response.responsetype())
sv_rcon::response response;
bool bSuccess = Decode(&response, pMsgBuf, nMsgLen);
if (!bSuccess)
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to decode RCON buffer\n");
return false;
}
switch (response.responsetype())
{
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
{
if (!sv_response.responseval().empty())
if (!response.responseval().empty())
{
const long i = strtol(sv_response.responseval().c_str(), NULL, NULL);
const long i = strtol(response.responseval().c_str(), NULL, NULL);
if (!i) // sv_rcon_sendlogs is not set.
{
string svLogQuery = this->Serialize("", "1",
cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
this->Send(svLogQuery);
vector<char> vecMsg;
bool ret = Serialize(vecMsg, "", "1", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
if (ret && !Send(GetSocket(), vecMsg.data(), int(vecMsg.size())))
{
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
}
}
}
DevMsg(eDLL_T::NETCON, "%s", sv_response.responsemsg().c_str());
DevMsg(eDLL_T::NETCON, "%s", response.responsemsg().c_str());
break;
}
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
{
NetMsg(static_cast<LogType_t>(sv_response.messagetype()),
static_cast<eDLL_T>(sv_response.messageid()),
sv_response.responseval().c_str(), "%s", sv_response.responsemsg().c_str());
NetMsg(static_cast<LogType_t>(response.messagetype()),
static_cast<eDLL_T>(response.messageid()),
response.responseval().c_str(), "%s", response.responsemsg().c_str());
break;
}
default:
@ -439,51 +311,47 @@ void CNetCon::ProcessMessage(const sv_rcon::response& sv_response) const
break;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: serializes input
// Input : *svReqBuf -
// Purpose: serializes message to vector
// Input : &vecBuf -
// *szReqBuf -
// *svReqVal -
// request_t -
// Output : serialized results as string
// requestType -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& svReqVal, const cl_rcon::request_t request_t) const
bool CNetCon::Serialize(vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType) const
{
cl_rcon::request cl_request;
cl_request.set_messageid(-1);
cl_request.set_requesttype(request_t);
switch (request_t)
{
case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE:
case cl_rcon::request_t::SERVERDATA_REQUEST_AUTH:
{
cl_request.set_requestmsg(svReqBuf);
cl_request.set_requestval(svReqVal);
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND:
{
cl_request.set_requestmsg(svReqBuf);
break;
}
}
return cl_request.SerializeAsString();
return CL_NetConSerialize(this, vecBuf, szReqBuf, szReqVal, requestType);
}
//-----------------------------------------------------------------------------
// Purpose: de-serializes input
// Input : *svBuf -
// Output : de-serialized object
// Purpose: retrieves the remote socket
// Output : SOCKET_ERROR (-1) on failure
//-----------------------------------------------------------------------------
sv_rcon::response CNetCon::Deserialize(const std::string& svBuf) const
SocketHandle_t CNetCon::GetSocket(void)
{
sv_rcon::response sv_response;
sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return SH_GetNetConSocketHandle(this, 0);
}
return sv_response;
//-----------------------------------------------------------------------------
// Purpose: returns whether the rcon client is initialized
//-----------------------------------------------------------------------------
bool CNetCon::IsInitialized(void) const
{
return m_bInitialized;
}
//-----------------------------------------------------------------------------
// Purpose: returns whether the rcon client is connected
//-----------------------------------------------------------------------------
bool CNetCon::IsConnected(void)
{
return (GetSocket() != SOCKET_ERROR);
}
//-----------------------------------------------------------------------------
@ -500,7 +368,7 @@ int main(int argc, char* argv[])
if (argc >= 3) // Get IP and Port from command line.
{
if (!NetConsole()->Connect(argv[1], argv[2]))
if (!NetConsole()->Connect(argv[1], atoi(argv[2])))
{
return EXIT_FAILURE;
}

View File

@ -6,10 +6,11 @@
#pragma once
#include "protoc/cl_rcon.pb.h"
#include "protoc/sv_rcon.pb.h"
#include "engine/shared/base_rcon.h"
constexpr const char* NETCON_VERSION = "2.0.0.1";
class CNetCon
class CNetCon : public CNetConBase
{
public:
CNetCon(void);
@ -25,29 +26,23 @@ public:
void RunFrame(void);
bool ShouldQuit(void) const;
bool Connect(const std::string& svInAdr, const std::string& svInPort);
void Disconnect(void);
virtual void Disconnect(const char* szReason = nullptr);
virtual bool ProcessMessage(const char* pMsgBuf, int nMsgLen) override;
void Send(const std::string& svMessage) const;
void Recv(void);
bool Serialize(vector<char>& vecBuf, const char* szReqBuf,
const char* szReqVal, const cl_rcon::request_t requestType) const;
void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const sv_rcon::response& sv_response) const;
std::string Serialize(const std::string& svReqBuf, const std::string& svReqVal, const cl_rcon::request_t request_t) const;
sv_rcon::response Deserialize(const std::string& svBuf) const;
SocketHandle_t GetSocket(void);
bool IsInitialized(void) const;
bool IsConnected(void);
private:
bool m_bInitialized;
bool m_bQuitApplication;
bool m_bPromptConnect;
bool m_bConnEstablished;
float m_flTickInterval;
CNetAdr m_Address;
CSocketCreator m_Socket;
std::string m_Input;
mutable std::mutex m_Mutex;
};

View File

@ -113,6 +113,8 @@ extern ConVar* sv_rcon_banpenalty;
extern ConVar* sv_rcon_maxfailures;
extern ConVar* sv_rcon_maxignores;
extern ConVar* sv_rcon_maxsockets;
extern ConVar* sv_rcon_maxconnections;
extern ConVar* sv_rcon_maxpacketsize;
extern ConVar* sv_rcon_whitelist_address;
//#endif // DEDICATED
#endif // CLIENT_DLL

View File

@ -14,13 +14,13 @@ public:
void RunFrame(void);
void ProcessAccept(void);
bool ConfigureSocket(SocketHandle_t hSocket, bool bDualStack = true);
bool CreateListenSocket(const netadr_t& netAdr, bool bDualStack = true);
void CloseListenSocket(void);
int ConnectSocket(const netadr_t& netAdr, bool bSingleSocket);
void DisconnectSocket(void);
bool ConfigureSocket(SocketHandle_t hSocket, bool bDualStack = true);
int OnSocketAccepted(SocketHandle_t hSocket, const netadr_t& netAdr);
void CloseAcceptedSocket(int nIndex);

View File

@ -257,6 +257,8 @@ void CoreMsgV(LogType_t logType, LogLevel_t logLevel, eDLL_T context,
bool bSquirrel = false;
bool bWarning = false;
bool bError = false;
#else
NOTE_UNUSED(pszLogger);
#endif // !NETCONSOLE
//-------------------------------------------------------------------------
@ -376,10 +378,10 @@ void CoreMsgV(LogType_t logType, LogLevel_t logLevel, eDLL_T context,
if (bToConsole)
{
#ifndef CLIENT_DLL
if (!LoggedFromClient(context))
if (!LoggedFromClient(context) && RCONServer()->ShouldSend(sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG))
{
RCONServer()->Send(formatted, pszUpTime, sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG,
static_cast<int>(context), static_cast<int>(logType));
RCONServer()->SendEncode(formatted.c_str(), pszUpTime, sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG,
int(context), int(logType));
}
#endif // !CLIENT_DLL
#ifndef DEDICATED

View File

@ -108,6 +108,8 @@ ConVar* sv_rcon_banpenalty = nullptr; // TODO
ConVar* sv_rcon_maxfailures = nullptr;
ConVar* sv_rcon_maxignores = nullptr;
ConVar* sv_rcon_maxsockets = nullptr;
ConVar* sv_rcon_maxconnections = nullptr;
ConVar* sv_rcon_maxpacketsize = nullptr;
ConVar* sv_rcon_whitelist_address = nullptr;
//#endif // DEDICATED
#endif // !CLIENT_DLL
@ -348,7 +350,9 @@ void ConVar::StaticInit(void)
sv_rcon_maxfailures = ConVar::StaticCreate("sv_rcon_maxfailures", "10", FCVAR_RELEASE, "Max number of times a user can fail rcon authentication before being banned.", true, 1.f, false, 0.f, nullptr, nullptr);
sv_rcon_maxignores = ConVar::StaticCreate("sv_rcon_maxignores" , "15", FCVAR_RELEASE, "Max number of times a user can ignore the instruction message before being banned.", true, 1.f, false, 0.f, nullptr, nullptr);
sv_rcon_maxsockets = ConVar::StaticCreate("sv_rcon_maxsockets" , "32", FCVAR_RELEASE, "Max number of accepted sockets before the server starts closing redundant sockets.", true, 1.f, false, 0.f, nullptr, nullptr);
sv_rcon_whitelist_address = ConVar::StaticCreate("sv_rcon_whitelist_address", "", FCVAR_RELEASE, "This address is not considered a 'redundant' socket and will never be banned for failed authentication attempts.", false, 0.f, false, 0.f, &RCON_WhiteListAddresChanged_f, "Format: '::ffff:127.0.0.1'");
sv_rcon_maxconnections = ConVar::StaticCreate("sv_rcon_maxconnections" , "1" , FCVAR_RELEASE, "Max number of authenticated connections before the server closes the listen socket.", true, 1.f, false, 0.f, nullptr, nullptr);
sv_rcon_maxpacketsize = ConVar::StaticCreate("sv_rcon_maxpacketsize" , "1024", FCVAR_RELEASE, "Max number of bytes allowed in a command packet from a non-authenticated net console.", true, 0.f, false, 0.f, nullptr, nullptr);
sv_rcon_whitelist_address = ConVar::StaticCreate("sv_rcon_whitelist_address", "" , FCVAR_RELEASE, "This address is not considered a 'redundant' socket and will never be banned for failed authentication attempts.", false, 0.f, false, 0.f, &RCON_WhiteListAddresChanged_f, "Format: '::ffff:127.0.0.1'");
sv_quota_stringCmdsPerSecond = ConVar::StaticCreate("sv_quota_stringCmdsPerSecond", "16", FCVAR_RELEASE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands.", true, 0.f, false, 0.f, nullptr, nullptr);
sv_validatePersonaName = ConVar::StaticCreate("sv_validatePersonaName" , "1" , FCVAR_RELEASE, "Validate the client's textual persona name on connect.", true, 0.f, false, 0.f, nullptr, nullptr);

View File

@ -17,7 +17,7 @@
//-----------------------------------------------------------------------------
CSocketCreator::CSocketCreator(void)
{
m_hListenSocket = -1;
m_hListenSocket = SOCKET_ERROR;
}
//-----------------------------------------------------------------------------
@ -68,56 +68,6 @@ void CSocketCreator::ProcessAccept(void)
OnSocketAccepted(newSocket, netAdr);
}
//-----------------------------------------------------------------------------
// Purpose: Configures a socket for use
// Input : iSocket -
// bDualStack -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CSocketCreator::ConfigureSocket(SocketHandle_t hSocket, bool bDualStack /*= true*/)
{
// Disable NAGLE as RCON cmds are small in size.
int opt = 1;
int ret = ::setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "TCP_NODELAY", NET_ErrorString(WSAGetLastError()));
return false;
}
// Mark socket as reusable.
opt = 1;
ret = ::setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "SO_REUSEADDR", NET_ErrorString(WSAGetLastError()));
return false;
}
if (bDualStack)
{
// Disable IPv6 only mode to enable dual stack.
opt = 0;
ret = ::setsockopt(hSocket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "IPV6_V6ONLY", NET_ErrorString(WSAGetLastError()));
return false;
}
}
// Mark socket as non-blocking.
opt = 1;
ret = ::ioctlsocket(hSocket, FIONBIO, reinterpret_cast<u_long*>(&opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'ioctl(%s)' failed (%s)\n", "FIONBIO", NET_ErrorString(WSAGetLastError()));
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: bind to a TCP port and accept incoming connections
// Input : *netAdr -
@ -245,6 +195,56 @@ void CSocketCreator::DisconnectSocket(void)
CloseAllAcceptedSockets();
}
//-----------------------------------------------------------------------------
// Purpose: Configures a socket for use
// Input : iSocket -
// bDualStack -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CSocketCreator::ConfigureSocket(SocketHandle_t hSocket, bool bDualStack /*= true*/)
{
// Disable NAGLE as RCON cmds are small in size.
int opt = 1;
int ret = ::setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "TCP_NODELAY", NET_ErrorString(WSAGetLastError()));
return false;
}
// Mark socket as reusable.
opt = 1;
ret = ::setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "SO_REUSEADDR", NET_ErrorString(WSAGetLastError()));
return false;
}
if (bDualStack)
{
// Disable IPv6 only mode to enable dual stack.
opt = 0;
ret = ::setsockopt(hSocket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&opt), sizeof(opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'sockopt(%s)' failed (%s)\n", "IPV6_V6ONLY", NET_ErrorString(WSAGetLastError()));
return false;
}
}
// Mark socket as non-blocking.
opt = 1;
ret = ::ioctlsocket(hSocket, FIONBIO, reinterpret_cast<u_long*>(&opt));
if (ret == SOCKET_ERROR)
{
Warning(eDLL_T::ENGINE, "Socket 'ioctl(%s)' failed (%s)\n", "FIONBIO", NET_ErrorString(WSAGetLastError()));
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: handles new TCP requests and puts them in accepted queue
// Input : hSocket -
@ -305,7 +305,7 @@ void CSocketCreator::CloseAllAcceptedSockets(void)
//-----------------------------------------------------------------------------
bool CSocketCreator::IsListening(void) const
{
return m_hListenSocket != -1;
return m_hListenSocket != SOCKET_ERROR;
}
//-----------------------------------------------------------------------------

View File

@ -54,6 +54,8 @@
<ClCompile Include="..\engine\networkstringtable.cpp" />
<ClCompile Include="..\engine\net_chan.cpp" />
<ClCompile Include="..\engine\sdk_dll.cpp" />
<ClCompile Include="..\engine\shared\base_rcon.cpp" />
<ClCompile Include="..\engine\shared\shared_rcon.cpp" />
<ClCompile Include="..\engine\staticpropmgr.cpp" />
<ClCompile Include="..\engine\sys_dll.cpp" />
<ClCompile Include="..\engine\sys_dll2.cpp" />
@ -232,6 +234,8 @@
<ClInclude Include="..\engine\net_chan.h" />
<ClInclude Include="..\engine\packed_entity.h" />
<ClInclude Include="..\engine\sdk_dll.h" />
<ClInclude Include="..\engine\shared\base_rcon.h" />
<ClInclude Include="..\engine\shared\shared_rcon.h" />
<ClInclude Include="..\engine\staticpropmgr.h" />
<ClInclude Include="..\engine\sys_dll.h" />
<ClInclude Include="..\engine\sys_dll2.h" />

View File

@ -229,6 +229,9 @@
<Filter Include="sdk\public\tier2">
<UniqueIdentifier>{3a764173-162a-449c-952b-2db659cbad95}</UniqueIdentifier>
</Filter>
<Filter Include="sdk\engine\shared">
<UniqueIdentifier>{bef90121-4004-47a7-b338-a41ca8078c12}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\client\cdll_engine_int.cpp">
@ -675,6 +678,12 @@
<ClCompile Include="..\engine\staticpropmgr.cpp">
<Filter>sdk\engine</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\base_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\shared_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\client\cdll_engine_int.h">
@ -1724,6 +1733,12 @@
<ClInclude Include="..\engine\staticpropmgr.h">
<Filter>sdk\engine</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\shared_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\base_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="..\shared\resource\lockedserver.png">

View File

@ -237,6 +237,8 @@
<ClInclude Include="..\engine\server\sv_main.h" />
<ClInclude Include="..\engine\server\sv_rcon.h" />
<ClInclude Include="..\engine\staticpropmgr.h" />
<ClInclude Include="..\engine\shared\base_rcon.h" />
<ClInclude Include="..\engine\shared\shared_rcon.h" />
<ClInclude Include="..\engine\sys_dll.h" />
<ClInclude Include="..\engine\sys_dll2.h" />
<ClInclude Include="..\engine\sys_engine.h" />
@ -548,6 +550,8 @@
<ClCompile Include="..\engine\server\sv_main.cpp" />
<ClCompile Include="..\engine\server\sv_rcon.cpp" />
<ClCompile Include="..\engine\staticpropmgr.cpp" />
<ClCompile Include="..\engine\shared\base_rcon.cpp" />
<ClCompile Include="..\engine\shared\shared_rcon.cpp" />
<ClCompile Include="..\engine\sys_dll.cpp" />
<ClCompile Include="..\engine\sys_dll2.cpp" />
<ClCompile Include="..\engine\sys_engine.cpp" />

View File

@ -178,6 +178,9 @@
<Filter Include="sdk\public\tier2">
<UniqueIdentifier>{f6e7b39b-4032-4a7a-8a44-3aabcbe46ba5}</UniqueIdentifier>
</Filter>
<Filter Include="sdk\engine\shared">
<UniqueIdentifier>{4bccf09c-4f8b-4d7e-ab72-54fd8c1fb5cf}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\common\opcodes.h">
@ -1134,6 +1137,12 @@
<ClInclude Include="..\engine\staticpropmgr.h">
<Filter>sdk\engine</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\base_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\shared_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\common\opcodes.cpp">
@ -1526,6 +1535,12 @@
<ClCompile Include="..\engine\staticpropmgr.cpp">
<Filter>sdk\engine</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\base_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\shared_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\Dedicated.def" />

View File

@ -59,6 +59,8 @@
<ClCompile Include="..\engine\server\sv_main.cpp" />
<ClCompile Include="..\engine\server\sv_rcon.cpp" />
<ClCompile Include="..\engine\staticpropmgr.cpp" />
<ClCompile Include="..\engine\shared\base_rcon.cpp" />
<ClCompile Include="..\engine\shared\shared_rcon.cpp" />
<ClCompile Include="..\engine\sys_dll.cpp" />
<ClCompile Include="..\engine\sys_dll2.cpp" />
<ClCompile Include="..\engine\sys_engine.cpp" />
@ -257,6 +259,8 @@
<ClInclude Include="..\engine\server\sv_main.h" />
<ClInclude Include="..\engine\server\sv_rcon.h" />
<ClInclude Include="..\engine\staticpropmgr.h" />
<ClInclude Include="..\engine\shared\base_rcon.h" />
<ClInclude Include="..\engine\shared\shared_rcon.h" />
<ClInclude Include="..\engine\sys_dll.h" />
<ClInclude Include="..\engine\sys_dll2.h" />
<ClInclude Include="..\engine\sys_engine.h" />

View File

@ -238,6 +238,9 @@
<Filter Include="sdk\public\tier2">
<UniqueIdentifier>{42a3745e-8686-4f83-97bd-cec9336b4937}</UniqueIdentifier>
</Filter>
<Filter Include="sdk\engine\shared">
<UniqueIdentifier>{f3335ce8-0956-4d3c-9d12-ca2bd5646d8c}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\client\vengineclient_impl.cpp">
@ -741,6 +744,12 @@
<ClCompile Include="..\engine\staticpropmgr.cpp">
<Filter>sdk\engine</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\base_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\shared_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\client\cdll_engine_int.h">
@ -1895,6 +1904,12 @@
<ClInclude Include="..\engine\staticpropmgr.h">
<Filter>sdk\engine</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\base_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\shared_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="..\shared\resource\lockedserver.png">

View File

@ -38,6 +38,8 @@
</ClCompile>
<ClCompile Include="..\core\termutil.cpp" />
<ClCompile Include="..\engine\net.cpp" />
<ClCompile Include="..\engine\shared\base_rcon.cpp" />
<ClCompile Include="..\engine\shared\shared_rcon.cpp" />
<ClCompile Include="..\netconsole\netconsole.cpp" />
<ClCompile Include="..\netconsole\plat_time.cpp" />
<ClCompile Include="..\protoc\cl_rcon.pb.cc">
@ -67,6 +69,8 @@
<ClInclude Include="..\core\stdafx.h" />
<ClInclude Include="..\core\termutil.h" />
<ClInclude Include="..\engine\net.h" />
<ClInclude Include="..\engine\shared\base_rcon.h" />
<ClInclude Include="..\engine\shared\shared_rcon.h" />
<ClInclude Include="..\netconsole\netconsole.h" />
<ClInclude Include="..\protoc\cl_rcon.pb.h" />
<ClInclude Include="..\protoc\sv_rcon.pb.h" />

View File

@ -41,6 +41,9 @@
<Filter Include="sdk\public\tier2">
<UniqueIdentifier>{3bbe24ed-5dce-4eb6-bb00-ed575b8bffc3}</UniqueIdentifier>
</Filter>
<Filter Include="sdk\engine\shared">
<UniqueIdentifier>{b6e80c43-6b7e-4503-aa64-60981ac963dc}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\core\stdafx.cpp">
@ -82,6 +85,12 @@
<ClCompile Include="..\core\logdef.cpp">
<Filter>core</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\shared_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
<ClCompile Include="..\engine\shared\base_rcon.cpp">
<Filter>sdk\engine\shared</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\core\stdafx.h">
@ -120,5 +129,11 @@
<ClInclude Include="..\public\tier2\socketcreator.h">
<Filter>sdk\public\tier2</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\base_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
<ClInclude Include="..\engine\shared\shared_rcon.h">
<Filter>sdk\engine\shared</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -853,11 +853,13 @@ void RCON_CmdQuery_f(const CCommand& args)
{
if (args.ArgC() < 2)
{
const char* pszAddress = rcon_address->GetString();
if (RCONClient()->IsInitialized()
&& !RCONClient()->IsConnected()
&& strlen(rcon_address->GetString()) > 0)
&& pszAddress[0])
{
RCONClient()->Connect();
RCONClient()->Connect(pszAddress);
}
}
else
@ -869,18 +871,26 @@ void RCON_CmdQuery_f(const CCommand& args)
}
else if (RCONClient()->IsConnected())
{
vector<char> vecMsg;
bool bSuccess = false;
const SocketHandle_t hSocket = RCONClient()->GetSocket();
if (strcmp(args.Arg(1), "PASS") == 0) // Auth with RCON server using rcon_password ConVar value.
{
string svCmdQuery;
if (args.ArgC() > 2)
{
svCmdQuery = RCONClient()->Serialize(args.Arg(2), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
bSuccess = RCONClient()->Serialize(vecMsg, args.Arg(2), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
}
else // Use 'rcon_password' ConVar as password.
{
svCmdQuery = RCONClient()->Serialize(rcon_password->GetString(), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
bSuccess = RCONClient()->Serialize(vecMsg, rcon_password->GetString(), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
}
RCONClient()->Send(svCmdQuery);
if (bSuccess)
{
RCONClient()->Send(hSocket, vecMsg.data(), vecMsg.size());
}
return;
}
else if (strcmp(args.Arg(1), "disconnect") == 0) // Disconnect from RCON server.
@ -889,8 +899,11 @@ void RCON_CmdQuery_f(const CCommand& args)
return;
}
string svCmdQuery = RCONClient()->Serialize(args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
RCONClient()->Send(svCmdQuery);
bSuccess = RCONClient()->Serialize(vecMsg, args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
if (bSuccess)
{
RCONClient()->Send(hSocket, vecMsg.data(), vecMsg.size());
}
return;
}
else
@ -910,9 +923,11 @@ RCON_Disconnect_f
*/
void RCON_Disconnect_f(const CCommand& args)
{
if (RCONClient()->IsConnected())
const bool bIsConnected = RCONClient()->IsConnected();
RCONClient()->Disconnect();
if (bIsConnected) // Log if client was indeed connected.
{
RCONClient()->Disconnect();
DevMsg(eDLL_T::CLIENT, "User closed RCON connection\n");
}
}