diff --git a/rehlds/engine/host.cpp b/rehlds/engine/host.cpp index 8417c2a..cd1a48a 100644 --- a/rehlds/engine/host.cpp +++ b/rehlds/engine/host.cpp @@ -958,6 +958,9 @@ void _Host_Frame(float time) Host_Quit_f(); } + //Rehlds Security + Rehlds_Security_Frame(); + #ifdef REHLDS_FLIGHT_REC if (rehlds_flrec_frame.string[0] != '0') { FR_EndFrame(frameCounter); @@ -1177,6 +1180,8 @@ int Host_Init(quakeparms_t *parms) //SystemWrapper_Init(); Host_Version(); + //Rehlds Security + Rehlds_Security_Init(); Q_snprintf(versionString, sizeof(versionString), "%s,%i,%i", gpszVersionString, PROTOCOL_VERSION, build_number()); @@ -1266,6 +1271,9 @@ void Host_Shutdown(void) if (g_pcls.state != ca_dedicated) ClientDLL_Shutdown(); + //Rehlds Security + Rehlds_Security_Shutdown(); + Cmd_RemoveGameCmds(); Cmd_Shutdown(); Cvar_Shutdown(); diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 8f8c7bb..f0fa9c2 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -2395,6 +2395,9 @@ void EXT_FUNC SV_ConnectClient_internal(void) host_client->datagram.buffername = host_client->name; host_client->sendinfo_time = 0.0f; + //Rehlds Security + Rehlds_Security_ClientConnected(host_client - g_psvs.clients); + g_RehldsHookchains.m_ClientConnected.callChain(NULL, GetRehldsApiClient(host_client)); } diff --git a/rehlds/engine/sv_user.cpp b/rehlds/engine/sv_user.cpp index 52e0129..2e113b2 100644 --- a/rehlds/engine/sv_user.cpp +++ b/rehlds/engine/sv_user.cpp @@ -1560,6 +1560,19 @@ void SV_ParseMove(client_t *pSenderClient) net_drop = 0; } + //check move commands rate for this player +#ifdef REHLDS_FIXES + int numCmdsToIssue = numcmds; + if (net_drop > 0) { + numCmdsToIssue += net_drop; + } + g_MoveCommandRateLimiter.MoveCommandsIssued(host_client - g_psvs.clients, numCmdsToIssue); + + if (!host_client->active) { + return; //return if player was kicked + } +#endif + sv_player->v.button = cmds[0].buttons; sv_player->v.light_level = cmds[0].lightlevel; SV_EstablishTimeBase(host_client, cmds, net_drop, numbackup, numcmds); diff --git a/rehlds/msvc/ReHLDS.vcxproj b/rehlds/msvc/ReHLDS.vcxproj index 89fe96c..834c912 100644 --- a/rehlds/msvc/ReHLDS.vcxproj +++ b/rehlds/msvc/ReHLDS.vcxproj @@ -212,6 +212,7 @@ precompiled.h + @@ -542,6 +543,7 @@ + diff --git a/rehlds/msvc/ReHLDS.vcxproj.filters b/rehlds/msvc/ReHLDS.vcxproj.filters index 31093ab..29382ae 100644 --- a/rehlds/msvc/ReHLDS.vcxproj.filters +++ b/rehlds/msvc/ReHLDS.vcxproj.filters @@ -343,6 +343,9 @@ unittests + + rehlds + @@ -1059,6 +1062,9 @@ unittests + + rehlds + diff --git a/rehlds/rehlds/precompiled.h b/rehlds/rehlds/precompiled.h index 0639d6a..401c661 100644 --- a/rehlds/rehlds/precompiled.h +++ b/rehlds/rehlds/precompiled.h @@ -54,5 +54,6 @@ #include "rehlds_api_impl.h" #include "FlightRecorderImpl.h" #include "flight_recorder.h" +#include "rehlds_security.h" #include "dlls/cdll_dll.h" diff --git a/rehlds/rehlds/rehlds_security.cpp b/rehlds/rehlds/rehlds_security.cpp new file mode 100644 index 0000000..b0c307c --- /dev/null +++ b/rehlds/rehlds/rehlds_security.cpp @@ -0,0 +1,102 @@ +#include "precompiled.h" + +cvar_t sv_rehlds_movecmdrate_max_avg = { "sv_rehlds_movecmdrate_max_avg", "750", 0, 750.0f, NULL }; +cvar_t sv_rehlds_movecmdrate_max_burst = { "sv_rehlds_movecmdrate_max_burst", "3500", 0, 3500.0f, NULL }; + +CMoveCommandRateLimiter g_MoveCommandRateLimiter; + +CMoveCommandRateLimiter::CMoveCommandRateLimiter() { + memset(m_AverageMoveCmdRate, 0, sizeof(m_AverageMoveCmdRate)); + memset(m_CurrentMoveCmds, 0, sizeof(m_CurrentMoveCmds)); + m_LastCheckTime = 0; +} + +void CMoveCommandRateLimiter::UpdateAverageRates(double currentTime) { + double dt = currentTime - m_LastCheckTime; + for (unsigned int i = 0; i < MAX_CLIENTS; i++) { + m_AverageMoveCmdRate[i] = (2.0 * m_AverageMoveCmdRate[i] / 3.0) + m_CurrentMoveCmds[i] / dt / 3.0; + m_CurrentMoveCmds[i] = 0; + + CheckAverageRate(i); + } +} + +void CMoveCommandRateLimiter::Frame() { + double currentTime = realtime; + double dt = currentTime - m_LastCheckTime; + + if (dt < 0.5) { //refresh avg. rate every 0.5 sec + return; + } + + UpdateAverageRates(currentTime); + m_LastCheckTime = currentTime; +} + +void CMoveCommandRateLimiter::ClientConnected(unsigned int clientId) { + if (clientId >= (unsigned)g_psvs.maxclients) { + rehlds_syserror(__FUNCTION__": Invalid clientId %u", clientId); + } + + m_CurrentMoveCmds[clientId] = 0; + m_AverageMoveCmdRate[clientId] = 0.0f; +} + +void CMoveCommandRateLimiter::MoveCommandsIssued(unsigned int clientId, unsigned int numCmds) { + if (clientId >= (unsigned)g_psvs.maxclients) { + rehlds_syserror(__FUNCTION__": Invalid clientId %u", clientId); + } + + m_CurrentMoveCmds[clientId] += numCmds; + CheckBurstRate(clientId); +} + +void CMoveCommandRateLimiter::CheckBurstRate(unsigned int clientId) { + client_t* cl = &g_psvs.clients[clientId]; + if (!cl->active || sv_rehlds_movecmdrate_max_burst.value <= 0.0f) { + return; + } + + double dt = realtime - m_LastCheckTime; + if (dt < 0.2) { + dt = 0.2; //small intervals may give too high rates + } + if ((m_CurrentMoveCmds[clientId] / dt) > sv_rehlds_movecmdrate_max_burst.value) { + Cbuf_AddText(va("addip %i %s\n", 5, NET_BaseAdrToString(cl->netchan.remote_address))); + SV_DropClient(cl, false, "Banned for move commands flooding (burst)"); + } +} + +void CMoveCommandRateLimiter::CheckAverageRate(unsigned int clientId) { + client_t* cl = &g_psvs.clients[clientId]; + if (!cl->active || sv_rehlds_movecmdrate_max_burst.value <= 0.0f) { + return; + } + + if (m_AverageMoveCmdRate[clientId] > sv_rehlds_movecmdrate_max_avg.value) { + Cbuf_AddText(va("addip %i %s\n", 5, NET_BaseAdrToString(cl->netchan.remote_address))); + SV_DropClient(cl, false, "Banned for move commands flooding (Avg)"); + } +} + +void Rehlds_Security_Init() { +#ifdef REHLDS_FIXES + Cvar_RegisterVariable(&sv_rehlds_movecmdrate_max_avg); + Cvar_RegisterVariable(&sv_rehlds_movecmdrate_max_burst); +#endif +} + +void Rehlds_Security_Shutdown() { +} + +void Rehlds_Security_Frame() { +#ifdef REHLDS_FIXES + g_MoveCommandRateLimiter.Frame(); +#endif +} + +void Rehlds_Security_ClientConnected(unsigned int clientId) { +#ifdef REHLDS_FIXES + g_MoveCommandRateLimiter.ClientConnected(clientId); +#endif +} \ No newline at end of file diff --git a/rehlds/rehlds/rehlds_security.h b/rehlds/rehlds/rehlds_security.h new file mode 100644 index 0000000..0fd3ac0 --- /dev/null +++ b/rehlds/rehlds/rehlds_security.h @@ -0,0 +1,28 @@ +#pragma once + +#include "engine.h" + +class CMoveCommandRateLimiter { +public: + CMoveCommandRateLimiter(); + void Frame(); + void MoveCommandsIssued(unsigned int clientId, unsigned int numCmds); + void ClientConnected(unsigned int clientId); + +private: + void UpdateAverageRates(double currentTime); + void CheckBurstRate(unsigned int clientId); + void CheckAverageRate(unsigned int clientId); + +private: + float m_AverageMoveCmdRate[MAX_CLIENTS]; + int m_CurrentMoveCmds[MAX_CLIENTS]; + double m_LastCheckTime; +}; + +extern CMoveCommandRateLimiter g_MoveCommandRateLimiter; + +extern void Rehlds_Security_Init(); +extern void Rehlds_Security_Shutdown(); +extern void Rehlds_Security_Frame(); +extern void Rehlds_Security_ClientConnected(unsigned int clientId);