From e6687be4933e5115d31ade014300648051af5047 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sun, 4 Aug 2013 08:17:07 +0300 Subject: [PATCH] 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 --- src/connection.cpp | 16 +- src/connection.h | 2 +- src/server.cpp | 363 +++++++++++++++++++++++++-------------------- src/server.h | 16 +- 4 files changed, 229 insertions(+), 168 deletions(-) diff --git a/src/connection.cpp b/src/connection.cpp index dd7ff597b..42262846f 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -207,12 +207,13 @@ RPBSearchResult ReliablePacketBuffer::notFound() { return m_list.end(); } -u16 ReliablePacketBuffer::getFirstSeqnum() +bool ReliablePacketBuffer::getFirstSeqnum(u16 *result) { if(empty()) - throw NotFoundException("Buffer is empty"); + return false; 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() { @@ -700,7 +701,7 @@ void Connection::receive() bool single_wait_done = false; - for(;;) + for(u32 loop_i=0; loop_i<1000; loop_i++) // Limit in case of DoS { try{ /* Check if some buffer has relevant data */ @@ -1245,17 +1246,16 @@ bool Connection::checkIncomingBuffers(Channel *channel, u16 &peer_id, { u16 firstseqnum = 0; // Clear old packets from start of buffer - try{ for(;;){ - firstseqnum = channel->incoming_reliables.getFirstSeqnum(); + bool found = channel->incoming_reliables.getFirstSeqnum(&firstseqnum); + if(!found) + break; if(seqnum_higher(channel->next_incoming_seqnum, firstseqnum)) channel->incoming_reliables.popFirst(); else break; } // This happens if all packets are old - }catch(con::NotFoundException) - {} if(channel->incoming_reliables.empty() == false) { diff --git a/src/connection.h b/src/connection.h index f5cddcbf4..e68557ccd 100644 --- a/src/connection.h +++ b/src/connection.h @@ -281,7 +281,7 @@ public: u32 size(); RPBSearchResult findPacket(u16 seqnum); RPBSearchResult notFound(); - u16 getFirstSeqnum(); + bool getFirstSeqnum(u16 *result); BufferedPacket popFirst(); BufferedPacket popSeqnum(u16 seqnum); void insert(BufferedPacket &p); diff --git a/src/server.cpp b/src/server.cpp index 7527f172c..4099d9997 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -60,6 +60,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "defaultsettings.h" +class ClientNotFoundException : public BaseException +{ +public: + ClientNotFoundException(const char *s): + BaseException(s) + {} +}; + void * ServerThread::Thread() { ThreadStarted(); @@ -90,6 +98,9 @@ void * ServerThread::Thread() { infostream<<"Server: PeerNotFoundException"<setAsyncFatalError(e.what()); @@ -1766,8 +1777,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) < 1){ + infostream<<"Server: Not allowing another client to connect in" + <<" simple singleplayer mode"<= g_settings->getU16("max_users") && + !checkPriv(playername, "server") && + !checkPriv(playername, "ban") && + !checkPriv(playername, "privs") && + !checkPriv(playername, "password") && + playername != g_settings->get("name")) + { + actionstream<<"Server: "<getBool("disallow_empty_password") && std::string(given_password) == ""){ - SendAccessDenied(m_con, peer_id, L"Empty passwords are " + actionstream<<"Server: "<getAuth(playername, &checkpwd, NULL); if(!has_auth){ - SendAccessDenied(m_con, peer_id, L"Not allowed to login"); + actionstream<<"Server: "< 1){ - infostream<<"Server: Not allowing another client to connect in" - <<" simple singleplayer mode"<= g_settings->getU16("max_users") && - !checkPriv(playername, "server") && - !checkPriv(playername, "ban") && - !checkPriv(playername, "privs") && - !checkPriv(playername, "password") && - playername != g_settings->get("name")) - { - actionstream<<"Server: "<denied) + continue; 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::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::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::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::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<getName()<<" "; + } + + actionstream<getName()<<" " + <<(reason==CDR_TIMEOUT?"times out.":"leaves game.") + <<" List of players: "<::iterator n; n = m_clients.find(peer_id); - // A client should exist for all peers - assert(n != m_clients.end()); + // 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 NULL; return n->second; } @@ -5249,113 +5404,7 @@ void Server::handlePeerChange(PeerChange &c) Delete */ - // Error check - std::map::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::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::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::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<getName()<<" "; - } - - actionstream<getName()<<" " - <<(c.timeout?"times out.":"leaves game.") - <<" List of players: " - < m_clients; u16 m_clients_number; //for announcing masterserver - // Bann checking + // Ban checking BanManager m_banmanager; // Rollback manager (behind m_env_mutex)