Made portalsessionhandler manage command rate limitations directly, using a cmd/s average over 200 commands.

This commit is contained in:
Griatch 2015-02-28 15:37:20 +01:00
parent e201cda2c3
commit 9793c57dd4
3 changed files with 40 additions and 24 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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)