From d553c46ba51f49383af80772a31e76718a7be253 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 21 Sep 2015 16:49:44 +0200 Subject: [PATCH] Implemented a more robust DoS mechanism for commands. A large number of concurrent players can now send data to the server without general slowdown, the prize to pay for not overwhelming the server is that when load is high you will occationally get an error and asked to enter your command again. --- evennia/server/portal/portalsessionhandler.py | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index cce4ccfc0..00ede7adf 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -51,6 +51,7 @@ class PortalSessionHandler(SessionHandler): self.connection_task = None self.command_counter = 0 self.command_counter_reset = time() + self.command_overflow = False def at_server_connection(self): """ @@ -110,7 +111,7 @@ class PortalSessionHandler(SessionHandler): self.sessions[session.sessid] = session session.server_connected = True #print "connecting", session.sessid, " number:", len(self.sessions) - self.portal.amp_protocol.call_remote_ServerAdmin(session.sessid, + self.portal.amp_protocol.send_AdminPortal2Server(session.sessid, operation=PCONN, data=sessdata) @@ -139,7 +140,7 @@ class PortalSessionHandler(SessionHandler): "conn_time", "protocol_flags", "server_data",)) - self.portal.amp_protocol.call_remote_ServerAdmin(session.sessid, + self.portal.amp_protocol.send_AdminPortal2Server(session.sessid, operation=PCONNSYNC, data=sessdata) @@ -159,7 +160,7 @@ class PortalSessionHandler(SessionHandler): _CONNECTION_QUEUE.remove(session) return sessid = session.sessid - self.portal.amp_protocol.call_remote_ServerAdmin(sessid, + self.portal.amp_protocol.send_AdminPortal2Server(sessid, operation=PDISCONN) def server_connect(self, protocol_path="", config=dict()): @@ -380,25 +381,34 @@ class PortalSessionHandler(SessionHandler): Data is serialized before passed on. """ - from evennia.server.profiling.timetrace import timetrace - text = timetrace(text, "portalsessionhandler.data_out") - now = time() - # data throttle (anti DoS measure) - self.command_counter += 1 - if self.command_counter > _MAX_COMMAND_RATE: - self.command_counter = 0 - if now - session.cmd_last < 1.0: - session.cmd_last = now - #reactor.callLater(_MIN_TIME_BETWEEN_COMMANDS, - # self.data_in(session, text=text, **kwargs)) + #from evennia.server.profiling.timetrace import timetrace + #text = timetrace(text, "portalsessionhandler.data_in") + + if session: + if self.command_counter > _MAX_COMMAND_RATE: + # data throttle (anti DoS measure) + now = time() + dT = now - self.command_counter_reset + print " command rate:", _MAX_COMMAND_RATE / dT, dT, self.command_counter + self.command_counter = 0 + self.command_counter_reset = now + self.command_overflow = dT < 1.0 + if self.command_overflow: + reactor.callLater(1.0, self.data_in, None) + if self.command_overflow: self.data_out(session.sessid, text=_ERROR_COMMAND_OVERFLOW) return - session.cmd_last = now + # relay data to Server + self.command_counter += 1 + self.portal.amp_protocol.send_MsgPortal2Server(session.sessid, + msg=text, + data=kwargs) + else: + # called by the callLater callback + if self.command_overflow: + self.command_overflow = False + reactor.callLater(1.0, self.data_in, None) - # relay data to Server - self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, - msg=text, - data=kwargs) def data_out(self, sessid, text=None, **kwargs): """ @@ -414,8 +424,9 @@ class PortalSessionHandler(SessionHandler): kwargs (any): Other data from protocol. """ - from evennia.server.profiling.timetrace import timetrace - text = timetrace(text, "portalsessionhandler.data_out") + #from evennia.server.profiling.timetrace import timetrace + #text = timetrace(text, "portalsessionhandler.data_out") + session = self.sessions.get(sessid, None) if session: # convert oob to the generic format