def test_listeners(self): self.expires = [] self.addrmap = [] clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) am.add_listener(self) now = datetime.datetime.now() + datetime.timedelta(seconds=10) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=10) line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s"' % (now.strftime( self.fmt), nowutc.strftime(self.fmt)) am.update(line) # see if our listener got an update a = am.find('www.example.com') self.assertEqual(self.addrmap, [a]) # advance time past when the expiry should have occurred clock.advance(10) # check that our listener got an expires event self.assertEqual(self.expires, ['www.example.com'])
def test_parse(self): """ Make sure it's parsing things properly. """ now = datetime.datetime.now() + datetime.timedelta(seconds=10) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=10) # we need to not-barf on extra args as per control-spec.txt line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s" FOO=bar BAR=baz' % ( now.strftime(self.fmt), nowutc.strftime(self.fmt)) am = AddrMap() am.update(line) addr = am.find('www.example.com') self.assertTrue(addr.ip == '72.30.2.43' or addr.ip.exploded == '72.30.2.43') # maybe not the most robust, should convert to # seconds-since-epoch instead? the net result of the parsing # is we've rounded to seconds... self.assertEqual(addr.expires.ctime(), nowutc.ctime()) line = 'www.example.com 72.30.2.43 "%s" "%s"' % (now.strftime( self.fmt), nowutc.strftime(self.fmt)) am.update(line) self.assertEqual(addr.expires.ctime(), nowutc.ctime()) # this will have resulted in an expiry call, which we need to # cancel to keep the reactor clean. for consistency, we use # the IReactorTime interface from AddrMap am.scheduler.getDelayedCalls()[0].cancel()
def test_expires_with_update(self): """ This test updates the expiry time and checks that we properly delay our expiry callback. """ clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) # now do an actual update to an existing Addr entry. now = datetime.datetime.now() + datetime.timedelta(seconds=10) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=10) line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s"' % (now.strftime( self.fmt), nowutc.strftime(self.fmt)) am.update(line) self.assertTrue(am.find('www.example.com')) # the update now = datetime.datetime.now() + datetime.timedelta(seconds=20) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=20) line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s"' % (now.strftime( self.fmt), nowutc.strftime(self.fmt)) am.update(line) self.assertTrue('www.example.com' in am.addr) # advance time by the old expiry value and we should still # find the entry clock.advance(10) self.assertTrue('www.example.com' in am.addr) # ...but advance past the new expiry (another 10 seconds) and # it should vanish clock.advance(10) self.assertTrue('www.example.com' not in am.addr)
def test_8596_cached_3(self): clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) line = 'example.invalid <error> "2013-04-03 08:28:52" error=yes EXPIRES="2013-04-03 06:28:52" CACHE="NO"' am.update(line) self.assertTrue('example.invalid' not in am.addr) self.assertEqual(len(clock.getDelayedCalls()), 0)
def test_8596_cached_2(self): clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) line = 'example.com 192.0.43.10 "2013-04-03 22:29:11" EXPIRES="2013-04-03 20:29:11" CACHED="NO"' am.update(line) self.assertTrue('example.com' in am.addr) self.assertEqual(len(clock.getDelayedCalls()), 1)
def test_8596_cached_1(self): clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) line = 'example.com 192.0.2.1 NEVER CACHED="YES"' am.update(line) self.assertTrue('example.com' in am.addr) self.assertEqual(len(clock.getDelayedCalls()), 0)
def test_expires_never(self): """ Test a NEVER expires line, as in what we'd get a startup for a configured address-mapping. """ clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) line = 'www.example.com 72.30.2.43 "NEVER"' am.update(line) self.assertTrue('www.example.com' in am.addr) self.assertEqual(len(clock.getDelayedCalls()), 0)
def test_expires(self): """ Test simply expiry case """ clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) now = datetime.datetime.now() + datetime.timedelta(seconds=10) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=10) line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s"' % (now.strftime(self.fmt), nowutc.strftime(self.fmt)) am.update(line) self.assertTrue('www.example.com' in am.addr) # advance time past when the expiry should have occurred clock.advance(10) self.assertTrue('www.example.com' not in am.addr)
def test_expires_old(self): """ Test something that expires before "now" """ clock = task.Clock() am = AddrMap() am.scheduler = IReactorTime(clock) now = datetime.datetime.now() + datetime.timedelta(seconds=-10) nowutc = datetime.datetime.utcnow() + datetime.timedelta(seconds=-10) line = 'www.example.com 72.30.2.43 "%s" EXPIRES="%s"' % (now.strftime(self.fmt), nowutc.strftime(self.fmt)) am.update(line) self.assertTrue('www.example.com' in am.addr) # arguably we shouldn't even have put this in the map maybe, # but the reactor needs to iterate before our expiry callback # gets called (right away) which is simulated by the # clock.advance call clock.advance(0) self.assertTrue('www.example.com' not in am.addr)
def __init__(self, protocol, bootstrap=True): self.protocol = ITorControlProtocol(protocol) # fixme could use protocol.on_disconnect to re-connect; see issue #3 # could override these to get your own Circuit/Stream subclasses # to track these things self.circuit_factory = Circuit self.stream_factory = Stream self._attacher = None """If set, provides :class:`txtorcon.interface.IStreamAttacher` to attach new streams we hear about.""" self.tor_binary = 'tor' self.circuit_listeners = [] self.stream_listeners = [] self.addrmap = AddrMap() #: keys on id (integer) self.circuits = {} #: keys on id (integer) self.streams = {} #: list of unique routers self.all_routers = set() #: keys by hexid (string) and by unique names self.routers = {} self._old_routers = {} #: keys on name, value always list (many duplicate "Unnamed" #: routers, for example) self.routers_by_name = {} #: keys by hexid (string) self.routers_by_hash = {} #: potentially-usable as entry guards, I think? (any router #: with 'Guard' flag) self.guards = {} #: from GETINFO entry-guards, our current entry guards self.entry_guards = {} #: list of entry guards we didn't parse out self.unusable_entry_guards = [] #: keys by name self.authorities = {} #: see set_attacher self._cleanup = None self._network_status_parser = MicrodescriptorParser( self._create_router) self.post_bootstrap = defer.Deferred() if bootstrap: self.protocol.post_bootstrap.addCallback(self._bootstrap) self.protocol.post_bootstrap.addErrback( self.post_bootstrap.errback)
def test_double_add_listener(self): am = AddrMap() am.add_listener(self) am.add_listener(self) self.assertEqual(1, len(am.listeners))
def __init__(self, protocol, bootstrap=True, write_state_diagram=False): self.protocol = ITorControlProtocol(protocol) ## fixme could use protocol.on_disconnect to re-connect; see issue #3 ## could override these to get your own Circuit/Stream subclasses ## to track these things self.circuit_factory = Circuit self.stream_factory = Stream self.attacher = None """If set, provides :class:`txtorcon.interface.IStreamAttacher` to attach new streams we hear about.""" self.tor_binary = 'tor' self.circuit_listeners = [] self.stream_listeners = [] self.addrmap = AddrMap() self.circuits = {} # keys on id (integer) self.streams = {} # keys on id (integer) self.routers = {} # keys by hexid (string) and by unique names self.routers_by_name = { } # keys on name, value always list (many duplicate "Unnamed" routers, for example) self.guards = { } # potentially-usable as entry guards, I think? (any router with 'Guard' flag) self.entry_guards = { } # from GETINFO entry-guards, our current entry guards self.unusable_entry_guards = [ ] # list of entry guards we didn't parse out self.authorities = {} # keys by name self.cleanup = None # see set_attacher class die(object): __name__ = 'die' # FIXME? just to ease spagetti.py:82's pain def __init__(self, msg): self.msg = msg def __call__(self, *args): raise RuntimeError(self.msg % tuple(args)) def nothing(*args): pass waiting_r = State("waiting_r") waiting_w = State("waiting_w") waiting_p = State("waiting_p") waiting_s = State("waiting_s") def ignorable_line(x): return x.strip() == '.' or x.strip( ) == 'OK' or x[:3] == 'ns/' or x.strip() == '' waiting_r.add_transition(Transition(waiting_r, ignorable_line, nothing)) waiting_r.add_transition( Transition(waiting_s, lambda x: x[:2] == 'r ', self._router_begin)) ## FIXME use better method/func than die!! waiting_r.add_transition( Transition(waiting_r, lambda x: x[:2] != 'r ', die('Expected "r " while parsing routers not "%s"'))) waiting_s.add_transition( Transition(waiting_w, lambda x: x[:2] == 's ', self._router_flags)) waiting_s.add_transition( Transition(waiting_s, lambda x: x[:2] == 'a ', self._router_address)) waiting_s.add_transition(Transition(waiting_r, ignorable_line, nothing)) waiting_s.add_transition( Transition(waiting_r, lambda x: x[:2] != 's ' and x[:2] != 'a ', die('Expected "s " while parsing routers not "%s"'))) waiting_s.add_transition( Transition(waiting_r, lambda x: x.strip() == '.', nothing)) waiting_w.add_transition( Transition(waiting_p, lambda x: x[:2] == 'w ', self._router_bandwidth)) waiting_w.add_transition(Transition(waiting_r, ignorable_line, nothing)) waiting_w.add_transition( Transition(waiting_s, lambda x: x[:2] == 'r ', self._router_begin)) # "w" lines are optional waiting_w.add_transition( Transition(waiting_r, lambda x: x[:2] != 'w ', die('Expected "w " while parsing routers not "%s"'))) waiting_w.add_transition( Transition(waiting_r, lambda x: x.strip() == '.', nothing)) waiting_p.add_transition( Transition(waiting_r, lambda x: x[:2] == 'p ', self._router_policy)) waiting_p.add_transition(Transition(waiting_r, ignorable_line, nothing)) waiting_p.add_transition( Transition(waiting_s, lambda x: x[:2] == 'r ', self._router_begin)) # "p" lines are optional waiting_p.add_transition( Transition(waiting_r, lambda x: x[:2] != 'p ', die('Expected "p " while parsing routers not "%s"'))) waiting_p.add_transition( Transition(waiting_r, lambda x: x.strip() == '.', nothing)) self._network_status_parser = FSM( [waiting_r, waiting_s, waiting_w, waiting_p]) if write_state_diagram: with open('routerfsm.dot', 'w') as fsmfile: fsmfile.write(self._network_status_parser.dotty()) self.post_bootstrap = defer.Deferred() if bootstrap: if self.protocol.post_bootstrap: self.protocol.post_bootstrap.addCallback( self._bootstrap).addErrback(self.post_bootstrap.errback) else: self._bootstrap()