From 742614180cbbe598694a48bd9eb6f7b97a762243 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Sat, 3 Aug 2013 23:16:37 +0300
Subject: [PATCH] Fix anticheat

---
 src/content_sao.cpp | 129 +++++++++++++++++++++++---------------------
 src/content_sao.h   |  38 ++++++++++++-
 src/environment.cpp |   3 +-
 src/environment.h   |   6 +++
 src/server.cpp      |  40 +++++++++++---
 5 files changed, 146 insertions(+), 70 deletions(-)

diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index 90c4fa69c..8d46d4237 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -934,7 +934,6 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
 	m_peer_id(peer_id_),
 	m_inventory(NULL),
 	m_last_good_position(0,0,0),
-	m_last_good_position_age(0),
 	m_time_from_last_punch(0),
 	m_nocheat_dig_pos(32767, 32767, 32767),
 	m_nocheat_dig_time(0),
@@ -1002,7 +1001,6 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s)
 	m_player->setPlayerSAO(this);
 	m_player->peer_id = m_peer_id;
 	m_last_good_position = m_player->getPosition();
-	m_last_good_position_age = 0.0;
 }
 
 // Called before removing from environment
@@ -1106,6 +1104,19 @@ void PlayerSAO::step(float dtime, bool send_recommended)
 		m_moved = true;
 	}
 
+	//dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
+
+	// Set lag pool maximums based on estimated lag
+	const float LAG_POOL_MIN = 5.0;
+	float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
+	if(lag_pool_max < LAG_POOL_MIN)
+		lag_pool_max = LAG_POOL_MIN;
+	m_dig_pool.setMax(lag_pool_max);
+	m_move_pool.setMax(lag_pool_max);
+
+	// Increment cheat prevention timers
+	m_dig_pool.add(dtime);
+	m_move_pool.add(dtime);
 	m_time_from_last_punch += dtime;
 	m_nocheat_dig_time += dtime;
 
@@ -1115,66 +1126,8 @@ void PlayerSAO::step(float dtime, bool send_recommended)
 	{
 		v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
 		m_last_good_position = pos;
-		m_last_good_position_age = 0;
 		m_player->setPosition(pos);
 	}
-	else
-	{
-		if(m_is_singleplayer || g_settings->getBool("disable_anticheat"))
-		{
-			m_last_good_position = m_player->getPosition();
-			m_last_good_position_age = 0;
-		}
-		else
-		{
-			/*
-				Check player movements
-
-				NOTE: Actually the server should handle player physics like the
-				client does and compare player's position to what is calculated
-				on our side. This is required when eg. players fly due to an
-				explosion. Altough a node-based alternative might be possible
-				too, and much more lightweight.
-			*/
-
-			float player_max_speed = 0;
-			float player_max_speed_up = 0;
-			if(m_privs.count("fast") != 0){
-				// Fast speed
-				player_max_speed = BS * 20;
-				player_max_speed_up = BS * 20;
-			} else {
-				// Normal speed
-				player_max_speed = BS * 4.0;
-				player_max_speed_up = BS * 4.0;
-			}
-			// Tolerance
-			player_max_speed *= 2.5;
-			player_max_speed_up *= 2.5;
-
-			m_last_good_position_age += dtime;
-			if(m_last_good_position_age >= 1.0){
-				float age = m_last_good_position_age;
-				v3f diff = (m_player->getPosition() - m_last_good_position);
-				float d_vert = diff.Y;
-				diff.Y = 0;
-				float d_horiz = diff.getLength();
-				/*infostream<<m_player->getName()<<"'s horizontal speed is "
-						<<(d_horiz/age)<<std::endl;*/
-				if(d_horiz <= age * player_max_speed &&
-						(d_vert < 0 || d_vert < age * player_max_speed_up)){
-					m_last_good_position = m_player->getPosition();
-				} else {
-					actionstream<<"Player "<<m_player->getName()
-							<<" moved too fast; resetting position"
-							<<std::endl;
-					m_player->setPosition(m_last_good_position);
-					m_moved = true;
-				}
-				m_last_good_position_age = 0;
-			}
-		}
-	}
 
 	if(send_recommended == false)
 		return;
