summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPerttu Ahola <celeron55@gmail.com>2013-08-04 08:17:07 +0300
committerPerttu Ahola <celeron55@gmail.com>2013-08-04 10:44:37 +0300
commite6687be4933e5115d31ade014300648051af5047 (patch)
tree320a1bb7ecf718f604ffe386b1d40d547c41d3e3
parent8831669505905dd9cd415711063f705d8e7ce02c (diff)
downloadminetest-e6687be4933e5115d31ade014300648051af5047.tar.gz
minetest-e6687be4933e5115d31ade014300648051af5047.tar.bz2
minetest-e6687be4933e5115d31ade014300648051af5047.zip
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
-rw-r--r--src/connection.cpp16
-rw-r--r--src/connection.h2
-rw-r--r--src/server.cpp363
-rw-r--r--src/server.h16
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"<<std::endl;
}
+ catch(ClientNotFoundException &e)
+ {
+ }
catch(con::ConnectionBindFailed &e)
{
m_server->setAsyncFatalError(e.what());
@@ -1766,8 +1777,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
<<addr_s<<"; banned name was "
<<m_banmanager.getBanName(addr_s)<<std::endl;
// This actually doesn't seem to transfer to the client
- SendAccessDenied(m_con, peer_id,
- L"Your ip is banned. Banned name was "
+ DenyAccess(peer_id, L"Your ip is banned. Banned name was "
+narrow_to_wide(m_banmanager.getBanName(addr_s)));
m_con.DeletePeer(peer_id);
return;
@@ -1803,6 +1813,15 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
verbosestream<<"Server: Got TOSERVER_INIT from "
<<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
// serialization version
u8 client_max = data[2];
@@ -1823,7 +1842,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
infostream<<"Server: Cannot negotiate "
"serialization version with peer "
<<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"Server version is ")
+ 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
<<std::endl;
- SendAccessDenied(m_con, peer_id, std::wstring(
+ DenyAccess(peer_id, std::wstring(
L"Your client's version is not supported.\n"
L"Server version is ")
+ 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 "
<<"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"Server version is ")
+ 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 "
<<"tried to connect from "<<addr_s<<std::endl;
- SendAccessDenied(m_con, peer_id,
- L"Empty name");
+ DenyAccess(peer_id, L"Empty name");
return;
}
@@ -1933,8 +1951,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{
actionstream<<"Server: Player with an invalid name "
<<"tried to connect from "<<addr_s<<std::endl;
- SendAccessDenied(m_con, peer_id,
- L"Name contains unallowed characters");
+ DenyAccess(peer_id, L"Name contains unallowed characters");
return;
}
@@ -1942,8 +1959,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{
actionstream<<"Server: Player with an invalid name "
<<"tried to connect from "<<addr_s<<std::endl;
- SendAccessDenied(m_con, peer_id,
- L"Name is not allowed");
+ DenyAccess(peer_id, L"Name is not allowed");
return;
}
@@ -1967,9 +1983,25 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
}
if(!base64_is_valid(given_password)){
- infostream<<"Server: "<<playername
+ actionstream<<"Server: "<<playername
<<" 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;
}
@@ -1981,7 +2013,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
if(!isSingleplayer() &&
g_settings->getBool("disallow_empty_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.");
return;
}
@@ -2000,41 +2034,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
has_auth = m_script->getAuth(playername, &checkpwd, NULL);
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;
}
if(given_password != checkpwd){
- infostream<<"Server: peer_id="<<peer_id
- <<": supplied invalid password for "
- <<playername<<std::endl;
- 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.");
+ actionstream<<"Server: "<<playername<<" supplied invalid password"
+ <<" (peer_id="<<peer_id<<")"<<std::endl;
+ DenyAccess(peer_id, L"Invalid password");
return;
}
@@ -2046,6 +2055,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{
errorstream<<"Server: peer_id="<<peer_id
<<": failed to emerge player"<<std::endl;
+ DenyAccess(peer_id, L"Could not allocate player. You"
+ " may need to wait for a timeout.");
return;
}
@@ -4232,7 +4243,11 @@ void Server::SendBlocks(float dtime)
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);
@@ -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)
{
DSTACK(__FUNCTION_NAME);
@@ -4638,12 +4786,19 @@ void Server::UpdateCrafting(u16 peer_id)
RemoteClient* Server::getClient(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
- //JMutexAutoLock lock(m_con_mutex);
+ RemoteClient *client = getClientNoEx(peer_id);
+ if(!client)
+ throw ClientNotFoundException("Client not found");
+ return client;
+}
+RemoteClient* Server::getClientNoEx(u16 peer_id)
+{
std::map<u16, RemoteClient*>::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<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);
+ DeleteClient(c.peer_id, c.timeout?CDR_TIMEOUT:CDR_LEAVE);
} // PEER_REMOVED
else
diff --git a/src/server.h b/src/server.h
index 4d8d0ca67..d49ecdf7b 100644
--- a/src/server.h
+++ b/src/server.h
@@ -250,6 +250,8 @@ public:
bool definitions_sent;
+ bool denied;
+
RemoteClient():
m_time_from_building(9999),
m_excess_gotblocks(0)
@@ -259,6 +261,7 @@ public:
net_proto_version = 0;
pending_serialization_version = SER_FMT_VER_INVALID;
definitions_sent = false;
+ denied = false;
m_nearest_unsent_d = 0;
m_nearest_unsent_reset_timer = 0.0;
m_nothing_to_send_counter = 0;
@@ -589,7 +592,7 @@ private:
void SendHUDChange(u16 peer_id, u32 id, HudElementStat stat, void *value);
void SendHUDSetFlags(u16 peer_id, u32 flags, u32 mask);
void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value);
-
+
/*
Send a node removal/addition event to all clients except ignore_id.
Additionally, if far_players!=NULL, players further away than
@@ -658,11 +661,20 @@ private:
void DiePlayer(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);
// When called, connection mutex should be locked
RemoteClient* getClient(u16 peer_id);
+ RemoteClient* getClientNoEx(u16 peer_id);
// When called, environment mutex should be locked
std::string getPlayerName(u16 peer_id)
@@ -737,7 +749,7 @@ private:
std::map<u16, RemoteClient*> m_clients;
u16 m_clients_number; //for announcing masterserver
- // Bann checking
+ // Ban checking
BanManager m_banmanager;
// Rollback manager (behind m_env_mutex)