Fix anticheat
authorPerttu Ahola <celeron55@gmail.com>
Sat, 3 Aug 2013 20:16:37 +0000 (23:16 +0300)
committerPerttu Ahola <celeron55@gmail.com>
Sat, 3 Aug 2013 20:16:37 +0000 (23:16 +0300)
src/content_sao.cpp
src/content_sao.h
src/environment.cpp
src/environment.h
src/server.cpp

index 90c4fa69cb43d9cfd8019533ada0090e458aaab3..8d46d423729445241d267d3ee6b3c0280f49a20a 100644 (file)
@@ -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;
 }
+
index 140211cf6f8ba3f3d3f5d87ce4801d17e83d81bb..9640e5f0883d4a40bb708ebc86f7593acea62fcc 100644 (file)
@@ -122,6 +122,36 @@ private:
        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 @@ public:
        {
                m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
        }
+       LagPool& getDigPool()
+       {
+               return m_dig_pool;
+       }
+       void checkMovementCheat();
 
        // Other
 
@@ -249,8 +284,9 @@ private:
        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;
index 57fdfd7e577d4b293ff1729bdb8892280c7b8e39..63718f3fc85a9413b150566daf8fd6e64a187fae 100644 (file)
@@ -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)
 {
 }
 
index 8fc5611e45a4ca83cbd42d998c0bc38fd9b6d8ec..b59ce83c16dd31d17177b9e97eae79cb72af48f4 100644 (file)
@@ -304,6 +304,9 @@ public:
        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 @@ private:
        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
index 8c67846c500f8953059736069d21bc1c7d36ec32..cdd42c2e9f2befca7cc981fe79c27889d6494891 100644 (file)
@@ -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;