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);