diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index f686fe1d67..c3d6b3f18d 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -1,14 +1,18 @@ """ Sessionhandler for portal sessions """ -import time -from twisted.internet import reactor +from collections import deque +from time import time +from twisted.internet import reactor, task from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC _CONNECTION_RATE = 5.0 _MIN_TIME_BETWEEN_CONNECTS = 1.0 / _CONNECTION_RATE _MOD_IMPORT = None +_MAX_CMD_RATE = 150.0 +_ERROR_COMMAND_OVERFLOW = "You entered commands too fast. Wait a moment and try again." + #------------------------------------------------------------ # Portal-SessionHandler class #------------------------------------------------------------ @@ -31,9 +35,17 @@ class PortalSessionHandler(SessionHandler): self.portal = None self.sessions = {} self.latest_sessid = 0 - self.uptime = time.time() + self.uptime = time() self.connection_time = 0 - self.time_last_connect = time.time() + self.time_last_connect = time() + self.cmd_last = time() + self.cmd_rate_history = deque([], 200) + self.cmd_rate = 0.0 + self.overflows = 0 + task.LoopingCall(self._report, interval=30) + + def _report(self): + print " INFO: cmd rate: %s, overflows: %s" % (sum(self.cmd_rate_history) / 200.0, self.overflows) def at_server_connection(self): """ @@ -41,7 +53,7 @@ class PortalSessionHandler(SessionHandler): Server. At this point, the AMP connection is already established. """ - self.connection_time = time.time() + self.connection_time = time() def connect(self, session): """ @@ -61,7 +73,7 @@ class PortalSessionHandler(SessionHandler): self.latest_sessid += 1 session.sessid = self.latest_sessid - now = time.time() + now = time() current_rate = 1.0 / (now - self.time_last_connect) if current_rate > _CONNECTION_RATE: @@ -290,6 +302,28 @@ class PortalSessionHandler(SessionHandler): in from the protocol to the server. data is serialized before passed on. """ + + now = time() + self.cmd_rate_history.append(1.0 / (now - self.cmd_last)) + + if session.logged_in: + # command flood protection + self.cmd_rate = sum(self.cmd_rate_history) / 200.0 + if self.cmd_rate > _MAX_CMD_RATE: + max_rate_per_session = _MAX_CMD_RATE / len(self.sessions) + session_rate = 1.0 / (now - session.cmd_last) + session.cmd_last = now + if session_rate > max_rate_per_session: + self.overflows += 1 + session.data_out(text=_ERROR_COMMAND_OVERFLOW) + if 100 % self.overflows == 0: + print "CMD OVERFLOW %s: %s (%s/%s, %s/%s)" % (session.sessid, text, + self.cmd_rate, _MAX_CMD_RATE, + session_rate, max_rate_per_session) + return + else: + session.cmd_last = now + self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, msg=text, data=kwargs) diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index b150b4a522..9261ccbb1d 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -48,7 +48,6 @@ class ServerSession(Session): self.player = None self.cmdset_storage_string = "" self.cmdset = CmdSetHandler(self, True) - self.cmd_per_second = 0.0 def __cmdset_storage_get(self): return [path.strip() for path in self.cmdset_storage_string.split(',')] @@ -200,10 +199,6 @@ class ServerSession(Session): oobhandler at this point. """ - now = time() - self.cmd_per_second = 1.0 / (now - self.cmd_last) - self.cmd_last = now - #explicitly check for None since text can be an empty string, which is #also valid if text is not None: diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 116402e189..09e3bb2576 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -136,8 +136,6 @@ class ServerSessionHandler(SessionHandler): self.sessions = {} self.server = None self.server_data = {"servername": _SERVERNAME} - self.cmd_last = time() - self.cmd_per_second = 0.0 def portal_connect(self, portalsession): """ @@ -500,17 +498,6 @@ class ServerSessionHandler(SessionHandler): """ session = self.sessions.get(sessid, None) if session: - - now = time() - self.cmd_per_second = 1.0 / (now - self.cmd_last) - self.cmd_last = now - - if self.cmd_per_second > _MAX_SERVER_COMMANDS_PER_SECOND: - if session.cmd_per_second > _MAX_SESSION_COMMANDS_PER_SECOND: - session.data.out(text=_ERROR_COMMAND_OVERFLOW) - logger.log_infomsg("overflow kicked in for session %s: %s" % (session.sessid, text)) - return - text = text and to_unicode(strip_control_sequences(text), encoding=session.encoding) if "oob" in kwargs: # incoming data is always on the form (cmdname, args, kwargs)