def test_create_ipaddr_fail(self, ipaddr): def foo(blam): raise ValueError('testing') ipaddr.ip_address.side_effect = foo ip = maybe_ip_addr('1.2.3.4') self.assertTrue(isinstance(ip, type('1.2.3.4')))
def update(self, *args): """ deals with an update from Tor; see parsing logic in torcontroller """ gmtexpires = None (name, ip, expires) = args[:3] for arg in args: if arg.lower().startswith('expires='): gmtexpires = arg[8:] if gmtexpires is None: if len(args) == 3: gmtexpires = expires elif len(args) > 3: if args[2] == 'NEVER': gmtexpires = args[2] else: gmtexpires = args[3] self.name = name # "www.example.com" self.ip = maybe_ip_addr(ip) # IPV4Address instance, or string if self.ip == '<error>': self._expire() return fmt = "%Y-%m-%d %H:%M:%S" ## if we already have expiry times, etc then we want to ## properly delay our timeout oldexpires = self.expires key = 'EXPIRES=' if gmtexpires.find(key) == 0: gmtexpires = gmtexpires[len(key):] if gmtexpires == 'NEVER': ## FIXME can I just select a date 100 years in the future instead? self.expires = None else: self.expires = datetime.datetime.strptime(gmtexpires, fmt) self.created = datetime.datetime.utcnow() if self.expires is not None: if oldexpires is None: if self.expires <= self.created: diff = datetime.timedelta(seconds=0) else: diff = self.expires - self.created self.expiry = self.map.scheduler.callLater(diff.seconds, self._expire) else: diff = self.expires - oldexpires self.expiry.delay(diff.seconds)
def test_ipv6_source(self): listener = Listener( [ ('new', {'source_addr': maybe_ip_addr('::1'), 'source_port': 12345}) ] ) stream = Stream(self) stream.listen(listener) stream.update("1234 NEW 0 127.0.0.1:80 SOURCE_ADDR=::1:12345 PURPOSE=USER".split())
def test_ipv6_source(self): listener = Listener([('new', { 'source_addr': maybe_ip_addr('::1'), 'source_port': 12345 })]) stream = Stream(self) stream.listen(listener) stream.update( "1234 NEW 0 127.0.0.1:80 SOURCE_ADDR=::1:12345 PURPOSE=USER".split( ))
def test_listener_fail(self): listener = Listener( [ ('new', {'target_host': 'www.yahoo.com', 'target_port': 80}), ('attach', {'target_addr': maybe_ip_addr('1.2.3.4')}), ('failed', {'kwargs': dict(REASON='TIMEOUT', REMOTE_REASON='DESTROYED')}) ] ) stream = Stream(self) stream.listen(listener) stream.update("316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER".split()) self.circuits[186] = FakeCircuit(186) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) stream.update("316 FAILED 0 1.2.3.4:80 REASON=TIMEOUT REMOTE_REASON=DESTROYED".split())
def test_listener_attach(self): self.circuits[186] = FakeCircuit(186) listener = Listener( [ ('new', {'target_host': 'www.yahoo.com', 'target_port': 80}), ('attach', {'target_addr': maybe_ip_addr('1.2.3.4')}) ] ) stream = Stream(self) stream.listen(listener) stream.update("316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER".split()) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) self.assertEqual(self.circuits[186].streams[0], stream)
def test_listener_close(self): self.circuits[186] = FakeCircuit(186) listener = Listener( [ ('new', {'target_host': 'www.yahoo.com', 'target_port': 80}), ('attach', {'target_addr': maybe_ip_addr('1.2.3.4')}), ('closed', {'kwargs': dict(REASON='END', REMOTE_REASON='DONE')}) ] ) stream = Stream(self) stream.listen(listener) stream.update("316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER".split()) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) stream.update("316 CLOSED 186 1.2.3.4:80 REASON=END REMOTE_REASON=DONE".split()) self.assertEqual(len(self.circuits[186].streams), 0)
def test_listener_attach(self): self.circuits[186] = FakeCircuit(186) listener = Listener([('new', { 'target_host': 'www.yahoo.com', 'target_port': 80 }), ('attach', { 'target_addr': maybe_ip_addr('1.2.3.4') })]) stream = Stream(self) stream.listen(listener) stream.update( "316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER" .split()) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) self.assertEqual(self.circuits[186].streams[0], stream)
def test_listener_close(self): self.circuits[186] = FakeCircuit(186) listener = Listener([('new', { 'target_host': 'www.yahoo.com', 'target_port': 80 }), ('attach', { 'target_addr': maybe_ip_addr('1.2.3.4') }), ('closed', { 'kwargs': dict(REASON='END', REMOTE_REASON='DONE') })]) stream = Stream(self) stream.listen(listener) stream.update( "316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER" .split()) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) stream.update( "316 CLOSED 186 1.2.3.4:80 REASON=END REMOTE_REASON=DONE".split()) self.assertEqual(len(self.circuits[186].streams), 0)
def test_listener_fail(self): listener = Listener([('new', { 'target_host': 'www.yahoo.com', 'target_port': 80 }), ('attach', { 'target_addr': maybe_ip_addr('1.2.3.4') }), ('failed', { 'kwargs': dict(REASON='TIMEOUT', REMOTE_REASON='DESTROYED') })]) stream = Stream(self) stream.listen(listener) stream.update( "316 NEW 0 www.yahoo.com:80 SOURCE_ADDR=127.0.0.1:55877 PURPOSE=USER" .split()) self.circuits[186] = FakeCircuit(186) stream.update("316 REMAP 186 1.2.3.4:80 SOURCE=EXIT".split()) stream.update( "316 FAILED 0 1.2.3.4:80 REASON=TIMEOUT REMOTE_REASON=DESTROYED". split())
def update(self, args): # print "update",self.id,args if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if 'SOURCE_ADDR' in kw: last_colon = kw['SOURCE_ADDR'].rfind(':') self.source_addr = kw['SOURCE_ADDR'][:last_colon] if self.source_addr != '(Tor_internal)': self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw['SOURCE_ADDR'][last_colon + 1:]) self.state = args[1] # XXX why not using the state-machine stuff? ;) if self.state in ['NEW', 'NEWRESOLVE', 'SUCCEEDED']: if self.target_host is None: last_colon = args[3].rfind(':') self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1:]) self.target_port = int(self.target_port) if self.state == 'NEW': if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) [x.stream_new(self) for x in self.listeners] else: [x.stream_succeeded(self) for x in self.listeners] elif self.state == 'REMAP': self.target_addr = maybe_ip_addr(args[3][:args[3].rfind(':')]) elif self.state == 'CLOSED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) [x.stream_closed(self, **flags) for x in self.listeners] elif self.state == 'FAILED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) [x.stream_failed(self, **flags) for x in self.listeners] elif self.state == 'SENTCONNECT': pass # print 'SENTCONNECT',self,args elif self.state == 'DETACHED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None # FIXME does this count as closed? # self.maybe_call_closing_deferred() flags = self._create_flags(kw) [x.stream_detach(self, **flags) for x in self.listeners] elif self.state in ['NEWRESOLVE', 'SENTRESOLVE']: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) # see if we attached to a circuit. I believe this only happens # on a SENTCONNECT or REMAP. DETACHED is excluded so we don't # immediately re-add the circuit we just detached from if self.state not in ['CLOSED', 'FAILED', 'DETACHED']: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) for x in self.listeners: x.stream_attach(self, self.circuit) else: if self.circuit.id != cid: log.err( RuntimeError( 'Circuit ID changed from %d to %d.' % (self.circuit.id, cid) ) )
def _add_real_target(self, real_addr, circuit, d): # joy oh joy, ipaddress wants unicode, Twisted gives us bytes... real_host = maybe_ip_addr(six.text_type(real_addr.host)) real_port = real_addr.port self._circuit_targets[(real_host, real_port)] = (circuit, d)
def test_create_ipaddr(self, ipaddr): def foo(blam): raise ValueError('testing') ipaddr.IPAddress.side_effect = foo ip = maybe_ip_addr('1.2.3.4')
def test_create_ipaddr(self, ipaddr): ip = maybe_ip_addr('1.2.3.4')
def update(self, args): if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if 'SOURCE_ADDR' in kw: last_colon = kw['SOURCE_ADDR'].rfind(':') self.source_addr = kw['SOURCE_ADDR'][:last_colon] if self.source_addr != '(Tor_internal)': self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw['SOURCE_ADDR'][last_colon + 1:]) self.state = args[1] # XXX why not using the state-machine stuff? ;) if self.state in ['NEW', 'NEWRESOLVE', 'SUCCEEDED']: if self.target_host is None: last_colon = args[3].rfind(':') self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1:]) # target_host is often an IP address (newer tors? did # this change?) so we attempt to look it up in our # AddrMap and make it a name no matter what. if self._addrmap: try: h = self._addrmap.find(self.target_host) self.target_host = h.name except KeyError: pass self.target_port = int(self.target_port) if self.state == 'NEW': if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) self._notify('stream_new', self) else: self._notify('stream_succeeded', self) elif self.state == 'REMAP': self.target_addr = maybe_ip_addr(args[3][:args[3].rfind(':')]) elif self.state == 'CLOSED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_closed', self, **flags) elif self.state == 'FAILED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) self._notify('stream_failed', self, **flags) elif self.state == 'SENTCONNECT': pass # print 'SENTCONNECT',self,args elif self.state == 'DETACHED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None # FIXME does this count as closed? # self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_detach', self, **flags) elif self.state in ['NEWRESOLVE', 'SENTRESOLVE']: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) # see if we attached to a circuit. I believe this only happens # on a SENTCONNECT or REMAP. DETACHED is excluded so we don't # immediately re-add the circuit we just detached from if self.state not in ['CLOSED', 'FAILED', 'DETACHED']: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) self._notify('stream_attach', self, self.circuit) else: if self.circuit.id != cid: log.err( RuntimeError('Circuit ID changed from %d to %d.' % (self.circuit.id, cid)))
def update(self, args): ## print "update",self.id,args if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if 'SOURCE_ADDR' in kw: last_colon = kw['SOURCE_ADDR'].rfind(':') self.source_addr = kw['SOURCE_ADDR'][:last_colon] if self.source_addr != '(Tor_internal)': self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw['SOURCE_ADDR'][last_colon + 1:]) self.state = args[1] if self.state in ['NEW', 'NEWRESOLVE', 'SUCCEEDED']: if self.target_host is None: last_colon = args[3].rfind(':') self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1:]) self.target_port = int(self.target_port) if self.state == 'NEW': if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) [x.stream_new(self) for x in self.listeners] else: [x.stream_succeeded(self) for x in self.listeners] elif self.state == 'REMAP': self.target_addr = maybe_ip_addr(args[3][:args[3].rfind(':')]) elif self.state == 'CLOSED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) [x.stream_closed(self, **flags) for x in self.listeners] elif self.state == 'FAILED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) [x.stream_failed(self, **flags) for x in self.listeners] elif self.state == 'SENTCONNECT': pass # print 'SENTCONNECT',self,args elif self.state == 'DETACHED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None ## FIXME does this count as closed? ##self.maybe_call_closing_deferred() flags = self._create_flags(kw) [x.stream_detach(self, **flags) for x in self.listeners] elif self.state in ['NEWRESOLVE', 'SENTRESOLVE']: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) ## see if we attached to a circuit. I believe this only ## happens on a SENTCONNECT or REMAP. DETACHED is excluded so ## we don't immediately re-add the circuit we just detached ## from if self.state not in ['CLOSED', 'FAILED', 'DETACHED']: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) [x.stream_attach(self, self.circuit) for x in self.listeners] else: if self.circuit.id != cid: log.err(RuntimeError('Circuit ID changed from %d to %d.' % (self.circuit.id, cid)))
def test_create_ipaddr(self, ipaddr): def foo(blam): raise ValueError('testing') ipaddr.ip_address.side_effect = foo ip = maybe_ip_addr('1.2.3.4')
def test_create_ipaddr(self): ip = maybe_ip_addr('1.2.3.4') self.assertTrue(isinstance(ip, ipaddress.IPv4Address))
def test_ipv6_remap(self): stream = Stream(self) stream.update("1234 REMAP 0 ::1:80 SOURCE_ADDR=127.0.0.1:57349 PURPOSE=USER".split()) self.assertEqual(stream.target_addr, maybe_ip_addr('::1'))
def test_ipv6_remap(self): stream = Stream(self) stream.update( "1234 REMAP 0 ::1:80 SOURCE_ADDR=127.0.0.1:57349 PURPOSE=USER". split()) self.assertEqual(stream.target_addr, maybe_ip_addr('::1'))
def update(self, args): # print "update",self.id,args if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if "SOURCE_ADDR" in kw: last_colon = kw["SOURCE_ADDR"].rfind(":") self.source_addr = kw["SOURCE_ADDR"][:last_colon] if self.source_addr != "(Tor_internal)": self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw["SOURCE_ADDR"][last_colon + 1 :]) self.state = args[1] # XXX why not using the state-machine stuff? ;) if self.state in ["NEW", "NEWRESOLVE", "SUCCEEDED"]: if self.target_host is None: last_colon = args[3].rfind(":") self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1 :]) # target_host is often an IP address (newer tors? did # this change?) so we attempt to look it up in our # AddrMap and make it a name no matter what. if self._addrmap: try: h = self._addrmap.find(self.target_host) self.target_host = h.name except KeyError: pass self.target_port = int(self.target_port) if self.state == "NEW": if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) self._notify("stream_new", self) else: self._notify("stream_succeeded", self) elif self.state == "REMAP": self.target_addr = maybe_ip_addr(args[3][: args[3].rfind(":")]) elif self.state == "CLOSED": if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify("stream_closed", self, **flags) elif self.state == "FAILED": if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) self._notify("stream_failed", self, **flags) elif self.state == "SENTCONNECT": pass # print 'SENTCONNECT',self,args elif self.state == "DETACHED": if self.circuit: self.circuit.streams.remove(self) self.circuit = None # FIXME does this count as closed? # self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify("stream_detach", self, **flags) elif self.state in ["NEWRESOLVE", "SENTRESOLVE"]: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) # see if we attached to a circuit. I believe this only happens # on a SENTCONNECT or REMAP. DETACHED is excluded so we don't # immediately re-add the circuit we just detached from if self.state not in ["CLOSED", "FAILED", "DETACHED"]: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) self._notify("stream_attach", self, self.circuit) else: if self.circuit.id != cid: log.err(RuntimeError("Circuit ID changed from %d to %d." % (self.circuit.id, cid)))
def update(self, args): if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if 'SOURCE_ADDR' in kw: last_colon = kw['SOURCE_ADDR'].rfind(':') self.source_addr = kw['SOURCE_ADDR'][:last_colon] if self.source_addr != '(Tor_internal)': self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw['SOURCE_ADDR'][last_colon + 1:]) self.state = args[1] # XXX why not using the state-machine stuff? ;) if self.state in ['NEW', 'NEWRESOLVE', 'SUCCEEDED']: if self.target_host is None: last_colon = args[3].rfind(':') self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1:]) # target_host is often an IP address (newer tors? did # this change?) so we attempt to look it up in our # AddrMap and make it a name no matter what. if self._addrmap: try: h = self._addrmap.find(self.target_host) self.target_host = h.name except KeyError: pass self.target_port = int(self.target_port) if self.state == 'NEW': if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) self._notify('stream_new', self) else: self._notify('stream_succeeded', self) elif self.state == 'REMAP': self.target_addr = maybe_ip_addr(args[3][:args[3].rfind(':')]) elif self.state == 'CLOSED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_closed', self, **flags) elif self.state == 'FAILED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) self._notify('stream_failed', self, **flags) elif self.state == 'SENTCONNECT': pass # print 'SENTCONNECT',self,args elif self.state == 'DETACHED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None # FIXME does this count as closed? # self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_detach', self, **flags) elif self.state in ['NEWRESOLVE', 'SENTRESOLVE']: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) # see if we attached to a circuit. I believe this only happens # on a SENTCONNECT or REMAP. DETACHED is excluded so we don't # immediately re-add the circuit we just detached from if self.state not in ['CLOSED', 'FAILED', 'DETACHED']: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) self._notify('stream_attach', self, self.circuit) else: # XXX I am seeing this from torexitscan (*and* # from my TorNS thing, so I think it's some kind # of 'consistent' behavior out of Tor) so ... this # is probably when we're doing stream-attachment # stuff? maybe the stream gets assigned a circuit # 'provisionally' and then it's changed? # ...yup, looks like it! if self.circuit.id != cid: # you can get SENTCONNECT twice in a row (for example) with different circuit-ids if there is something (e.g. another txtorcon) doing R log.err( RuntimeError( 'Circuit ID changed from %d to %d state=%s.' % (self.circuit.id, cid, self.state)))
def update(self, args): if self.id is None: self.id = int(args[0]) else: if self.id != int(args[0]): raise RuntimeError("Update for wrong stream.") kw = find_keywords(args) self.flags = kw if 'SOURCE_ADDR' in kw: last_colon = kw['SOURCE_ADDR'].rfind(':') self.source_addr = kw['SOURCE_ADDR'][:last_colon] if self.source_addr != '(Tor_internal)': self.source_addr = maybe_ip_addr(self.source_addr) self.source_port = int(kw['SOURCE_ADDR'][last_colon + 1:]) self.state = args[1] # XXX why not using the state-machine stuff? ;) if self.state in ['NEW', 'NEWRESOLVE', 'SUCCEEDED']: if self.target_host is None: last_colon = args[3].rfind(':') self.target_host = args[3][:last_colon] self.target_port = int(args[3][last_colon + 1:]) # target_host is often an IP address (newer tors? did # this change?) so we attempt to look it up in our # AddrMap and make it a name no matter what. if self._addrmap: try: h = self._addrmap.find(self.target_host) self.target_host = h.name except KeyError: pass self.target_port = int(self.target_port) if self.state == 'NEW': if self.circuit is not None: log.err(RuntimeError("Weird: circuit valid in NEW")) self._notify('stream_new', self) else: self._notify('stream_succeeded', self) elif self.state == 'REMAP': self.target_addr = maybe_ip_addr(args[3][:args[3].rfind(':')]) elif self.state == 'CLOSED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_closed', self, **flags) elif self.state == 'FAILED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None self.maybe_call_closing_deferred() # build lower-case version of all flags flags = self._create_flags(kw) self._notify('stream_failed', self, **flags) elif self.state == 'SENTCONNECT': pass # print 'SENTCONNECT',self,args elif self.state == 'DETACHED': if self.circuit: self.circuit.streams.remove(self) self.circuit = None # FIXME does this count as closed? # self.maybe_call_closing_deferred() flags = self._create_flags(kw) self._notify('stream_detach', self, **flags) elif self.state in ['NEWRESOLVE', 'SENTRESOLVE']: pass # print self.state, self, args else: raise RuntimeError("Unknown state: %s" % self.state) # see if we attached to a circuit. I believe this only happens # on a SENTCONNECT or REMAP. DETACHED is excluded so we don't # immediately re-add the circuit we just detached from if self.state not in ['CLOSED', 'FAILED', 'DETACHED']: cid = int(args[2]) if cid == 0: if self.circuit and self in self.circuit.streams: self.circuit.streams.remove(self) self.circuit = None else: if self.circuit is None: self.circuit = self.circuit_container.find_circuit(cid) if self not in self.circuit.streams: self.circuit.streams.append(self) self._notify('stream_attach', self, self.circuit) else: # XXX I am seeing this from torexitscan (*and* # from my TorNS thing, so I think it's some kind # of 'consistent' behavior out of Tor) so ... this # is probably when we're doing stream-attachment # stuff? maybe the stream gets assigned a circuit # 'provisionally' and then it's changed? # ...yup, looks like it! if self.circuit.id != cid: # you can get SENTCONNECT twice in a row (for example) with different circuit-ids if there is something (e.g. another txtorcon) doing R log.err( RuntimeError( 'Circuit ID changed from %d to %d state=%s.' % (self.circuit.id, cid, self.state) ) )