mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
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:
parent
b015f4802a
commit
9ac3296b04
5 changed files with 64 additions and 22 deletions
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue