Fix anticheat

This commit is contained in:
Perttu Ahola 2013-08-03 23:16:37 +03:00
parent bc5db9b027
commit 742614180c
5 changed files with 146 additions and 70 deletions

@ -934,7 +934,6 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
m_peer_id(peer_id_), m_peer_id(peer_id_),
m_inventory(NULL), m_inventory(NULL),
m_last_good_position(0,0,0), m_last_good_position(0,0,0),
m_last_good_position_age(0),
m_time_from_last_punch(0), m_time_from_last_punch(0),
m_nocheat_dig_pos(32767, 32767, 32767), m_nocheat_dig_pos(32767, 32767, 32767),
m_nocheat_dig_time(0), m_nocheat_dig_time(0),
@ -1002,7 +1001,6 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s)
m_player->setPlayerSAO(this); m_player->setPlayerSAO(this);
m_player->peer_id = m_peer_id; m_player->peer_id = m_peer_id;
m_last_good_position = m_player->getPosition(); m_last_good_position = m_player->getPosition();
m_last_good_position_age = 0.0;
} }
// Called before removing from environment // Called before removing from environment
@ -1106,6 +1104,19 @@ void PlayerSAO::step(float dtime, bool send_recommended)
m_moved = true; 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_time_from_last_punch += dtime;
m_nocheat_dig_time += 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(); v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
m_last_good_position = pos; m_last_good_position = pos;
m_last_good_position_age = 0;
m_player->setPosition(pos); 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) if(send_recommended == false)
return; return;
@ -1267,7 +1220,6 @@ void PlayerSAO::setPos(v3f pos)
m_player->setPosition(pos); m_player->setPosition(pos);
// Movement caused by this command is always valid // Movement caused by this command is always valid
m_last_good_position = pos; m_last_good_position = pos;
m_last_good_position_age = 0;
// Force position change on client // Force position change on client
m_moved = true; m_moved = true;
} }
@ -1279,7 +1231,6 @@ void PlayerSAO::moveTo(v3f pos, bool continuous)
m_player->setPosition(pos); m_player->setPosition(pos);
// Movement caused by this command is always valid // Movement caused by this command is always valid
m_last_good_position = pos; m_last_good_position = pos;
m_last_good_position_age = 0;
// Force position change on client // Force position change on client
m_moved = true; m_moved = true;
} }
@ -1503,6 +1454,59 @@ std::string PlayerSAO::getPropertyPacket()
return gob_cmd_set_properties(m_prop); 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) { bool PlayerSAO::getCollisionBox(aabb3f *toset) {
//update collision box //update collision box
*toset = m_player->getCollisionbox(); *toset = m_player->getCollisionbox();
@ -1516,3 +1520,4 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) {
bool PlayerSAO::collideWithObjects(){ bool PlayerSAO::collideWithObjects(){
return true; return true;
} }

@ -122,6 +122,36 @@ private:
PlayerSAO needs some internals exposed. 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 class PlayerSAO : public ServerActiveObject
{ {
public: public:
@ -228,6 +258,11 @@ public:
{ {
m_nocheat_dig_pos = v3s16(32767, 32767, 32767); m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
} }
LagPool& getDigPool()
{
return m_dig_pool;
}
void checkMovementCheat();
// Other // Other
@ -249,8 +284,9 @@ private:
Inventory *m_inventory; Inventory *m_inventory;
// Cheat prevention // Cheat prevention
LagPool m_dig_pool;
LagPool m_move_pool;
v3f m_last_good_position; v3f m_last_good_position;
float m_last_good_position_age;
float m_time_from_last_punch; float m_time_from_last_punch;
v3s16 m_nocheat_dig_pos; v3s16 m_nocheat_dig_pos;
float m_nocheat_dig_time; float m_nocheat_dig_time;

@ -332,7 +332,8 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, ScriptApi *scriptIface,
m_active_block_interval_overload_skip(0), m_active_block_interval_overload_skip(0),
m_game_time(0), m_game_time(0),
m_game_time_fraction_counter(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)
{ {
} }

@ -304,6 +304,9 @@ public:
bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0); bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0);
u32 getGameTime() { return m_game_time; } u32 getGameTime() { return m_game_time; }
void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
float getMaxLagEstimate() { return m_max_lag_estimate; }
private: private:
/* /*
@ -378,6 +381,9 @@ private:
std::list<ABMWithState> m_abms; std::list<ABMWithState> m_abms;
// An interval for generally sending object positions and stuff // An interval for generally sending object positions and stuff
float m_recommended_send_interval; 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 #ifndef SERVER

@ -1090,6 +1090,16 @@ void Server::AsyncRunStep()
{ {
JMutexAutoLock lock(m_env_mutex); 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 // Step environment
ScopeProfiler sp(g_profiler, "SEnv step"); ScopeProfiler sp(g_profiler, "SEnv step");
ScopeProfiler sp2(g_profiler, "SEnv step avg", SPT_AVG); 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.LMB = (bool)(keyPressed&128);
player->control.RMB = (bool)(keyPressed&256); player->control.RMB = (bool)(keyPressed&256);
playersao->checkMovementCheat();
/*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to " /*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
<<"("<<position.X<<","<<position.Y<<","<<position.Z<<")" <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
<<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/ <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
@ -2953,13 +2965,27 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
<<std::endl; <<std::endl;
is_valid_dig = false; is_valid_dig = false;
} }
// If time is considerably too short, ignore dig // Check digging time
// Check time only for medium and slow timed digs // If already invalidated, we don't have to
if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){ 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() infostream<<"Server: NoCheat: "<<player->getName()
<<" completed digging " <<" completed digging "<<PP(p_under)
<<PP(p_under)<<" in "<<nocheat_t<<"s; expected " <<"too fast; not digging."<<std::endl;
<<params.time<<"s; not digging."<<std::endl;
is_valid_dig = false; is_valid_dig = false;
} }
} }
@ -4617,6 +4643,8 @@ std::wstring Server::getStatusString()
os<<L"version="<<narrow_to_wide(VERSION_STRING); os<<L"version="<<narrow_to_wide(VERSION_STRING);
// Uptime // Uptime
os<<L", uptime="<<m_uptime.get(); os<<L", uptime="<<m_uptime.get();
// Max lag estimate
os<<L", max_lag="<<m_env->getMaxLagEstimate();
// Information about clients // Information about clients
std::map<u16, RemoteClient*>::iterator i; std::map<u16, RemoteClient*>::iterator i;
bool first; bool first;