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

View file

@ -2,8 +2,11 @@
Sessionhandler for portal sessions
"""
import time
from twisted.internet import reactor
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC
_CONNECTION_RATE = 5.0
_MIN_TIME_BETWEEN_CONNECTS = 1.0 / _CONNECTION_RATE
_MOD_IMPORT = None
#------------------------------------------------------------
@ -30,6 +33,7 @@ class PortalSessionHandler(SessionHandler):
self.latest_sessid = 0
self.uptime = time.time()
self.connection_time = 0
self.time_last_connect = time.time()
def at_server_connection(self):
"""
@ -43,25 +47,56 @@ class PortalSessionHandler(SessionHandler):
"""
Called by protocol at first connect. This adds a not-yet
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
sessid = self.latest_sessid
session.sessid = sessid
sessdata = session.get_sync_data()
self.sessions[sessid] = session
session.server_connected = False
if not session.sessid:
# only assign if we were not delayed
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
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,
data=sessdata)
def sync(self, session):
"""
Called by the protocol of an already connected session. This
can be used to sync the session info in a delayed manner,
such as when negotiation and handshakes are delayed.
"""
if session.sessid:
# only use if session already has sessid (i.e. has already connected)
if session.sessid and session.server_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()
if self.portal.amp_protocol:
# 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
add DUMMYRUNNER_MIXIN=True to your settings file to avoid this
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.objs = [] # obj names created
self._connected = False
self._loggedin = False
self._logging_out = False
self._report = ""
@ -210,16 +214,17 @@ class DummyClient(telnet.StatefulTelnetProtocol):
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):
"Echo incoming data to stdout"
pass
"Wait to start stepping until the server actually responds"
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):
"loosing the connection"

View file

@ -58,12 +58,12 @@ TIMESTEP = 2
# Chance of a dummy actually performing an action on a given tick.
# 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
# action every tick. This emulates not all players logging in
# 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
# default telnet port of the running server.

View file

@ -166,7 +166,7 @@ class ServerSessionHandler(SessionHandler):
if session:
# since some of the session properties may have had
# 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
# protocol_flags etc)
session.load_sync_data(portalsessiondata)