Introduced a trottling mechanism in the portalsessionhandler, limiting the number of new connections per second. Also fixed a bug in deleting a puppeted object.

This commit is contained in:
Griatch 2015-02-24 23:11:04 +01:00
parent b015f4802a
commit 9ac3296b04
5 changed files with 64 additions and 22 deletions

View file

@ -57,6 +57,7 @@ class SessidHandler(object):
def get(self): def get(self):
"Returns a list of one or more session ids" "Returns a list of one or more session ids"
return self._cache return self._cache
all = get # alias
def add(self, sessid): def add(self, sessid):
"Add sessid to handler" "Add sessid to handler"
@ -812,7 +813,8 @@ class DefaultObject(ObjectDB):
# no need to disconnect, Player just jumps to OOC mode. # no need to disconnect, Player just jumps to OOC mode.
# sever the connection (important!) # sever the connection (important!)
if self.player: if self.player:
self.player.character = None for sessid in self.sessid.all():
self.player.unpuppet_object(sessid)
self.player = None self.player = None
for script in _ScriptDB.objects.get_all_scripts_on_obj(self): for script in _ScriptDB.objects.get_all_scripts_on_obj(self):

View file

@ -2,8 +2,11 @@
Sessionhandler for portal sessions Sessionhandler for portal sessions
""" """
import time import time
from twisted.internet import reactor
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC
_CONNECTION_RATE = 5.0
_MIN_TIME_BETWEEN_CONNECTS = 1.0 / _CONNECTION_RATE
_MOD_IMPORT = None _MOD_IMPORT = None
#------------------------------------------------------------ #------------------------------------------------------------
@ -30,6 +33,7 @@ class PortalSessionHandler(SessionHandler):
self.latest_sessid = 0 self.latest_sessid = 0
self.uptime = time.time() self.uptime = time.time()
self.connection_time = 0 self.connection_time = 0
self.time_last_connect = time.time()
def at_server_connection(self): def at_server_connection(self):
""" """
@ -43,25 +47,56 @@ class PortalSessionHandler(SessionHandler):
""" """
Called by protocol at first connect. This adds a not-yet Called by protocol at first connect. This adds a not-yet
authenticated session using an ever-increasing counter for sessid. authenticated session using an ever-increasing counter for sessid.
We implement a throttling mechanism here to limit the speed at which
new connections are accepted - this is both a stop against DoS attacks
as well as helps using the Dummyrunner tester with a large number of
connector dummies.
""" """
self.latest_sessid += 1 session.server_connected = False
sessid = self.latest_sessid
session.sessid = sessid if not session.sessid:
sessdata = session.get_sync_data() # only assign if we were not delayed
self.sessions[sessid] = session self.latest_sessid += 1
session.sessid = self.latest_sessid
now = time.time()
current_rate = 1.0 / (now - self.time_last_connect)
if current_rate > _CONNECTION_RATE:
# we have too many connections per second. Delay.
#print " delaying connecting", session.sessid
reactor.callLater(_MIN_TIME_BETWEEN_CONNECTS, self.connect, session)
return
if not self.portal.amp_protocol:
# if amp is not yet ready (usually because the server is
# booting up), try again a little later
reactor.CallLater(0.5, self.connect, session)
return
# sync with server-side # sync with server-side
if self.portal.amp_protocol: # this is a timing issue
self.portal.amp_protocol.call_remote_ServerAdmin(sessid, self.time_last_connect = now
sessdata = session.get_sync_data()
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,
operation=PCONN, operation=PCONN,
data=sessdata) data=sessdata)
def sync(self, session): def sync(self, session):
""" """
Called by the protocol of an already connected session. This Called by the protocol of an already connected session. This
can be used to sync the session info in a delayed manner, can be used to sync the session info in a delayed manner,
such as when negotiation and handshakes are delayed. such as when negotiation and handshakes are delayed.
""" """
if session.sessid: if session.sessid and session.server_connected:
# only use if session already has sessid (i.e. has already connected) # only use if session already has sessid and has already connected
# once to the server - if so we must re-sync woth the server, otherwise
# we skip this step.
sessdata = session.get_sync_data() sessdata = session.get_sync_data()
if self.portal.amp_protocol: if self.portal.amp_protocol:
# we only send sessdata that should not have changed # we only send sessdata that should not have changed

View file

@ -101,6 +101,9 @@ ERROR_NO_MIXIN = \
reason, you can change their values manually after the import, or reason, you can change their values manually after the import, or
add DUMMYRUNNER_MIXIN=True to your settings file to avoid this add DUMMYRUNNER_MIXIN=True to your settings file to avoid this
error completely. error completely.
Warning: Don't run dummyrunner on a production database! It will
create a lot of spammy objects and player accounts!
""" """
@ -200,6 +203,7 @@ class DummyClient(telnet.StatefulTelnetProtocol):
self.exits = [] # exit names created self.exits = [] # exit names created
self.objs = [] # obj names created self.objs = [] # obj names created
self._connected = False
self._loggedin = False self._loggedin = False
self._logging_out = False self._logging_out = False
self._report = "" self._report = ""
@ -210,16 +214,17 @@ class DummyClient(telnet.StatefulTelnetProtocol):
reactor.addSystemEventTrigger('before', 'shutdown', self.logout) reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
# start client tick
d = LoopingCall(self.step)
# dissipate exact step by up to +/- 0.5 second
timestep = TIMESTEP + (-0.5 + (random.random()*1.0))
d.start(timestep, now=True).addErrback(self.error)
def dataReceived(self, data): def dataReceived(self, data):
"Echo incoming data to stdout" "Wait to start stepping until the server actually responds"
pass if not self._connected and not data.startswith(chr(255)):
# wait until we actually get text back (not just telnet
# negotiation)
self._connected = True
# start client tick
d = LoopingCall(self.step)
# dissipate exact step by up to +/- 0.5 second
timestep = TIMESTEP + (-0.5 + (random.random()*1.0))
d.start(timestep, now=True).addErrback(self.error)
def connectionLost(self, reason): def connectionLost(self, reason):
"loosing the connection" "loosing the connection"

View file

@ -58,12 +58,12 @@ TIMESTEP = 2
# Chance of a dummy actually performing an action on a given tick. # Chance of a dummy actually performing an action on a given tick.
# This spreads out usage randomly, like it would be in reality. # This spreads out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.20 CHANCE_OF_ACTION = 0.5
# Chance of a currently unlogged-in dummy performing its login # Chance of a currently unlogged-in dummy performing its login
# action every tick. This emulates not all players logging in # action every tick. This emulates not all players logging in
# at exactly the same time. # at exactly the same time.
CHANCE_OF_LOGIN = 0.10 CHANCE_OF_LOGIN = 1.0#0.5
# Which telnet port to connect to. If set to None, uses the first # Which telnet port to connect to. If set to None, uses the first
# default telnet port of the running server. # default telnet port of the running server.

View file

@ -166,7 +166,7 @@ class ServerSessionHandler(SessionHandler):
if session: if session:
# since some of the session properties may have had # since some of the session properties may have had
# a chance to change already before the portal gets here # a chance to change already before the portal gets here
# the portal doesn't send all sessiondata here, but only # the portal doesn't send all sessiondata but only
# ones which should only be changed from portal (like # ones which should only be changed from portal (like
# protocol_flags etc) # protocol_flags etc)
session.load_sync_data(portalsessiondata) session.load_sync_data(portalsessiondata)