class ProtocolIntegrationTests(unittest.TestCase): """ Tests which use a real TorControlProtocol objects, not a mock. """ def setUp(self): self.protocol = TorControlProtocol(lambda: defer.succeed('foo')) self.transport = proto_helpers.StringTransport() def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") @defer.inlineCallbacks def test_with_arg(self): info = TorInfo(self.protocol) pb = info.post_bootstrap ## now we hook up the protocol like it connected to a real Tor self.protocol.makeConnection(self.transport) ## answer all the requests generated by TorControlProtocol boostrapping etc. self.send('250-AUTH METHODS=PASSWORD') self.send('250 OK') ## response to AUTHENTICATE self.send('250 OK') ## now we're in _bootstrap() in TorControlProtocol() self.send("250-version=foo") self.send("250 OK") self.send("250-events/names=") self.send("250 OK") self.send("250 OK") # for USEFEATURE ## do the TorInfo magic self.send('250-info/names=') self.send('250-multi/path/arg/* a documentation string') self.send('250 OK') ## we had to save this up above due to the "interesting" way ## TorInfo switches to become a possible-nice magic thingy ## that does attribute-access for you. yield pb self.assertTrue(hasattr(info, 'multi')) self.assertTrue(hasattr(getattr(info, 'multi'), 'path')) self.assertTrue(hasattr(getattr(getattr(info, 'multi'), 'path'), 'arg')) ## Finally! The test! We see if we can get this multi-path ## value with an argument... ## a "real" tor example is "net/listeners/socks" which shows ## up in info/names as "net/listeners/*" d = info.multi.path.arg('quux') d.addCallback(CheckAnswer(self, 'foo')) self.send("250-multi/path/arg/quux=foo") self.send("250 OK") yield d
class TorStatePy3Tests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.state = TorState(self.protocol) # avoid spew in trial logs; state prints this by default self.state._attacher_error = lambda f: f self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def send(self, line): self.protocol.dataReceived(line.strip() + b"\r\n") def test_attacher_coroutine(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self, answer): self.streams = [] self.answer = answer async def attach_stream(self, stream, circuits): self.streams.append(stream) x = await defer.succeed(self.answer) return x self.state.circuits[1] = FakeCircuit(1) self.state.circuits[1].state = 'BUILT' attacher = MyAttacher(self.state.circuits[1]) self.state.set_attacher(attacher, FakeReactor(self)) # boilerplate to finish enough set-up in the protocol so it # works events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send( b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], b'ATTACHSTREAM 1 1')
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError, e: self.assertTrue('No code set yet' in str(e))
class StateTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.state = TorState(self.protocol) self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_close_stream_with_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] def attach_stream(self, stream, circuits): self.streams.append(stream) return None attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE") def test_stream_update(self): ## we use a circuit ID of 0 so it doesn't try to look anything up but it's ## not really correct to have a SUCCEEDED w/o a valid circuit, I don't think self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80') self.assertTrue(1610 in self.state.streams) def test_single_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status('stream-status=123 SUCCEEDED 496 www.example.com:6667\r\nOK') self.assertEqual(len(self.state.streams), 1) def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_bootstrap_callback(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250+circuit-status=") self.send(".") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250-address-mappings/all=") self.send("250 OK") for ignored in self.state.event_map.items(): self.send("250 OK") fakerouter = object() self.state.routers['$0000000000000000000000000000000000000000'] = fakerouter self.state.routers['$9999999999999999999999999999999999999999'] = fakerouter self.send("250+entry-guards=") self.send("$0000000000000000000000000000000000000000=name up") self.send("$1111111111111111111111111111111111111111=foo up") self.send("$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00") self.send(".") self.send("250 OK") ## implicitly created Router object for the $1111...11 lookup ## but 0.0.0.0 will have to country, so Router will ask Tor ## for one via GETINFO ip-to-country self.send("250-ip-to-country/0.0.0.0=??") self.send("250 OK") self.assertEqual(len(self.state.entry_guards), 2) self.assertTrue('$0000000000000000000000000000000000000000' in self.state.entry_guards) self.assertEqual(self.state.entry_guards['$0000000000000000000000000000000000000000'], fakerouter) self.assertTrue('$1111111111111111111111111111111111111111' in self.state.entry_guards) self.assertEqual(len(self.state.unusable_entry_guards), 1) self.assertTrue('$9999999999999999999999999999999999999999' in self.state.unusable_entry_guards[0]) return d def test_bootstrap_existing_addresses(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250+circuit-status=") self.send(".") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('www.example.com 127.0.0.1 "2012-01-01 00:00:00"') self.send('subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"') self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertEqual(len(self.state.addrmap.addr), 2) self.assertTrue('www.example.com' in self.state.addrmap.addr) self.assertTrue('subdomain.example.com' in self.state.addrmap.addr) return d def test_bootstrap_single_existing_circuit(self): ''' test with exactly one circuit. should probably test with 2 as well, since there was a bug with the handling of just one. ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250-circuit-status=123 BUILT PURPOSE=GENERAL") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertTrue(self.state.find_circuit(123)) self.assertEquals(len(self.state.circuits), 1) return d def test_unset_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def attach_stream(self, stream, circuits): return None fr = FakeReactor(self) self.state.set_attacher(MyAttacher(), fr) self.send("250 OK") self.state.set_attacher(None, fr) self.send("250 OK") self.assertEqual(self.transport.value(), 'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF __LeaveStreamsUnattached=0\r\n') def test_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] self.answer = None def attach_stream(self, stream, circuits): self.streams.append(stream) return self.answer attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER") self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 0') # we should totally ignore .exit URIs attacher.streams = [] self.send("650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 0) self.assertEqual(len(self.protocol.commands), 1) # we should NOT ignore .onion URIs attacher.streams = [] self.send("650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 1) self.assertEqual(len(self.protocol.commands), 2) self.assertEqual(self.protocol.commands[1][1], 'ATTACHSTREAM 3 0') # normal attach circ = FakeCircuit(1) circ.state = 'BUILT' self.state.circuits[1] = circ attacher.answer = circ self.send("650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 2) self.assertEqual(len(self.protocol.commands), 3) self.assertEqual(self.protocol.commands[2][1], 'ATTACHSTREAM 4 1') def test_attacher_defer(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): self.streams.append(stream) return defer.succeed(self.answer) self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(self.state.circuits[1]) self.state.set_attacher(attacher, FakeReactor(self)) ## boilerplate to finish enough set-up in the protocol so it ## works events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER") self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1') def test_attacher_errors(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): return self.answer self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(FakeCircuit(2)) self.state.set_attacher(attacher, FakeReactor(self)) stream = Stream(self.state) stream.id = 3 msg = '' try: self.state._maybe_attach(stream) except Exception, e: msg = str(e) self.assertTrue('circuit unknown' in msg) attacher.answer = self.state.circuits[1] msg = '' try: self.state._maybe_attach(stream) except Exception, e: msg = str(e)
class AuthenticationTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.transport = proto_helpers.StringTransport() def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_authenticate_cookie(self): self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() cookie_data = 'cookiedata!cookiedata!cookiedata' with open('authcookie', 'w') as f: f.write(cookie_data) self.send('250-PROTOCOLINFO 1') self.send( '250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % cookie_data.encode("hex")) def test_authenticate_password(self): self.protocol.password_function = lambda: 'foo' self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex")) def test_authenticate_password_deferred(self): d = defer.Deferred() self.protocol.password_function = lambda: d self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') ## make sure we haven't tried to authenticate before getting ## the password callback self.assertEqual(self.transport.value(), '') d.callback('foo') ## now make sure we DID try to authenticate self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex")) def confirmAuthFailed(self, *args): self.auth_failed = True def test_authenticate_no_password(self): self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed) self.auth_failed = False self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertTrue(self.auth_failed)
class AuthenticationTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.transport = proto_helpers.StringTransport() def send(self, line): assert type(line) == bytes self.protocol.dataReceived(line.strip() + b"\r\n") def test_authenticate_cookie(self): self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() cookie_data = b'cookiedata!cookiedata!cookiedata' with open('authcookie', 'wb') as f: f.write(cookie_data) self.send(b'250-PROTOCOLINFO 1') self.send( b'250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(cookie_data) + b'\r\n', ) def test_authenticate_password(self): self.protocol.password_function = lambda: 'foo' self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=HASHEDPASSWORD') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') self.assertEqual(self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n') def test_authenticate_password_not_bytes(self): self.protocol.password_function = lambda: u'foo' self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=HASHEDPASSWORD') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') self.assertEqual(self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n') def test_authenticate_null(self): self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=NULL') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') self.assertEqual(self.transport.value(), b'AUTHENTICATE\r\n') def test_authenticate_password_deferred(self): d = defer.Deferred() self.protocol.password_function = lambda: d self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=HASHEDPASSWORD') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') # make sure we haven't tried to authenticate before getting # the password callback self.assertEqual(self.transport.value(), b'') d.callback('foo') # now make sure we DID try to authenticate self.assertEqual(self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b"foo") + b'\r\n') def test_authenticate_password_deferred_but_no_password(self): d = defer.Deferred() self.protocol.password_function = lambda: d self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.transport.clear() self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=HASHEDPASSWORD') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') d.callback(None) return self.assertFailure(self.protocol.post_bootstrap, RuntimeError) def confirmAuthFailed(self, *args): self.auth_failed = True def test_authenticate_no_password(self): self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed) self.auth_failed = False self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n') self.send(b'250-PROTOCOLINFO 1') self.send(b'250-AUTH METHODS=HASHEDPASSWORD') self.send(b'250-VERSION Tor="0.2.2.34"') self.send(b'250 OK') self.assertTrue(self.auth_failed)
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): assert type(line) == bytes self.protocol.dataReceived(line.strip() + b"\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('No code set yet' in str(e)) def test_statemachine_broadcast_unknown_code(self): try: self.protocol.code = 999 self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('Unknown code' in str(e)) def test_statemachine_is_finish(self): self.assertTrue(not self.protocol._is_finish_line('')) self.assertTrue(self.protocol._is_finish_line('.')) self.assertTrue(self.protocol._is_finish_line('300 ')) self.assertTrue(not self.protocol._is_finish_line('250-')) def test_statemachine_singleline(self): self.assertTrue(not self.protocol._is_single_line_response('foo')) def test_statemachine_continuation(self): try: self.protocol.code = 250 self.protocol._is_continuation_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_statemachine_multiline(self): try: self.protocol.code = 250 self.protocol._is_multi_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_response_with_no_request(self): with self.assertRaises(RuntimeError) as ctx: self.protocol.code = 200 self.protocol._broadcast_response('200 OK') self.assertTrue("didn't issue a command" in str(ctx.exception)) def auth_failed(self, msg): self.assertEqual(str(msg.value), '551 go away') self.got_auth_failed = True def test_authenticate_fail(self): self.got_auth_failed = False self.protocol._auth_failed = self.auth_failed self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=HASHEDPASSWORD VERSION Tor="0.2.2.35" OK''') self.send(b'551 go away\r\n') self.assertTrue(self.got_auth_failed) def test_authenticate_no_auth_line(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 FOOAUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/dev/null" VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError as e: self.assertTrue('find AUTH line' in str(e)) def test_authenticate_not_enough_cookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_not_enough_safecookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_safecookie(self): with tempfile.NamedTemporaryFile() as cookietmp: cookiedata = bytes(bytearray([0] * 32)) cookietmp.write(cookiedata) cookietmp.flush() self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(cookietmp.name)) self.assertTrue( b'AUTHCHALLENGE SAFECOOKIE ' in self.transport.value()) x = self.transport.value().split()[-1] client_nonce = a2b_hex(x) self.transport.clear() server_nonce = bytes(bytearray([0] * 32)) server_hash = hmac_sha256( b"Tor safe cookie authentication server-to-controller hash", cookiedata + client_nonce + server_nonce, ) self.send(b'250 AUTHCHALLENGE SERVERHASH=' + base64.b16encode(server_hash) + b' SERVERNONCE=' + base64.b16encode(server_nonce) + b'\r\n') self.assertTrue(b'AUTHENTICATE ' in self.transport.value()) def test_authenticate_cookie_without_reading(self): server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s' % (base64.b16encode(server_hash), base64.b16encode(server_nonce))) self.assertTrue(False) except RuntimeError as e: self.assertTrue('not read' in str(e)) def test_authenticate_unexisting_cookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % unexisting_file) self.assertTrue(False) except RuntimeError: pass def test_authenticate_unexisting_safecookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertTrue(False) except RuntimeError: pass def test_authenticate_dont_send_cookiefile(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError: pass def test_authenticate_password_when_cookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_password_when_safecookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_safecookie_wrong_hash(self): cookiedata = bytes(bytearray([0] * 32)) server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) # pretend we already did PROTOCOLINFO and read the cookie # file self.protocol._cookie_data = cookiedata self.protocol.client_nonce = server_nonce # all 0's anyway try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH={} SERVERNONCE={}'.format( b2a_hex(server_hash).decode('ascii'), b2a_hex(server_nonce).decode('ascii'), )) self.assertTrue(False) except RuntimeError as e: self.assertTrue('hash not expected' in str(e)) def confirm_version_events(self, arg): self.assertEqual(self.protocol.version, 'foo') events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'.split( ) self.assertEqual(len(self.protocol.valid_events), len(events)) self.assertTrue(all(x in self.protocol.valid_events for x in events)) def test_bootstrap_callback(self): d = self.protocol.post_bootstrap d.addCallback(CallbackChecker(self.protocol)) d.addCallback(self.confirm_version_events) events = b'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._bootstrap() # answer all the requests generated by boostrapping etc. self.send(b"250-signal/names=") self.send(b"250 OK") self.send(b"250-version=foo") self.send(b"250 OK") self.send(b"250-events/names=" + events) self.send(b"250 OK") self.send(b"250 OK") # for USEFEATURE return d def test_bootstrap_tor_does_not_support_signal_names(self): self.protocol._bootstrap() self.send(b'552 Unrecognized key "signal/names"') valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"] self.assertEqual(self.protocol.valid_signals, valid_signals) def test_async(self): """ test the example from control-spec.txt to see that we handle interleaved async notifications properly. """ self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener('CIRC', lambda _: None) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_async_multiline(self): # same as above, but i think the 650's can be multline, # too. Like: # 650-CIRC 1000 EXTENDED moria1,moria2 0xBEEF # 650-EXTRAMAGIC=99 # 650 ANONYMITY=high self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker( "1000 EXTENDED moria1,moria2\nEXTRAMAGIC=99\nANONYMITY=high")) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({"ORPORT": "0", "SOCKSPORT": "9050"})) self.send(b"650-CIRC 1000 EXTENDED moria1,moria2") self.send(b"650-EXTRAMAGIC=99") self.send(b"650 ANONYMITY=high") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_multiline_plus(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na\nb\nc"})) self.send(b"250+FOO=") self.send(b"a") self.send(b"b") self.send(b"c") self.send(b".") self.send(b"250 OK") return d def test_multiline_plus_embedded_equals(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na="})) self.send(b"250+FOO=") self.send(b"a=") self.send(b".") self.send(b"250 OK") return d def incremental_check(self, expected, actual): if '=' in actual: return self.assertEqual(expected, actual) def test_getinfo_incremental(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar")) self.send(b"250+FOO=") self.send(b"bar") self.send(b"bar") self.send(b".") self.send(b"250 OK") return d def test_getinfo_incremental_continuation(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar")) self.send(b"250-FOO=") self.send(b"250-bar") self.send(b"250-bar") self.send(b"250 OK") return d def test_getinfo_one_line(self): d = self.protocol.get_info("foo", ) self.send(b'250 foo=bar') d.addCallback( lambda _: functools.partial(self.incremental_check, "bar")) return d def test_getconf(self): d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({'SocksPort': '9050', 'ORPort': '0'})) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_raw(self): d = self.protocol.get_conf_raw("SOCKSPORT ORPORT") d.addCallback(CallbackChecker('SocksPort=9050\nORPort=0')) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_single(self): d = self.protocol.get_conf_single("SOCKSPORT") d.addCallback(CallbackChecker('9050')) self.send(b"250 SocksPort=9050") return d def response_ok(self, v): self.assertEqual(v, '') def test_setconf(self): d = self.protocol.set_conf("foo", "bar").addCallback( functools.partial(self.response_ok)) self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value(), b"SETCONF foo=bar\r\n") def test_setconf_with_space(self): d = self.protocol.set_conf("foo", "a value with a space") d.addCallback(functools.partial(self.response_ok)) self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value(), b'SETCONF foo="a value with a space"\r\n') def test_setconf_multi(self): d = self.protocol.set_conf("foo", "bar", "baz", 1) self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"SETCONF foo=bar baz=1\r\n", ) def test_quit(self): d = self.protocol.quit() self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"QUIT\r\n", ) def test_dot(self): # just checking we don't expode self.protocol.graphviz_data() def test_debug(self): self.protocol.start_debug() self.assertTrue(exists('txtorcon-debug.log')) def error(self, failure): print("ERROR", failure) self.assertTrue(False) def test_twocommands(self): "Two commands on the wire before first response." d1 = self.protocol.get_conf("FOO") ht = {"a": "one", "b": "two"} d1.addCallback(CallbackChecker(ht)).addErrback(log.err) d2 = self.protocol.get_info_raw("BAR") d2.addCallback(CallbackChecker("bar")).addErrback(log.err) self.send(b"250-a=one") self.send(b"250-b=two") self.send(b"250 OK") self.send(b"250 bar") return d2 def test_signal_error(self): try: self.protocol.signal('FOO') self.fail() except Exception as e: self.assertTrue('Invalid signal' in str(e)) def test_signal(self): self.protocol.valid_signals = ['NEWNYM'] self.protocol.signal('NEWNYM') self.assertEqual( self.transport.value(), b'SIGNAL NEWNYM\r\n', ) def test_650_after_authenticate(self): self.protocol._set_valid_events('CONF_CHANGED') self.protocol.add_event_listener('CONF_CHANGED', CallbackChecker("Foo=bar")) self.send(b"250 OK") self.send(b"650-CONF_CHANGED") self.send(b"650-Foo=bar") def test_notify_after_getinfo(self): self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker("1000 EXTENDED moria1,moria2")) self.send(b"250 OK") d = self.protocol.get_info("a") d.addCallback(CallbackChecker({'a': 'one'})).addErrback(self.fail) self.send(b"250-a=one") self.send(b"250 OK") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") return d def test_notify_error(self): self.protocol._set_valid_events('CIRC') self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") def test_getinfo(self): d = self.protocol.get_info("version") d.addCallback(CallbackChecker({'version': '0.2.2.34'})) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_single(self): d = self.protocol.get_info_single("version") d.addCallback(CallbackChecker('0.2.2.34')) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_for_descriptor(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux protocols Link 1 2 Circuit 1 published 2013-07-05 23:48:52 fingerprint 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31 uptime 1818933 bandwidth 512000 62914560 1307929 extra-info-digest 17D0142F6EBCDF60160EB1794FA6C9717D581F8C caches-extra-info onion-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALzd4bhz1usB7wpoaAvP+BBOnNIk7mByAKV6zvyQ0p1M09oEmxPMc3qD AAm276oJNf0eq6KWC6YprzPWFsXEIdXSqA6RWXCII1JG/jOoy6nt478BkB8TS9I9 1MJW27ppRaqnLiTmBmM+qzrsgJGwf+onAgUKKH2GxlVgahqz8x6xAgMBAAE= -----END RSA PUBLIC KEY----- signing-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALtJ9uD7cD7iHjqNA3AgsX9prES5QN+yFQyr2uOkxzhvunnaf6SNhzWW bkfylnMrRm/qCz/czcjZO6N6EKHcXmypehvP566B7gAQ9vDsb+l7VZVWgXvzNc2s tl3P7qpC08rgyJh1GqmtQTCesIDqkEyWxwToympCt09ZQRq+fIttAgMBAAE= -----END RSA PUBLIC KEY----- hidden-service-dir contact 1024D/28988BF5 arma mit edu ntor-onion-key 9ZVjNkf/iLEnD685SpC5kcDytQ7u5ViiI9JOftdbE0k= reject *:* router-signature -----BEGIN SIGNATURE----- Y8Tj2e7mPbFJbguulkPEBVYzyO57p4btpWEXvRMD6vxIh/eyn25pehg5dUVBtZlL iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q 01NZPuSqXM1b6hnl2espFzL7XL8XEGRU+aeg+f/ukw4= -----END SIGNATURE----- . 250 OK""" d = self.protocol.get_info("desc/name/moria1") d.addCallback( CallbackChecker({ 'desc/name/moria1': '\n' + '\n'.join(descriptor_info.decode('ascii').split('\n')[1:-2]) })) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_getinfo_multiline(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux . 250 OK""" d = self.protocol.get_info("desc/name/moria1") gold = "\nrouter moria1 128.31.0.34 9101 0 9131\nplatform Tor 0.2.5.0-alpha-dev on Linux" d.addCallback(CallbackChecker({'desc/name/moria1': gold})) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_addevent(self): self.protocol._set_valid_events('FOO BAR') self.protocol.add_event_listener('FOO', lambda _: None) # is it dangerous/ill-advised to depend on internal state of # class under test? d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value().split(b'\r\n')[-2], b"SETEVENTS FOO") self.transport.clear() self.protocol.add_event_listener('BAR', lambda _: None) d = self.protocol.defer self.send(b"250 OK") self.assertTrue(self.transport.value() == b"SETEVENTS FOO BAR\r\n" or self.transport.value() == b"SETEVENTS BAR FOO\r\n") self._wait(d) try: self.protocol.add_event_listener('SOMETHING_INVALID', lambda _: None) self.assertTrue(False) except Exception: pass def test_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener.stream_events, 2) def test_eventlistener_error(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 do_error = False def __call__(self, data): self.stream_events += 1 if self.do_error: raise Exception("the bad thing happened") # we make sure the first listener has the errors to prove the # second one still gets called. listener0 = EventListener() listener0.do_error = True listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.protocol.add_event_listener('STREAM', listener1) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener0.stream_events, 2) self.assertEqual(listener1.stream_events, 2) # should have logged the two errors logged = self.flushLoggedErrors() self.assertEqual(2, len(logged)) self.assertTrue("the bad thing happened" in str(logged[0])) self.assertTrue("the bad thing happened" in str(logged[1])) def test_remove_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() self.protocol.remove_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') def test_remove_eventlistener_multiple(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener0 = EventListener() listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() # add another one, shouldn't issue a tor command self.protocol.add_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'') # remove one, should still not issue a tor command self.protocol.remove_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'') # remove the other one, NOW should issue a command self.protocol.remove_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') # try removing invalid event try: self.protocol.remove_event_listener('FOO', listener0) self.fail() except Exception as e: self.assertTrue('FOO' in str(e)) def test_continuation_line(self): d = self.protocol.get_info_raw("key") def check_continuation(v): self.assertEqual(v, "key=\nvalue0\nvalue1") d.addCallback(check_continuation) self.send(b"250+key=") self.send(b"value0") self.send(b"value1") self.send(b".") self.send(b"250 OK") return d def test_newdesc(self): """ FIXME: this test is now maybe a little silly, it's just testing multiline GETINFO... (Real test is in TorStateTests.test_newdesc_parse) """ self.protocol.get_info_raw( 'ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A') d = self.protocol.defer d.addCallback( CallbackChecker("""ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888""" )) self.send(b"250+ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=") self.send( b"r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80" ) self.send(b"s Exit Fast Guard HSDir Named Running Stable V2Dir Valid") self.send(b"w Bandwidth=518000") self.send( b"p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888" ) self.send(b".") self.send(b"250 OK") return d def test_plus_line_no_command(self): self.protocol.lineReceived(b"650+NS\r\n") self.protocol.lineReceived( b"r Gabor gFpAHsFOHGATy12ZUswRf0ZrqAU GG6GDp40cQfR3ODvkBT0r+Q09kw 2012-05-12 16:54:56 91.219.238.71 443 80\r\n" ) def test_minus_line_no_command(self): """ haven't seen 600's use - "in the wild" but don't see why it's not possible """ self.protocol._set_valid_events('NS') self.protocol.add_event_listener('NS', lambda _: None) self.protocol.lineReceived(b"650-NS\r\n") self.protocol.lineReceived(b"650 OK\r\n")
class StateTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.state = TorState(self.protocol) # avoid spew in trial logs; state prints this by default self.state._attacher_error = lambda f: f self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_close_stream_with_attacher(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self): self.streams = [] def attach_stream(self, stream, circuits): self.streams.append(stream) return None attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE") def test_attacher_error_handler(self): # make sure error-handling "does something" that isn't blowing up with patch('sys.stdout'): TorState(self.protocol)._attacher_error( Failure(RuntimeError("quote"))) def test_stream_update(self): # we use a circuit ID of 0 so it doesn't try to look anything # up but it's not really correct to have a SUCCEEDED w/o a # valid circuit, I don't think self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80') self.assertTrue(1610 in self.state.streams) def test_single_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status( 'stream-status=123 SUCCEEDED 496 www.example.com:6667') self.assertEqual(len(self.state.streams), 1) def test_multiple_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status('\r\n'.join([ 'stream-status=', '123 SUCCEEDED 496 www.example.com:6667', '124 SUCCEEDED 496 www.example.com:6667', ])) self.assertEqual(len(self.state.streams), 2) def send(self, line): self.protocol.dataReceived(line.strip() + b"\r\n") @defer.inlineCallbacks def test_bootstrap_callback(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' from .test_torconfig import FakeControlProtocol protocol = FakeControlProtocol([ "ns/all=", # ns/all "", # circuit-status "", # stream-status "", # address-mappings/all "entry-guards=\r\n$0000000000000000000000000000000000000000=name up\r\n$1111111111111111111111111111111111111111=foo up\r\n$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00\r\n", # entry-guards "99999", # process/pid "??", # ip-to-country/0.0.0.0 ]) state = yield TorState.from_protocol(protocol) self.assertEqual(len(state.entry_guards), 2) self.assertTrue( '$0000000000000000000000000000000000000000' in state.entry_guards) self.assertTrue( '$1111111111111111111111111111111111111111' in state.entry_guards) self.assertEqual(len(state.unusable_entry_guards), 1) self.assertTrue('$9999999999999999999999999999999999999999' in state.unusable_entry_guards[0]) def test_bootstrap_existing_addresses(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send(b"250+ns/all=") self.send(b".") self.send(b"250 OK") self.send(b"250+circuit-status=") self.send(b".") self.send(b"250 OK") self.send(b"250-stream-status=") self.send(b"250 OK") self.send(b"250+address-mappings/all=") self.send(b'www.example.com 127.0.0.1 "2012-01-01 00:00:00"') self.send(b'subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"') self.send(b".") self.send(b"250 OK") for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send(b"250-entry-guards=") self.send(b"250 OK") self.send(b"250 OK") self.assertEqual(len(self.state.addrmap.addr), 4) self.assertTrue('www.example.com' in self.state.addrmap.addr) self.assertTrue('subdomain.example.com' in self.state.addrmap.addr) self.assertTrue('10.0.0.0' in self.state.addrmap.addr) self.assertTrue('127.0.0.1' in self.state.addrmap.addr) self.assertEqual(IPv4Address(u'127.0.0.1'), self.state.addrmap.find('www.example.com').ip) self.assertEqual('www.example.com', self.state.addrmap.find('127.0.0.1').name) self.assertEqual(IPv4Address(u'10.0.0.0'), self.state.addrmap.find('subdomain.example.com').ip) self.assertEqual('subdomain.example.com', self.state.addrmap.find('10.0.0.0').name) return d def test_bootstrap_single_existing_circuit(self): ''' test with exactly one circuit. should probably test with 2 as well, since there was a bug with the handling of just one. ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send(b"250+ns/all=") self.send(b".") self.send(b"250 OK") self.send(b"250-circuit-status=123 BUILT PURPOSE=GENERAL") self.send(b"250 OK") self.send(b"250-stream-status=") self.send(b"250 OK") self.send(b"250+address-mappings/all=") self.send(b".") self.send(b"250 OK") for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send(b"250-entry-guards=") self.send(b"250 OK") self.send(b"250 OK") self.assertTrue(self.state.find_circuit(123)) self.assertEquals(len(self.state.circuits), 1) return d def test_unset_attacher(self): @implementer(IStreamAttacher) class MyAttacher(object): def attach_stream(self, stream, circuits): return None fr = FakeReactor(self) attacher = MyAttacher() self.state.set_attacher(attacher, fr) self.send(b"250 OK") self.state.set_attacher(None, fr) self.send(b"250 OK") self.assertEqual( self.transport.value(), b'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF' b' __LeaveStreamsUnattached=0\r\n') def test_attacher_twice(self): """ It should be an error to set an attacher twice """ @implementer(IStreamAttacher) class MyAttacher(object): pass attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) # attach the *same* instance twice; not an error self.state.set_attacher(attacher, FakeReactor(self)) with self.assertRaises(RuntimeError) as ctx: self.state.set_attacher(MyAttacher(), FakeReactor(self)) self.assertTrue("already have an attacher" in str(ctx.exception)) @defer.inlineCallbacks def _test_attacher_both_apis(self): """ similar to above, but first set_attacher is implicit via Circuit.stream_via """ reactor = Mock() directlyProvides(reactor, IReactorCore) @implementer(IStreamAttacher) class MyAttacher(object): pass circ = Circuit(self.state) circ.state = 'BUILT' # use the "preferred" API, which will set an attacher factory = Mock() proto = Mock() proto.when_done = Mock(return_value=defer.succeed(None)) factory.connect = Mock(return_value=defer.succeed(proto)) ep = circ.stream_via(reactor, 'meejah.ca', 443, factory) addr = Mock() addr.host = '10.0.0.1' addr.port = 1011 ep._target_endpoint._get_address = Mock( return_value=defer.succeed(addr)) print("EP", ep) attacher = yield _get_circuit_attacher(reactor, self.state) print("attacher", attacher) d = ep.connect('foo') print("doin' it") stream = Mock() import ipaddress stream.source_addr = ipaddress.IPv4Address(u'10.0.0.1') stream.source_port = 1011 attacher.attach_stream(stream, []) yield d # ...now use the low-level API (should be an error) with self.assertRaises(RuntimeError) as ctx: self.state.set_attacher(MyAttacher(), FakeReactor(self)) self.assertTrue("already have an attacher" in str(ctx.exception)) def test_attacher(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self): self.streams = [] self.answer = None def attach_stream(self, stream, circuits): self.streams.append(stream) return self.answer attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send( b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], b'ATTACHSTREAM 1 0') # we should totally ignore .exit URIs attacher.streams = [] self.send( b"650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 0) self.assertEqual(len(self.protocol.commands), 1) # we should NOT ignore .onion URIs attacher.streams = [] self.send( b"650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 1) self.assertEqual(len(self.protocol.commands), 2) self.assertEqual(self.protocol.commands[1][1], b'ATTACHSTREAM 3 0') # normal attach circ = FakeCircuit(1) circ.state = 'BUILT' self.state.circuits[1] = circ attacher.answer = circ self.send( b"650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 2) self.assertEqual(len(self.protocol.commands), 3) self.assertEqual(self.protocol.commands[2][1], b'ATTACHSTREAM 4 1') def test_attacher_defer(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): self.streams.append(stream) return defer.succeed(self.answer) self.state.circuits[1] = FakeCircuit(1) self.state.circuits[1].state = 'BUILT' attacher = MyAttacher(self.state.circuits[1]) self.state.set_attacher(attacher, FakeReactor(self)) # boilerplate to finish enough set-up in the protocol so it # works events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send( b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], b'ATTACHSTREAM 1 1') @defer.inlineCallbacks def test_attacher_errors(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): return self.answer self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(FakeCircuit(2)) self.state.set_attacher(attacher, FakeReactor(self)) stream = Stream(self.state) stream.id = 3 msg = '' try: yield self.state._maybe_attach(stream) except Exception as e: msg = str(e) self.assertTrue('circuit unknown' in msg) attacher.answer = self.state.circuits[1] msg = '' try: yield self.state._maybe_attach(stream) except Exception as e: msg = str(e) self.assertTrue('only attach to BUILT' in msg) attacher.answer = 'not a Circuit instance' msg = '' try: yield self.state._maybe_attach(stream) except Exception as e: msg = str(e) self.assertTrue('Circuit instance' in msg) def test_attacher_no_attach(self): @implementer(IStreamAttacher) class MyAttacher(object): def __init__(self): self.streams = [] def attach_stream(self, stream, circuits): self.streams.append(stream) return TorState.DO_NOT_ATTACH attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") self.transport.clear() self.send( b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(self.transport.value(), b'') def test_close_stream_with_id(self): stream = Stream(self.state) stream.id = 1 self.state.streams[1] = stream self.state.close_stream(stream) self.assertEqual(self.transport.value(), b'CLOSESTREAM 1 1\r\n') def test_close_stream_with_stream(self): stream = Stream(self.state) stream.id = 1 self.state.streams[1] = stream self.state.close_stream(stream.id) self.assertEqual(self.transport.value(), b'CLOSESTREAM 1 1\r\n') def test_close_stream_invalid_reason(self): stream = Stream(self.state) stream.id = 1 self.state.streams[1] = stream self.assertRaises(ValueError, self.state.close_stream, stream, 'FOO_INVALID_REASON') def test_close_circuit_with_id(self): circuit = Circuit(self.state) circuit.id = 1 self.state.circuits[1] = circuit self.state.close_circuit(circuit.id) self.assertEqual(self.transport.value(), b'CLOSECIRCUIT 1\r\n') def test_close_circuit_with_circuit(self): circuit = Circuit(self.state) circuit.id = 1 self.state.circuits[1] = circuit self.state.close_circuit(circuit) self.assertEqual(self.transport.value(), b'CLOSECIRCUIT 1\r\n') def test_close_circuit_with_flags(self): circuit = Circuit(self.state) circuit.id = 1 # try: # self.state.close_circuit(circuit.id, IfUnused=True) # self.assertTrue(False) # except KeyError: # pass self.state.circuits[1] = circuit self.state.close_circuit(circuit.id, IfUnused=True) self.assertEqual(self.transport.value(), b'CLOSECIRCUIT 1 IfUnused\r\n') def test_circuit_destroy(self): self.state._circuit_update('365 LAUNCHED PURPOSE=GENERAL') self.assertTrue(365 in self.state.circuits) self.state._circuit_update( '365 FAILED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT' ) self.assertTrue(365 not in self.state.circuits) def test_circuit_destroy_already(self): self.state._circuit_update('365 LAUNCHED PURPOSE=GENERAL') self.assertTrue(365 in self.state.circuits) self.state._circuit_update( '365 CLOSED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT' ) self.assertTrue(365 not in self.state.circuits) self.state._circuit_update( '365 CLOSED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT' ) self.assertTrue(365 not in self.state.circuits) def test_circuit_listener(self): events = 'CIRC STREAM ORCONN BW DEBUG INFO NOTICE WARN ERR NEWDESC ADDRMAP AUTHDIR_NEWDESCS DESCCHANGED NS STATUS_GENERAL STATUS_CLIENT STATUS_SERVER GUARD STREAM_BW CLIENTS_SEEN NEWCONSENSUS BUILDTIMEOUT_SET' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") # we use this router later on in an EXTEND self.state._update_network_status("""ns/all= r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0 s Fast Guard Running Stable Unnamed Valid w Bandwidth=51500 p reject 1-65535""") expected = [('new', { 'id': 456 }), ('launched', {}), ('extend', { 'id': 123 })] listen = CircuitListener(expected) # first add a Circuit before we listen self.protocol.dataReceived( b"650 CIRC 123 LAUNCHED PURPOSE=GENERAL\r\n") self.assertEqual(len(self.state.circuits), 1) # make sure we get added to existing circuits self.state.add_circuit_listener(listen) first_circuit = list(self.state.circuits.values())[0] self.assertTrue(listen in first_circuit.listeners) # now add a Circuit after we started listening self.protocol.dataReceived( b"650 CIRC 456 LAUNCHED PURPOSE=GENERAL\r\n") self.assertEqual(len(self.state.circuits), 2) self.assertTrue( listen in list(self.state.circuits.values())[0].listeners) self.assertTrue( listen in list(self.state.circuits.values())[1].listeners) # now update the first Circuit to ensure we're really, really # listening self.protocol.dataReceived( b"650 CIRC 123 EXTENDED $D82183B1C09E1D7795FF2D7116BAB5106AA3E60E~PPrivCom012 PURPOSE=GENERAL\r\n" ) self.assertEqual(len(listen.expected), 0) def test_router_from_id_invalid_key(self): self.failUnlessRaises(KeyError, self.state.router_from_id, 'somethingcompletelydifferent..thatis42long') def test_router_from_named_router(self): r = self.state.router_from_id( '$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=foo') self.assertEqual(r.id_hex, '$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') self.assertEqual(r.unique_name, 'foo') def confirm_router_state(self, x): self.assertTrue( '$624926802351575FF7E4E3D60EFA3BFB56E67E8A' in self.state.routers) router = self.state.routers[ '$624926802351575FF7E4E3D60EFA3BFB56E67E8A'] self.assertTrue('exit' in router.flags) self.assertTrue('fast' in router.flags) self.assertTrue('guard' in router.flags) self.assertTrue('hsdir' in router.flags) self.assertTrue('named' in router.flags) self.assertTrue('running' in router.flags) self.assertTrue('stable' in router.flags) self.assertTrue('v2dir' in router.flags) self.assertTrue('valid' in router.flags) self.assertTrue('futureproof' in router.flags) self.assertEqual(router.bandwidth, 518000) self.assertTrue(router.accepts_port(43)) self.assertTrue(router.accepts_port(53)) self.assertTrue(not router.accepts_port(44)) self.assertTrue(router.accepts_port(989)) self.assertTrue(router.accepts_port(990)) self.assertTrue(not router.accepts_port(991)) self.assertTrue(not router.accepts_port(988)) def test_router_with_ipv6_address(self): self.state._update_network_status("""ns/all= r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0 a [2001:0:0:0::0]:4321 s Fast Guard Running Stable Named Valid w Bandwidth=51500 p reject 1-65535""") self.assertEqual( len(self.state.routers_by_name['PPrivCom012'][0].ip_v6), 1) self.assertEqual(self.state.routers_by_name['PPrivCom012'][0].ip_v6[0], '[2001:0:0:0::0]:4321') def test_invalid_routers(self): try: self.state._update_network_status('''ns/all= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888 .''') self.fail() except RuntimeError as e: self.assertTrue('"s "' in str(e)) def test_routers_no_policy(self): """ ensure we can parse a router descriptor which has no p line """ self.state._update_network_status('''ns/all= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=518000 r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888 .''') self.assertTrue('fake' in self.state.routers.keys()) self.assertTrue('PPrivCom012' in self.state.routers.keys()) def test_routers_no_bandwidth(self): """ ensure we can parse a router descriptor which has no w line """ self.state._update_network_status('''ns/all= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888 .''') self.assertTrue('fake' in self.state.routers.keys()) self.assertTrue('PPrivCom012' in self.state.routers.keys()) def test_router_factory(self): self.state._update_network_status('''ns/all= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888 r fake YxxmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof w Bandwidth=543000 p accept 43,53 .''') self.assertTrue( '$624926802351575FF7E4E3D60EFA3BFB56E67E8A' in self.state.routers) r = self.state.routers['$624926802351575FF7E4E3D60EFA3BFB56E67E8A'] self.assertEqual(r.controller, self.state.protocol) self.assertEqual(r.bandwidth, 518000) self.assertEqual(len(self.state.routers_by_name['fake']), 2) # now we do an update self.state._update_network_status('''ns/all= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof Authority w Bandwidth=543000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888 .''') self.assertEqual(r.bandwidth, 543000) def test_empty_stream_update(self): self.state._stream_update('''stream-status=''') def test_addrmap(self): self.state._addr_map( 'example.com 127.0.0.1 "2012-01-01 00:00:00" EXPIRES=NEVER') def test_double_newconsensus(self): """ The arrival of a second NEWCONSENSUS event causes parsing errors. """ # bootstrap the TorState so we can send it a "real" 650 # update self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send(b"250+ns/all=") self.send(b".") self.send(b"250 OK") self.send(b"250+circuit-status=") self.send(b".") self.send(b"250 OK") self.send(b"250-stream-status=") self.send(b"250 OK") self.send(b"250-address-mappings/all=") self.send(b'250 OK') for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send(b"250-entry-guards=") self.send(b"250 OK") self.send(b"250 OK") # state is now bootstrapped, we can send our NEWCONSENSUS update self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0 s Fast Guard Running Stable Valid w Bandwidth=166 p reject 1-65535 . 650 OK '''.split(b'\n'))) self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0 s Fast Guard Running Stable Valid w Bandwidth=166 p reject 1-65535 . 650 OK '''.split(b'\n'))) self.assertTrue('Unnamed' in self.state.routers) self.assertTrue( '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers) def test_NEWCONSENSUS_ends_with_OK_on_w(self): """ The arrival of a second NEWCONSENSUS event causes parsing errors. """ # bootstrap the TorState so we can send it a "real" 650 # update self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send(b"250+ns/all=") self.send(b".") self.send(b"250 OK") self.send(b"250+circuit-status=") self.send(b".") self.send(b"250 OK") self.send(b"250-stream-status=") self.send(b"250 OK") self.send(b"250-address-mappings/all=") self.send(b"250 OK") for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send(b"250-entry-guards=") self.send(b"250 OK") self.send(b"250 OK") # state is now bootstrapped, we can send our NEWCONSENSUS update self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0 s Fast Guard Running Stable Valid w Bandwidth=166 . 650 OK '''.split(b'\n'))) self.assertTrue('Unnamed' in self.state.routers) self.assertTrue( '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers) def test_NEWCONSENSUS_ends_with_OK_on_s(self): """ The arrival of a second NEWCONSENSUS event causes parsing errors. """ # bootstrap the TorState so we can send it a "real" 650 # update self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send(b"250+ns/all=") self.send(b".") self.send(b"250 OK") self.send(b"250+circuit-status=") self.send(b".") self.send(b"250 OK") self.send(b"250-stream-status=") self.send(b"250 OK") self.send(b"250-address-mappings/all=") self.send(b"250 OK") for ignored in self.state.event_map.items(): self.send(b"250 OK") self.send(b"250-entry-guards=") self.send(b"250 OK") self.send(b"250 OK") # state is now bootstrapped, we can send our NEWCONSENSUS update self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0 s Fast Guard Running Stable Valid . 650 OK '''.split(b'\n'))) self.assertTrue('Unnamed' in self.state.routers) self.assertTrue( '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers) def test_stream_create(self): self.state._stream_update('1610 NEW 0 1.2.3.4:56') self.assertTrue(1610 in self.state.streams) def test_stream_destroy(self): self.state._stream_update('1610 NEW 0 1.2.3.4:56') self.assertTrue(1610 in self.state.streams) self.state._stream_update( "1610 FAILED 0 www.example.com:0 REASON=DONE REMOTE_REASON=FAILED") self.assertTrue(1610 not in self.state.streams) def test_stream_detach(self): circ = FakeCircuit(1) circ.state = 'BUILT' self.state.circuits[1] = circ self.state._stream_update('1610 NEW 0 1.2.3.4:56') self.assertTrue(1610 in self.state.streams) self.state._stream_update("1610 SUCCEEDED 1 4.3.2.1:80") self.assertEqual(self.state.streams[1610].circuit, circ) self.state._stream_update( "1610 DETACHED 0 www.example.com:0 REASON=DONE REMOTE_REASON=FAILED" ) self.assertEqual(self.state.streams[1610].circuit, None) def test_stream_listener(self): self.protocol._set_valid_events( 'CIRC STREAM ORCONN BW DEBUG INFO NOTICE WARN ERR NEWDESC ADDRMAP AUTHDIR_NEWDESCS DESCCHANGED NS STATUS_GENERAL STATUS_CLIENT STATUS_SERVER GUARD STREAM_BW CLIENTS_SEEN NEWCONSENSUS BUILDTIMEOUT_SET' ) self.state._add_events() for ignored in self.state.event_map.items(): self.send(b"250 OK") expected = [ ('new', {}), ] listen = StreamListener(expected) self.send( b"650 STREAM 77 NEW 0 www.yahoo.cn:80 SOURCE_ADDR=127.0.0.1:54315 PURPOSE=USER" ) self.state.add_stream_listener(listen) self.assertEqual(1, len(self.state.streams.values())) self.assertTrue( listen in list(self.state.streams.values())[0].listeners) self.assertEqual(len(self.state.streams), 1) self.assertEqual(len(listen.expected), 1) self.send( b"650 STREAM 78 NEW 0 www.yahoo.cn:80 SOURCE_ADDR=127.0.0.1:54315 PURPOSE=USER" ) self.assertEqual(len(self.state.streams), 2) self.assertEqual(len(listen.expected), 0) def test_build_circuit(self): class FakeRouter: def __init__(self, i): self.id_hex = i self.flags = [] path = [] for x in range(3): path.append(FakeRouter("$%040d" % x)) # can't just check flags for guard status, need to know if # it's in the running Tor's notion of Entry Guards path[0].flags = ['guard'] self.state.build_circuit(path, using_guards=True) self.assertEqual( self.transport.value(), b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n' ) # should have gotten a warning about this not being an entry # guard self.assertEqual(len(self.flushWarnings()), 1) def test_build_circuit_no_routers(self): self.state.build_circuit() self.assertEqual(self.transport.value(), b'EXTENDCIRCUIT 0\r\n') def test_build_circuit_unfound_router(self): self.state.build_circuit( routers=[b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'], using_guards=False) self.assertEqual( self.transport.value(), b'EXTENDCIRCUIT 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n') def circuit_callback(self, circ): self.assertTrue(isinstance(circ, Circuit)) self.assertEqual(circ.id, 1234) def test_build_circuit_final_callback(self): class FakeRouter: def __init__(self, i): self.id_hex = i self.flags = [] path = [] for x in range(3): path.append(FakeRouter("$%040d" % x)) # can't just check flags for guard status, need to know if # it's in the running Tor's notion of Entry Guards path[0].flags = ['guard'] # FIXME TODO we should verify we get a circuit_new event for # this circuit d = self.state.build_circuit(path, using_guards=True) d.addCallback(self.circuit_callback) self.assertEqual( self.transport.value(), b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n' ) self.send(b"250 EXTENDED 1234") # should have gotten a warning about this not being an entry # guard self.assertEqual(len(self.flushWarnings()), 1) return d def test_build_circuit_error(self): """ tests that we check the callback properly """ try: self.state._find_circuit_after_extend("FOO 1234") self.assertTrue(False) except RuntimeError as e: self.assertTrue('Expected EXTENDED' in str(e)) def test_listener_mixins(self): self.assertTrue(verifyClass(IStreamListener, StreamListenerMixin)) self.assertTrue(verifyClass(ICircuitListener, CircuitListenerMixin)) def test_build_circuit_timedout(self): class FakeRouter: def __init__(self, i): self.id_hex = i self.flags = [] path = [] for x in range(3): path.append(FakeRouter("$%040d" % x)) # can't just check flags for guard status, need to know if # it's in the running Tor's notion of Entry Guards path[0].flags = ['guard'] # FIXME TODO we should verify we get a circuit_new event for # this circuit timeout = 10 clock = task.Clock() d = build_timeout_circuit(self.state, clock, path, timeout, using_guards=False) clock.advance(10) def check_for_timeout_error(f): self.assertTrue(isinstance(f.type(), CircuitBuildTimedOutError)) d.addErrback(check_for_timeout_error) return d def test_build_circuit_timeout_after_progress(self): """ Similar to above but we timeout after Tor has ack'd our circuit-creation attempt, but before reaching BUILT. """ class FakeRouter: def __init__(self, i): self.id_hex = i self.flags = [] class FakeCircuit(Circuit): def close(self): return defer.succeed(None) path = [] for x in range(3): path.append(FakeRouter("$%040d" % x)) def fake_queue(cmd): self.assertTrue(cmd.startswith('EXTENDCIRCUIT 0')) return defer.succeed("EXTENDED 1234") queue_command = patch.object(self.protocol, 'queue_command', fake_queue) circuit_factory = patch.object(self.state, 'circuit_factory', FakeCircuit) with queue_command, circuit_factory: timeout = 10 clock = task.Clock() d = build_timeout_circuit(self.state, clock, path, timeout, using_guards=False) clock.advance(timeout + 1) def check_for_timeout_error(f): self.assertTrue(isinstance(f.type(), CircuitBuildTimedOutError)) d.addErrback(check_for_timeout_error) return d def test_build_circuit_not_timedout(self): class FakeRouter: def __init__(self, i): self.id_hex = i self.flags = [] path = [] for x in range(3): path.append(FakeRouter("$%040d" % x)) path[0].flags = ['guard'] timeout = 10 clock = task.Clock() d = build_timeout_circuit(self.state, clock, path, timeout, using_guards=True) d.addCallback(self.circuit_callback) self.assertEqual( self.transport.value(), b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n' ) self.send(b"250 EXTENDED 1234") # we can't just .send(b'650 CIRC 1234 BUILT') this because we # didn't fully hook up the protocol to the state, e.g. via # post_bootstrap etc. self.state.circuits[1234].update(['1234', 'BUILT']) # should have gotten a warning about this not being an entry # guard self.assertEqual(len(self.flushWarnings()), 1) return d
class StateTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.state = TorState(self.protocol) # avoid spew in trial logs; state prints this by default self.state._attacher_error = lambda f: f self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_close_stream_with_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] def attach_stream(self, stream, circuits): self.streams.append(stream) return None attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE") def test_attacher_error_handler(self): # make sure error-handling "does something" that isn't blowing up with patch('sys.stdout') as fake_stdout: TorState(self.protocol)._attacher_error( Failure(RuntimeError("quote"))) def test_stream_update(self): # we use a circuit ID of 0 so it doesn't try to look anything # up but it's not really correct to have a SUCCEEDED w/o a # valid circuit, I don't think self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80') self.assertTrue(1610 in self.state.streams) def test_single_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status( 'stream-status=123 SUCCEEDED 496 www.example.com:6667') self.assertEqual(len(self.state.streams), 1) def test_multiple_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status('\r\n'.join([ 'stream-status=', '123 SUCCEEDED 496 www.example.com:6667', '124 SUCCEEDED 496 www.example.com:6667', ])) self.assertEqual(len(self.state.streams), 2) def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") @defer.inlineCallbacks def test_bootstrap_callback(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' from test_torconfig import FakeControlProtocol protocol = FakeControlProtocol([ "ns/all=", # ns/all "", # circuit-status "", # stream-status "", # address-mappings/all "entry-guards=\r\n$0000000000000000000000000000000000000000=name up\r\n$1111111111111111111111111111111111111111=foo up\r\n$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00\r\n", # entry-guards "99999", # process/pid "??", # ip-to-country/0.0.0.0 ]) state = yield TorState.from_protocol(protocol) self.assertEqual(len(state.entry_guards), 2) self.assertTrue( '$0000000000000000000000000000000000000000' in state.entry_guards) self.assertTrue( '$1111111111111111111111111111111111111111' in state.entry_guards) self.assertEqual(len(state.unusable_entry_guards), 1) self.assertTrue('$9999999999999999999999999999999999999999' in state.unusable_entry_guards[0]) def test_bootstrap_existing_addresses(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250+circuit-status=") self.send(".") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('www.example.com 127.0.0.1 "2012-01-01 00:00:00"') self.send('subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"') self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertEqual(len(self.state.addrmap.addr), 4) self.assertTrue('www.example.com' in self.state.addrmap.addr) self.assertTrue('subdomain.example.com' in self.state.addrmap.addr) self.assertTrue('10.0.0.0' in self.state.addrmap.addr) self.assertTrue('127.0.0.1' in self.state.addrmap.addr) self.assertEqual('127.0.0.1', self.state.addrmap.find('www.example.com').ip) self.assertEqual('www.example.com', self.state.addrmap.find('127.0.0.1').name) self.assertEqual('10.0.0.0', self.state.addrmap.find('subdomain.example.com').ip) self.assertEqual('subdomain.example.com', self.state.addrmap.find('10.0.0.0').name) return d def test_bootstrap_single_existing_circuit(self): ''' test with exactly one circuit. should probably test with 2 as well, since there was a bug with the handling of just one. ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250-circuit-status=123 BUILT PURPOSE=GENERAL") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertTrue(self.state.find_circuit(123)) self.assertEquals(len(self.state.circuits), 1) return d def test_unset_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def attach_stream(self, stream, circuits): return None fr = FakeReactor(self) self.state.set_attacher(MyAttacher(), fr) self.send("250 OK") self.state.set_attacher(None, fr) self.send("250 OK") self.assertEqual( self.transport.value(), 'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF' ' __LeaveStreamsUnattached=0\r\n') def test_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] self.answer = None def attach_stream(self, stream, circuits): self.streams.append(stream) return self.answer attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send( "650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 0') # we should totally ignore .exit URIs attacher.streams = [] self.send( "650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 0) self.assertEqual(len(self.protocol.commands), 1) # we should NOT ignore .onion URIs attacher.streams = [] self.send( "650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 1) self.assertEqual(len(self.protocol.commands), 2) self.assertEqual(self.protocol.commands[1][1], 'ATTACHSTREAM 3 0') # normal attach circ = FakeCircuit(1) circ.state = 'BUILT' self.state.circuits[1] = circ attacher.answer = circ self.send( "650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME" ) self.assertEqual(len(attacher.streams), 2) self.assertEqual(len(self.protocol.commands), 3) self.assertEqual(self.protocol.commands[2][1], 'ATTACHSTREAM 4 1') def test_attacher_defer(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): self.streams.append(stream) return defer.succeed(self.answer) self.state.circuits[1] = FakeCircuit(1) self.state.circuits[1].state = 'BUILT' attacher = MyAttacher(self.state.circuits[1]) self.state.set_attacher(attacher, FakeReactor(self)) # boilerplate to finish enough set-up in the protocol so it # works events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send( "650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER" ) self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1') @defer.inlineCallbacks def test_attacher_errors(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): return self.answer self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(FakeCircuit(2)) self.state.set_attacher(attacher, FakeReactor(self)) stream = Stream(self.state) stream.id = 3 msg = '' try: yield self.state._maybe_attach(stream) except Exception, e: msg = str(e) self.assertTrue('circuit unknown' in msg) attacher.answer = self.state.circuits[1] msg = '' try: yield self.state._maybe_attach(stream) except Exception, e: msg = str(e)