@@ -1267,7 +1220,6 @@ void PlayerSAO::setPos(v3f pos)
 	m_player->setPosition(pos);
 	// Movement caused by this command is always valid
 	m_last_good_position = pos;
-	m_last_good_position_age = 0;
 	// Force position change on client
 	m_moved = true;
 }
@@ -1279,7 +1231,6 @@ void PlayerSAO::moveTo(v3f pos, bool continuous)
 	m_player->setPosition(pos);
 	// Movement caused by this command is always valid
 	m_last_good_position = pos;
-	m_last_good_position_age = 0;
 	// Force position change on client
 	m_moved = true;
 }
@@ -1503,6 +1454,59 @@ std::string PlayerSAO::getPropertyPacket()
 	return gob_cmd_set_properties(m_prop);
 }
 
+void PlayerSAO::checkMovementCheat()
+{
+	if(isAttached() || m_is_singleplayer ||
+			g_settings->getBool("disable_anticheat"))
+	{
+		m_last_good_position = m_player->getPosition();
+	}
+	else
+	{
+		/*
+			Check player movements
+
+			NOTE: Actually the server should handle player physics like the
+			client does and compare player's position to what is calculated
+			on our side. This is required when eg. players fly due to an
+			explosion. Altough a node-based alternative might be possible
+			too, and much more lightweight.
+		*/
+
+		float player_max_speed = 0;
+		float player_max_speed_up = 0;
+		if(m_privs.count("fast") != 0){
+			// Fast speed
+			player_max_speed = m_player->movement_speed_fast;
+			player_max_speed_up = m_player->movement_speed_fast;
+		} else {
+			// Normal speed
+			player_max_speed = m_player->movement_speed_walk;
+			player_max_speed_up = m_player->movement_speed_walk;
+		}
+		// Tolerance. With the lag pool we shouldn't need it.
+		//player_max_speed *= 2.5;
+		//player_max_speed_up *= 2.5;
+
+		v3f diff = (m_player->getPosition() - m_last_good_position);
+		float d_vert = diff.Y;
+		diff.Y = 0;
+		float d_horiz = diff.getLength();
+		float required_time = d_horiz/player_max_speed;
+		if(d_vert > 0 && d_vert/player_max_speed > required_time)
+			required_time = d_vert/player_max_speed;
+		if(m_move_pool.grab(required_time)){
+			m_last_good_position = m_player->getPosition();
+		} else {
+			actionstream<<"Player "<<m_player->getName()
+					<<" moved too fast; resetting position"
+					<<std::endl;
+			m_player->setPosition(m_last_good_position);
+			m_moved = true;
+		}
+	}
+}
+
 bool PlayerSAO::getCollisionBox(aabb3f *toset) {
 	//update collision box
 	*toset = m_player->getCollisionbox();
@@ -1516,3 +1520,4 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) {
 bool PlayerSAO::collideWithObjects(){
 	return true;
 }
+
diff --git a/src/content_sao.h b/src/content_sao.h
index 140211cf6..9640e5f08 100644
--- a/src/content_sao.h
+++ b/src/content_sao.h
@@ -122,6 +122,36 @@ class LuaEntitySAO : public ServerActiveObject
 	PlayerSAO needs some internals exposed.
 */
 
+class LagPool
+{
+	float pool;
+	float max;
+public:
+	LagPool(): pool(15), max(15)
+	{}
+	void setMax(float new_max)
+	{
+		max = new_max;
+		if(pool > new_max)
+			pool = new_max;
+	}
+	void add(float dtime)
+	{
+		pool -= dtime;
+		if(pool < 0)
+			pool = 0;
+	}
+	bool grab(float dtime)
+	{
+		if(dtime <= 0)
+			return true;
+		if(pool + dtime > max)
+			return false;
+		pool += dtime;
+		return true;
+	}
+};
+
 class PlayerSAO : public ServerActiveObject
 {
 public:
@@ -228,6 +258,11 @@ class PlayerSAO : public ServerActiveObject
 	{
 		m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
 	}
+	LagPool& getDigPool()
+	{
+		return m_dig_pool;
+	}
+	void checkMovementCheat();
 
 	// Other
 
@@ -249,8 +284,9 @@ class PlayerSAO : public ServerActiveObject
 	Inventory *m_inventory;
 
 	// Cheat prevention
+	LagPool m_dig_pool;
+	LagPool m_move_pool;
 	v3f m_last_good_position;
-	float m_last_good_position_age;
 	float m_time_from_last_punch;
 	v3s16 m_nocheat_dig_pos;
 	float m_nocheat_dig_time;
diff --git a/src/environment.cpp b/src/environment.cpp
index 57fdfd7e5..63718f3fc 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -332,7 +332,8 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, ScriptApi *scriptIface,
 	m_active_block_interval_overload_skip(0),
 	m_game_time(0),
 	m_game_time_fraction_counter(0),
-	m_recommended_send_interval(0.1)
+	m_recommended_send_interval(0.1),
+	m_max_lag_estimate(0.1)
 {
 }
 
diff --git a/src/environment.h b/src/environment.h
index 8fc5611e4..b59ce83c1 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -304,6 +304,9 @@ class ServerEnvironment : public Environment
 	bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0);
 
 	u32 getGameTime() { return m_game_time; }
+
+	void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
+	float getMaxLagEstimate() { return m_max_lag_estimate; }
 private:
 
 	/*
@@ -378,6 +381,9 @@ class ServerEnvironment : public Environment
 	std::list<ABMWithState> m_abms;
 	// An interval for generally sending object positions and stuff
 	float m_recommended_send_interval;
+	// Estimate for general maximum lag as determined by server.
+	// Can raise to high values like 15s with eg. map generation mods.
+	float m_max_lag_estimate;
 };
 
 #ifndef SERVER
diff --git a/src/server.cpp b/src/server.cpp
index 8c67846c5..cdd42c2e9 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -1090,6 +1090,16 @@ void Server::AsyncRunStep()
 
 	{
 		JMutexAutoLock lock(m_env_mutex);
+		// Figure out and report maximum lag to environment
+		float max_lag = m_env->getMaxLagEstimate();
+		max_lag *= 0.9998; // Decrease slowly (about half per 5 minutes)
+		if(dtime > max_lag){
+			if(dtime > 0.1 && dtime > max_lag * 2.0)
+				infostream<<"Server: Maximum lag peaked to "<<dtime
+						<<" s"<<std::endl;
+			max_lag = dtime;
+		}
+		m_env->reportMaxLagEstimate(max_lag);
 		// Step environment
 		ScopeProfiler sp(g_profiler, "SEnv step");
 		ScopeProfiler sp2(g_profiler, "SEnv step avg", SPT_AVG);
@@ -2241,6 +2251,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		player->control.LMB = (bool)(keyPressed&128);
 		player->control.RMB = (bool)(keyPressed&256);
 
+		playersao->checkMovementCheat();
+
 		/*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
 				<<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
 				<<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
@@ -2953,13 +2965,27 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 								<<std::endl;
 						is_valid_dig = false;
 					}
-					// If time is considerably too short, ignore dig
-					// Check time only for medium and slow timed digs
-					if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
+					// Check digging time
+					// If already invalidated, we don't have to
+					if(!is_valid_dig){
+						// Well not our problem then
+					}
+					// Clean and long dig
+					else if(params.time > 2.0 && nocheat_t * 1.2 > params.time){
+						// All is good, but grab time from pool; don't care if
+						// it's actually available
+						playersao->getDigPool().grab(params.time);
+					}
+					// Short or laggy dig
+					// Try getting the time from pool
+					else if(playersao->getDigPool().grab(params.time)){
+						// All is good
+					}
+					// Dig not possible
+					else{
 						infostream<<"Server: NoCheat: "<<player->getName()
-								<<" completed digging "
-								<<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
-								<<params.time<<"s; not digging."<<std::endl;
+								<<" completed digging "<<PP(p_under)
+								<<"too fast; not digging."<<std::endl;
 						is_valid_dig = false;
 					}
 				}
@@ -4617,6 +4643,8 @@ std::wstring Server::getStatusString()
 	os<<L"version="<<narrow_to_wide(VERSION_STRING);
 	// Uptime
 	os<<L", uptime="<<m_uptime.get();
+	// Max lag estimate
+	os<<L", max_lag="<<m_env->getMaxLagEstimate();
 	// Information about clients
 	std::map<u16, RemoteClient*>::iterator i;
 	bool first;
-- 
GitLab