diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 08cad04122..e2b4cc46b6 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -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): diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 1c3c395a0a..5c600c79c9 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -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 diff --git a/evennia/server/profiling/dummyrunner.py b/evennia/server/profiling/dummyrunner.py index 342014018c..469747f5d1 100644 --- a/evennia/server/profiling/dummyrunner.py +++ b/evennia/server/profiling/dummyrunner.py @@ -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" diff --git a/evennia/server/profiling/dummyrunner_settings.py b/evennia/server/profiling/dummyrunner_settings.py index 7ee6914511..2a0cfe98f8 100644 --- a/evennia/server/profiling/dummyrunner_settings.py +++ b/evennia/server/profiling/dummyrunner_settings.py @@ -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. diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index ce084d758b..2634737ce0 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -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)