Fix server getting completely choked up on even a little of DoS

* If client count is unbearable, immediately delete denied clients
* Re-prioritize the checking order of things about incoming clients
* Remove a huge CPU-wasting exception in ReliablePacketBuffer
This commit is contained in:
Perttu Ahola 2013-08-04 08:17:07 +03:00
parent 8831669505
commit e6687be493
4 changed files with 229 additions and 168 deletions

@ -207,12 +207,13 @@ RPBSearchResult ReliablePacketBuffer::notFound()
{ {
return m_list.end(); return m_list.end();
} }
u16 ReliablePacketBuffer::getFirstSeqnum() bool ReliablePacketBuffer::getFirstSeqnum(u16 *result)
{ {
if(empty()) if(empty())
throw NotFoundException("Buffer is empty"); return false;
BufferedPacket p = *m_list.begin(); BufferedPacket p = *m_list.begin();
return readU16(&p.data[BASE_HEADER_SIZE+1]); *result = readU16(&p.data[BASE_HEADER_SIZE+1]);
return true;
} }
BufferedPacket ReliablePacketBuffer::popFirst() BufferedPacket ReliablePacketBuffer::popFirst()
{ {
@ -700,7 +701,7 @@ void Connection::receive()
bool single_wait_done = false; bool single_wait_done = false;
for(;;) for(u32 loop_i=0; loop_i<1000; loop_i++) // Limit in case of DoS
{ {
try{ try{
/* Check if some buffer has relevant data */ /* Check if some buffer has relevant data */
@ -1245,17 +1246,16 @@ bool Connection::checkIncomingBuffers(Channel *channel, u16 &peer_id,
{ {
u16 firstseqnum = 0; u16 firstseqnum = 0;
// Clear old packets from start of buffer // Clear old packets from start of buffer
try{
for(;;){ for(;;){
firstseqnum = channel->incoming_reliables.getFirstSeqnum(); bool found = channel->incoming_reliables.getFirstSeqnum(&firstseqnum);
if(!found)
break;
if(seqnum_higher(channel->next_incoming_seqnum, firstseqnum)) if(seqnum_higher(channel->next_incoming_seqnum, firstseqnum))
channel->incoming_reliables.popFirst(); channel->incoming_reliables.popFirst();
else else
break; break;
} }
// This happens if all packets are old // This happens if all packets are old
}catch(con::NotFoundException)
{}
if(channel->incoming_reliables.empty() == false) if(channel->incoming_reliables.empty() == false)
{ {

@ -281,7 +281,7 @@ public:
u32 size(); u32 size();
RPBSearchResult findPacket(u16 seqnum); RPBSearchResult findPacket(u16 seqnum);
RPBSearchResult notFound(); RPBSearchResult notFound();
u16 getFirstSeqnum(); bool getFirstSeqnum(u16 *result);
BufferedPacket popFirst(); BufferedPacket popFirst();
BufferedPacket popSeqnum(u16 seqnum); BufferedPacket popSeqnum(u16 seqnum);
void insert(BufferedPacket &p); void insert(BufferedPacket &p);

@ -60,6 +60,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/serialize.h" #include "util/serialize.h"
#include "defaultsettings.h" #include "defaultsettings.h"
class ClientNotFoundException : public BaseException
{
public:
ClientNotFoundException(const char *s):
BaseException(s)
{}
};
void * ServerThread::Thread() void * ServerThread::Thread()
{ {
ThreadStarted(); ThreadStarted();
@ -90,6 +98,9 @@ void * ServerThread::Thread()
{ {
infostream<<"Server: PeerNotFoundException"<<std::endl; infostream<<"Server: PeerNotFoundException"<<std::endl;
} }
catch(ClientNotFoundException &e)
{
}
catch(con::ConnectionBindFailed &e) catch(con::ConnectionBindFailed &e)
{ {
m_server->setAsyncFatalError(e.what()); m_server->setAsyncFatalError(e.what());
@ -1766,8 +1777,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
<<addr_s<<"; banned name was " <<addr_s<<"; banned name was "
<<m_banmanager.getBanName(addr_s)<<std::endl; <<m_banmanager.getBanName(addr_s)<<std::endl;
// This actually doesn't seem to transfer to the client // This actually doesn't seem to transfer to the client
SendAccessDenied(m_con, peer_id, DenyAccess(peer_id, L"Your ip is banned. Banned name was "
L"Your ip is banned. Banned name was "
+narrow_to_wide(m_banmanager.getBanName(addr_s))); +narrow_to_wide(m_banmanager.getBanName(addr_s)));
m_con.DeletePeer(peer_id); m_con.DeletePeer(peer_id);
return; return;
@ -1803,6 +1813,15 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
verbosestream<<"Server: Got TOSERVER_INIT from " verbosestream<<"Server: Got TOSERVER_INIT from "
<<peer_id<<std::endl; <<peer_id<<std::endl;
// Do not allow multiple players in simple singleplayer mode.
// This isn't a perfect way to do it, but will suffice for now.
if(m_simple_singleplayer_mode && m_clients.size() > 1){
infostream<<"Server: Not allowing another client to connect in"
<<" simple singleplayer mode"<<std::endl;
DenyAccess(peer_id, L"Running in simple singleplayer mode.");
return;
}
// First byte after command is maximum supported // First byte after command is maximum supported
// serialization version // serialization version
u8 client_max = data[2]; u8 client_max = data[2];
@ -1823,7 +1842,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
infostream<<"Server: Cannot negotiate " infostream<<"Server: Cannot negotiate "
"serialization version with peer " "serialization version with peer "
<<peer_id<<std::endl; <<peer_id<<std::endl;
SendAccessDenied(m_con, peer_id, std::wstring( DenyAccess(peer_id, std::wstring(
L"Your client's version is not supported.\n" L"Your client's version is not supported.\n"
L"Server version is ") L"Server version is ")
+ narrow_to_wide(VERSION_STRING) + L"." + narrow_to_wide(VERSION_STRING) + L"."
@ -1871,7 +1890,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
actionstream<<"Server: A mismatched client tried to connect from "<<addr_s actionstream<<"Server: A mismatched client tried to connect from "<<addr_s
<<std::endl; <<std::endl;
SendAccessDenied(m_con, peer_id, std::wstring( DenyAccess(peer_id, std::wstring(
L"Your client's version is not supported.\n" L"Your client's version is not supported.\n"
L"Server version is ") L"Server version is ")
+ narrow_to_wide(VERSION_STRING) + L",\n" + narrow_to_wide(VERSION_STRING) + L",\n"
@ -1893,7 +1912,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
actionstream<<"Server: A mismatched (strict) client tried to " actionstream<<"Server: A mismatched (strict) client tried to "
<<"connect from "<<addr_s<<std::endl; <<"connect from "<<addr_s<<std::endl;
SendAccessDenied(m_con, peer_id, std::wstring( DenyAccess(peer_id, std::wstring(
L"Your client's version is not supported.\n" L"Your client's version is not supported.\n"
L"Server version is ") L"Server version is ")
+ narrow_to_wide(VERSION_STRING) + L",\n" + narrow_to_wide(VERSION_STRING) + L",\n"
@ -1924,8 +1943,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
actionstream<<"Server: Player with an empty name " actionstream<<"Server: Player with an empty name "
<<"tried to connect from "<<addr_s<<std::endl; <<"tried to connect from "<<addr_s<<std::endl;
SendAccessDenied(m_con, peer_id, DenyAccess(peer_id, L"Empty name");
L"Empty name");
return; return;
} }
@ -1933,8 +1951,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
actionstream<<"Server: Player with an invalid name " actionstream<<"Server: Player with an invalid name "
<<"tried to connect from "<<addr_s<<std::endl; <<"tried to connect from "<<addr_s<<std::endl;
SendAccessDenied(m_con, peer_id, DenyAccess(peer_id, L"Name contains unallowed characters");
L"Name contains unallowed characters");
return; return;
} }
@ -1942,8 +1959,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
actionstream<<"Server: Player with an invalid name " actionstream<<"Server: Player with an invalid name "
<<"tried to connect from "<<addr_s<<std::endl; <<"tried to connect from "<<addr_s<<std::endl;
SendAccessDenied(m_con, peer_id, DenyAccess(peer_id, L"Name is not allowed");
L"Name is not allowed");
return; return;
} }
@ -1967,9 +1983,25 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
} }
if(!base64_is_valid(given_password)){ if(!base64_is_valid(given_password)){
infostream<<"Server: "<<playername actionstream<<"Server: "<<playername
<<" supplied invalid password hash"<<std::endl; <<" supplied invalid password hash"<<std::endl;
SendAccessDenied(m_con, peer_id, L"Invalid password hash"); DenyAccess(peer_id, L"Invalid password hash");
return;
}
// Enforce user limit.
// Don't enforce for users that have some admin right
if(m_clients.size() >= g_settings->getU16("max_users") &&
!checkPriv(playername, "server") &&
!checkPriv(playername, "ban") &&
!checkPriv(playername, "privs") &&
!checkPriv(playername, "password") &&
playername != g_settings->get("name"))
{
actionstream<<"Server: "<<playername<<" tried to join, but there"
<<" are already max_users="
<<g_settings->getU16("max_users")<<" players."<<std::endl;
DenyAccess(peer_id, L"Too many users.");
return; return;
} }
@ -1981,7 +2013,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
if(!isSingleplayer() && if(!isSingleplayer() &&
g_settings->getBool("disallow_empty_password") && g_settings->getBool("disallow_empty_password") &&
std::string(given_password) == ""){ std::string(given_password) == ""){
SendAccessDenied(m_con, peer_id, L"Empty passwords are " actionstream<<"Server: "<<playername
<<" supplied empty password"<<std::endl;
DenyAccess(peer_id, L"Empty passwords are "
L"disallowed. Set a password and try again."); L"disallowed. Set a password and try again.");
return; return;
} }
@ -2000,41 +2034,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
has_auth = m_script->getAuth(playername, &checkpwd, NULL); has_auth = m_script->getAuth(playername, &checkpwd, NULL);
if(!has_auth){ if(!has_auth){
SendAccessDenied(m_con, peer_id, L"Not allowed to login"); actionstream<<"Server: "<<playername<<" cannot be authenticated"
<<" (auth handler does not work?)"<<std::endl;
DenyAccess(peer_id, L"Not allowed to login");
return; return;
} }
if(given_password != checkpwd){ if(given_password != checkpwd){
infostream<<"Server: peer_id="<<peer_id actionstream<<"Server: "<<playername<<" supplied invalid password"
<<": supplied invalid password for " <<" (peer_id="<<peer_id<<")"<<std::endl;
<<playername<<std::endl; DenyAccess(peer_id, L"Invalid password");
SendAccessDenied(m_con, peer_id, L"Invalid password");
return;
}
// Do not allow multiple players in simple singleplayer mode.
// This isn't a perfect way to do it, but will suffice for now.
if(m_simple_singleplayer_mode && m_clients.size() > 1){
infostream<<"Server: Not allowing another client to connect in"
<<" simple singleplayer mode"<<std::endl;
SendAccessDenied(m_con, peer_id,
L"Running in simple singleplayer mode.");
return;
}
// Enforce user limit.
// Don't enforce for users that have some admin right
if(m_clients.size() >= g_settings->getU16("max_users") &&
!checkPriv(playername, "server") &&
!checkPriv(playername, "ban") &&
!checkPriv(playername, "privs") &&
!checkPriv(playername, "password") &&
playername != g_settings->get("name"))
{
actionstream<<"Server: "<<playername<<" tried to join, but there"
<<" are already max_users="
<<g_settings->getU16("max_users")<<" players."<<std::endl;
SendAccessDenied(m_con, peer_id, L"Too many users.");
return; return;
} }
@ -2046,6 +2055,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
errorstream<<"Server: peer_id="<<peer_id errorstream<<"Server: peer_id="<<peer_id
<<": failed to emerge player"<<std::endl; <<": failed to emerge player"<<std::endl;
DenyAccess(peer_id, L"Could not allocate player. You"
" may need to wait for a timeout.");
return; return;
} }
@ -4232,7 +4243,11 @@ void Server::SendBlocks(float dtime)
continue; continue;
} }
RemoteClient *client = getClient(q.peer_id); RemoteClient *client = getClientNoEx(q.peer_id);
if(!client)
continue;
if(client->denied)
continue;
SendBlockNoLock(q.peer_id, block, client->serialization_version, client->net_proto_version); SendBlockNoLock(q.peer_id, block, client->serialization_version, client->net_proto_version);
@ -4618,6 +4633,139 @@ void Server::RespawnPlayer(u16 peer_id)
} }
} }
void Server::DenyAccess(u16 peer_id, const std::wstring &reason)
{
DSTACK(__FUNCTION_NAME);
SendAccessDenied(m_con, peer_id, reason);
RemoteClient *client = getClientNoEx(peer_id);
if(client)
client->denied = true;
// If there are way too many clients, get rid of denied new ones immediately
if(m_clients.size() > 2 * g_settings->getU16("max_users")){
// Delete peer to stop sending it data
m_con.DeletePeer(peer_id);
// Delete client also to stop block sends and other stuff
DeleteClient(peer_id, CDR_DENY);
}
}
void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason)
{
DSTACK(__FUNCTION_NAME);
// Error check
std::map<u16, RemoteClient*>::iterator n;
n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if(n == m_clients.end())
return;
/*
Mark objects to be not known by the client
*/
RemoteClient *client = n->second;
// Handle objects
for(std::set<u16>::iterator
i = client->m_known_objects.begin();
i != client->m_known_objects.end(); ++i)
{
// Get object
u16 id = *i;
ServerActiveObject* obj = m_env->getActiveObject(id);
if(obj && obj->m_known_by_count > 0)
obj->m_known_by_count--;
}
/*
Clear references to playing sounds
*/
for(std::map<s32, ServerPlayingSound>::iterator
i = m_playing_sounds.begin();
i != m_playing_sounds.end();)
{
ServerPlayingSound &psound = i->second;
psound.clients.erase(peer_id);
if(psound.clients.size() == 0)
m_playing_sounds.erase(i++);
else
i++;
}
Player *player = m_env->getPlayer(peer_id);
// Collect information about leaving in chat
std::wstring message;
{
if(player != NULL && reason != CDR_DENY)
{
std::wstring name = narrow_to_wide(player->getName());
message += L"*** ";
message += name;
message += L" left the game.";
if(reason == CDR_TIMEOUT)
message += L" (timed out)";
}
}
/* Run scripts and remove from environment */
{
if(player != NULL)
{
PlayerSAO *playersao = player->getPlayerSAO();
assert(playersao);
m_script->on_leaveplayer(playersao);
playersao->disconnected();
}
}
/*
Print out action
*/
{
if(player != NULL && reason != CDR_DENY)
{
std::ostringstream os(std::ios_base::binary);
for(std::map<u16, RemoteClient*>::iterator
i = m_clients.begin();
i != m_clients.end(); ++i)
{
RemoteClient *client = i->second;
assert(client->peer_id == i->first);
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
// Get player
Player *player = m_env->getPlayer(client->peer_id);
if(!player)
continue;
// Get name of player
os<<player->getName()<<" ";
}
actionstream<<player->getName()<<" "
<<(reason==CDR_TIMEOUT?"times out.":"leaves game.")
<<" List of players: "<<os.str()<<std::endl;
}
}
// Delete client
delete m_clients[peer_id];
m_clients.erase(peer_id);
// Send player info to all remaining clients
//SendPlayerInfos();
// Send leave chat message to all remaining clients
if(message.length() != 0)
BroadcastChatMessage(message);
}
void Server::UpdateCrafting(u16 peer_id) void Server::UpdateCrafting(u16 peer_id)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -4638,12 +4786,19 @@ void Server::UpdateCrafting(u16 peer_id)
RemoteClient* Server::getClient(u16 peer_id) RemoteClient* Server::getClient(u16 peer_id)
{ {
DSTACK(__FUNCTION_NAME); RemoteClient *client = getClientNoEx(peer_id);
//JMutexAutoLock lock(m_con_mutex); if(!client)
throw ClientNotFoundException("Client not found");
return client;
}
RemoteClient* Server::getClientNoEx(u16 peer_id)
{
std::map<u16, RemoteClient*>::iterator n; std::map<u16, RemoteClient*>::iterator n;
n = m_clients.find(peer_id); n = m_clients.find(peer_id);
// A client should exist for all peers // The client may not exist; clients are immediately removed if their
assert(n != m_clients.end()); // access is denied, and this event occurs later then.
if(n == m_clients.end())
return NULL;
return n->second; return n->second;
} }
@ -5249,113 +5404,7 @@ void Server::handlePeerChange(PeerChange &c)
Delete Delete
*/ */
// Error check DeleteClient(c.peer_id, c.timeout?CDR_TIMEOUT:CDR_LEAVE);
std::map<u16, RemoteClient*>::iterator n;
n = m_clients.find(c.peer_id);
// The client should exist
assert(n != m_clients.end());
/*
Mark objects to be not known by the client
*/
RemoteClient *client = n->second;
// Handle objects
for(std::set<u16>::iterator
i = client->m_known_objects.begin();
i != client->m_known_objects.end(); ++i)
{
// Get object
u16 id = *i;
ServerActiveObject* obj = m_env->getActiveObject(id);
if(obj && obj->m_known_by_count > 0)
obj->m_known_by_count--;
}
/*
Clear references to playing sounds
*/
for(std::map<s32, ServerPlayingSound>::iterator
i = m_playing_sounds.begin();
i != m_playing_sounds.end();)
{
ServerPlayingSound &psound = i->second;
psound.clients.erase(c.peer_id);
if(psound.clients.size() == 0)
m_playing_sounds.erase(i++);
else
i++;
}
Player *player = m_env->getPlayer(c.peer_id);
// Collect information about leaving in chat
std::wstring message;
{
if(player != NULL)
{
std::wstring name = narrow_to_wide(player->getName());
message += L"*** ";
message += name;
message += L" left the game.";
if(c.timeout)
message += L" (timed out)";
}
}
/* Run scripts and remove from environment */
{
if(player != NULL)
{
PlayerSAO *playersao = player->getPlayerSAO();
assert(playersao);
m_script->on_leaveplayer(playersao);
playersao->disconnected();
}
}
/*
Print out action
*/
{
if(player != NULL)
{
std::ostringstream os(std::ios_base::binary);
for(std::map<u16, RemoteClient*>::iterator
i = m_clients.begin();
i != m_clients.end(); ++i)
{
RemoteClient *client = i->second;
assert(client->peer_id == i->first);
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
// Get player
Player *player = m_env->getPlayer(client->peer_id);
if(!player)
continue;
// Get name of player
os<<player->getName()<<" ";
}
actionstream<<player->getName()<<" "
<<(c.timeout?"times out.":"leaves game.")
<<" List of players: "
<<os.str()<<std::endl;
}
}
// Delete client
delete m_clients[c.peer_id];
m_clients.erase(c.peer_id);
// Send player info to all remaining clients
//SendPlayerInfos();
// Send leave chat message to all remaining clients
if(message.length() != 0)
BroadcastChatMessage(message);
} // PEER_REMOVED } // PEER_REMOVED
else else

@ -250,6 +250,8 @@ public:
bool definitions_sent; bool definitions_sent;
bool denied;
RemoteClient(): RemoteClient():
m_time_from_building(9999), m_time_from_building(9999),
m_excess_gotblocks(0) m_excess_gotblocks(0)
@ -259,6 +261,7 @@ public:
net_proto_version = 0; net_proto_version = 0;
pending_serialization_version = SER_FMT_VER_INVALID; pending_serialization_version = SER_FMT_VER_INVALID;
definitions_sent = false; definitions_sent = false;
denied = false;
m_nearest_unsent_d = 0; m_nearest_unsent_d = 0;
m_nearest_unsent_reset_timer = 0.0; m_nearest_unsent_reset_timer = 0.0;
m_nothing_to_send_counter = 0; m_nothing_to_send_counter = 0;
@ -589,7 +592,7 @@ private:
void SendHUDChange(u16 peer_id, u32 id, HudElementStat stat, void *value); void SendHUDChange(u16 peer_id, u32 id, HudElementStat stat, void *value);
void SendHUDSetFlags(u16 peer_id, u32 flags, u32 mask); void SendHUDSetFlags(u16 peer_id, u32 flags, u32 mask);
void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value); void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value);
/* /*
Send a node removal/addition event to all clients except ignore_id. Send a node removal/addition event to all clients except ignore_id.
Additionally, if far_players!=NULL, players further away than Additionally, if far_players!=NULL, players further away than
@ -658,11 +661,20 @@ private:
void DiePlayer(u16 peer_id); void DiePlayer(u16 peer_id);
void RespawnPlayer(u16 peer_id); void RespawnPlayer(u16 peer_id);
void DenyAccess(u16 peer_id, const std::wstring &reason);
enum ClientDeletionReason {
CDR_LEAVE,
CDR_TIMEOUT,
CDR_DENY
};
void DeleteClient(u16 peer_id, ClientDeletionReason reason);
void UpdateCrafting(u16 peer_id); void UpdateCrafting(u16 peer_id);
// When called, connection mutex should be locked // When called, connection mutex should be locked
RemoteClient* getClient(u16 peer_id); RemoteClient* getClient(u16 peer_id);
RemoteClient* getClientNoEx(u16 peer_id);
// When called, environment mutex should be locked // When called, environment mutex should be locked
std::string getPlayerName(u16 peer_id) std::string getPlayerName(u16 peer_id)
@ -737,7 +749,7 @@ private:
std::map<u16, RemoteClient*> m_clients; std::map<u16, RemoteClient*> m_clients;
u16 m_clients_number; //for announcing masterserver u16 m_clients_number; //for announcing masterserver
// Bann checking // Ban checking
BanManager m_banmanager; BanManager m_banmanager;
// Rollback manager (behind m_env_mutex) // Rollback manager (behind m_env_mutex)