class TestIronhouse(TestCase): def setUp(self): self.sk = '06391888e37a48cef1ded85a375490df4f9b2c74f7723e88c954a055f3d2685a' self.vk = '82540bb5a9c84162214c5540d6e43be49bbfe19cf49685660cab608998a65144' self.private_key = 'f0ca3d349e56e419e72f11c1fd734ae929a483f9490907d2ded554d9f794f361' self.public_key = '73619fa1464ce16802b480a0fd7868ffcce0f7285050a927a07ef1ffdd34c162' self.curve_public_key = b'B77YmmOI=O0<)GJ@DJ2Q+&5jzp/absPNMCh?88@S' self.ironhouse = Ironhouse(self.sk, wipe_certs=True, auth_validate=auth_validate) self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def test_assert_paths(self): self.assertEqual(self.ironhouse.base_dir, 'certs/ironhouse', 'key folder is incorrect') self.assertEqual(self.ironhouse.keys_dir, 'certs/ironhouse/certificates', 'keys dir is incorrect') self.assertEqual(self.ironhouse.public_keys_dir, 'certs/ironhouse/public_keys', 'public dir is incorrect') self.assertEqual(self.ironhouse.secret_keys_dir, 'certs/ironhouse/private_keys', 'secret dir is incorrect') self.assertEqual(self.ironhouse.secret_file, 'certs/ironhouse/private_keys/ironhouse.key_secret', 'secret_file is incorrect') def test_generate_certificates_failed(self): self.ironhouse.wipe_certs = False shutil.rmtree(self.ironhouse.base_dir) self.ironhouse.generate_certificates(self.sk) self.assertFalse(listdir(self.ironhouse.public_keys_dir), 'public keys dir should not be created') self.assertFalse(listdir(self.ironhouse.secret_keys_dir), 'secret keys dir should not be created') self.assertTrue( listdir(self.ironhouse.keys_dir) == [], 'certificate keys dir should not be created') def test_generate_certificates(self): self.ironhouse.generate_certificates(self.sk) self.assertTrue(listdir(self.ironhouse.public_keys_dir), 'public keys dir not created') self.assertTrue(listdir(self.ironhouse.secret_keys_dir), 'secret keys dir not created') self.assertTrue( listdir(self.ironhouse.keys_dir) == [], 'certificate keys is not empty') self.assertTrue(exists(self.ironhouse.secret_file), 'secret keys not created') self.assertEqual(self.private_key, decode(self.ironhouse.secret).hex(), 'secret key generation is incorrect') self.assertEqual(self.public_key, decode(self.ironhouse.public_key).hex(), 'public key generation is incorrect') def test_vk2pk(self): self.assertEqual( decode(self.ironhouse.vk2pk(self.vk)).hex(), self.public_key, 'conversion of vk to pk failed') def test_generate_from_private_key(self): self.ironhouse.create_from_private_key(self.private_key) self.assertTrue(listdir(self.ironhouse.public_keys_dir), 'public keys dir not created') self.assertTrue(listdir(self.ironhouse.secret_keys_dir), 'secret keys dir not created') self.assertTrue(listdir(self.ironhouse.keys_dir), 'certificate keys dir not created') self.assertTrue(exists(self.ironhouse.secret_file), 'secret keys not created') self.assertEqual(self.private_key, decode(self.ironhouse.secret).hex(), 'secret key generation is incorrect') self.assertEqual(self.public_key, decode(self.ironhouse.public_key).hex(), 'public key generation is incorrect') def test_generate_from_public_key(self): self.ironhouse.create_from_public_key(encode(self.public_key.encode())) self.assertTrue(listdir(self.ironhouse.public_keys_dir), 'public keys dir not created') self.assertTrue( exists('{}/ironhouse.key'.format(self.ironhouse.public_keys_dir)), 'public key not generated') def test_secure_context_async(self): ctx, auth = self.ironhouse.secure_context(async=True) self.assertIsInstance(ctx, zmq.asyncio.Context, 'asynchronous context created incorrectly') self.assertIsInstance(auth, AsyncioAuthenticator, 'synchronous auth object created incorrectly') auth.stop() def test_secure_context_sync(self): ctx, auth = self.ironhouse.secure_context(async=False) self.assertIsInstance(ctx, zmq.Context, 'synchronous context created incorrectly') self.assertIsInstance(auth, ThreadAuthenticator, 'synchronous auth object created incorrectly') auth.stop() def test_secure_socket_sync(self): ctx, auth = self.ironhouse.secure_context(async=False) sock = ctx.socket(zmq.REP) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure REP socket') sock = ctx.socket(zmq.REQ) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure REQ socket') sock = ctx.socket(zmq.PUSH) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PUSH socket') sock = ctx.socket(zmq.PULL) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PULL socket') sock = ctx.socket(zmq.DEALER) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure DEALER socket') sock = ctx.socket(zmq.ROUTER) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure ROUTER socket') sock = ctx.socket(zmq.PUB) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PUB socket') sock = ctx.socket(zmq.SUB) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure SUB socket') sec_sock.close() auth.stop() def test_secure_socket_async(self): ctx, auth = self.ironhouse.secure_context(async=True) sock = ctx.socket(zmq.REP) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure REP socket') sock = ctx.socket(zmq.REQ) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure REQ socket') sock = ctx.socket(zmq.PUSH) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PUSH socket') sock = ctx.socket(zmq.PULL) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PULL socket') sock = ctx.socket(zmq.DEALER) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure DEALER socket') sock = ctx.socket(zmq.ROUTER) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure ROUTER socket') sock = ctx.socket(zmq.PUB) sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure PUB socket') sock = ctx.socket(zmq.SUB) sec_sock = self.ironhouse.secure_socket( sock, curve_serverkey=self.curve_public_key) self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket, 'unable to secure SUB socket') sec_sock.close() auth.stop() def test_reconfigure_curve(self): ctx, auth = self.ironhouse.secure_context(async=True) sock = ctx.socket(zmq.REP) sec_sock = self.ironhouse.secure_socket(sock) self.assertIn(self.curve_public_key, auth.certs['*'].keys(), 'cannot find cert in auth') sec_sock.close() auth.stop() def test_secure_server(self): async def send_async_sec(): ip = '127.0.0.1' port = 4523 client = self.ironhouse.ctx.socket(zmq.REQ) client = self.ironhouse.secure_socket(client, self.curve_public_key) client.connect('tcp://{}:{}'.format(ip, port)) client.send(self.vk.encode()) msg = await client.recv() client.close() self.ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.ironhouse.setup_secure_server() self.assertIsInstance(self.ironhouse.ctx, zmq.Context, 'asynchronous context created incorrectly') self.assertIsInstance(self.ironhouse.sec_sock, zmq.sugar.socket.Socket, 'unable to secure a socket') self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate(self): port = 5523 async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertTrue(authorized) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.stop() self.fake = genkeys( '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.create_from_public_key(self.curve_public_key) self.fake_ironhouse.setup_secure_server() self.ironhouse.create_from_public_key(self.fake['curve_key']) self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate_self(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.curve_public_key, '127.0.0.1') self.assertTrue(authorized) self.ironhouse.cleanup() self.loop.stop() self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate_fail(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( b'A/c=Kn2)aHRI*>fK-{v*r^YCyXJ//3.CGQQC@A9J', '127.0.0.1') self.assertFalse(authorized) self.ironhouse.cleanup() self.loop.stop() self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertTrue(authorized) self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.stop() def auth_validate_fake(vk): self.validated = True return True def auth_validate(vk): return vk == 'b9284b28589523f055ae5b54c98b0b904a1df3b0be5d546d30208d0516e71aa0' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.create_from_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.fake_ironhouse.setup_secure_server() self.ironhouse.create_from_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_default(self): port = 5523 async def send_async_sec(): authorized = await self.dih.authenticate(self.a['curve_key'], '127.0.0.1', port) self.assertTrue(authorized) self.aih.cleanup() self.dih.cleanup() self.loop.stop() self.a = genkeys( '5664ec7306cc22e56820ae988b983bdc8ebec8246cdd771cfee9671299e98e3c') self.aih = Ironhouse(self.a['sk'], wipe_certs=True, auth_port=port, keyname='a') self.aih.create_from_public_key(self.curve_public_key) self.aih.setup_secure_server() self.dih = Ironhouse(self.sk, wipe_certs=True) self.dih.create_from_public_key(self.a['curve_key']) self.dih.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_invalid_public_key(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( b'ack', '127.0.0.1', 1234) self.assertFalse(authorized) self.ironhouse.cleanup() self.loop.stop() self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_fail_validate(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertFalse(authorized) self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.stop() def auth_validate_fake(vk): self.validated = True return True def auth_validate(vk): return vk == b'catastrophe' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.create_from_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.fake_ironhouse.setup_secure_server() self.ironhouse.create_from_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_fail_timeout(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertFalse(authorized) self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.stop() def auth_validate_fake(vk): self.validated = True return False def auth_validate(vk): return vk == b'catastrophe' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.create_from_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.fake_ironhouse.setup_secure_server() self.ironhouse.create_from_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_cleanup(self): async def delay(): await asyncio.sleep(0.1) del self.ironhouse self.loop.stop() self.loop.run_until_complete(asyncio.ensure_future(delay())) def tearDown(self): self.loop.close()
class TestServer(TestIronhouseBase): def test_secure_server(self): ip = '127.0.0.1' port = 4523 async def send_async_sec(): client = self.ironhouse.ctx.socket(zmq.REQ) client = self.ironhouse.secure_socket(client, self.secret, self.curve_public_key, self.curve_public_key) client.connect('tcp://{}:{}'.format(ip, port)) client.send(self.vk.encode()) client.close() self.ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.ironhouse.setup_secure_server() self.assertIsInstance(self.ironhouse.ctx, zmq.Context, 'asynchronous context created incorrectly') self.assertIsInstance(self.ironhouse.sec_sock, zmq.sugar.socket.Socket, 'unable to secure a socket') self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate(self): port = 5523 async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertTrue(authorized) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.fake = genkeys( '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.setup_secure_server() self.fake_ironhouse.add_public_key(self.curve_public_key) self.ironhouse.setup_secure_server() self.ironhouse.add_public_key(self.fake['curve_key']) self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate_self(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.curve_public_key, '127.0.0.1') self.assertTrue(authorized) self.ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate_then_reject(self): port = 5523 async def send_async_sec(): await self.ironhouse.authenticate(self.fake['curve_key'], '127.0.0.1', port) self.ironhouse.remove_public_key(self.fake['curve_key']) self.assertFalse( self.ironhouse.authorized_keys[self.fake['curve_key']]) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.fake = genkeys( '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.setup_secure_server() self.fake_ironhouse.add_public_key(self.curve_public_key) self.ironhouse.setup_secure_server() self.ironhouse.add_public_key(self.fake['curve_key']) self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_authenticate_fail(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( b'A/c=Kn2)aHRI*>fK-{v*r^YCyXJ//3.CGQQC@A9J', '127.0.0.1') self.assertEqual(authorized, 'no_reply') self.ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertTrue( self.ironhouse.authorized_keys[self.fake['curve_key']]) self.assertTrue(authorized) self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) def auth_validate_fake(vk): self.validated = True return True def auth_validate(vk): return vk == 'b9284b28589523f055ae5b54c98b0b904a1df3b0be5d546d30208d0516e71aa0' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.setup_secure_server() self.fake_ironhouse.add_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.ironhouse.setup_secure_server() self.ironhouse.add_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_default(self): port = 5523 async def send_async_sec(): authorized = await self.dih.authenticate(self.a['curve_key'], '127.0.0.1', port) self.assertTrue(authorized) self.aih.cleanup() self.dih.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.a = genkeys( '5664ec7306cc22e56820ae988b983bdc8ebec8246cdd771cfee9671299e98e3c') self.aih = Ironhouse(self.a['sk'], wipe_certs=True, auth_port=port, keyname='a') self.aih.setup_secure_server() self.aih.add_public_key(self.curve_public_key) self.dih = Ironhouse(self.sk, wipe_certs=True) self.dih.setup_secure_server() self.dih.add_public_key(self.a['curve_key']) self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_invalid_public_key(self): async def send_async_sec(): authorized = await self.ironhouse.authenticate( b'ack', '127.0.0.1', 1234) self.assertEqual(authorized, 'invalid') self.ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) self.ironhouse.setup_secure_server() self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_fail_validate(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertEqual(authorized, 'unauthorized') self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) def auth_validate_fake(vk): self.validated = True return True def auth_validate(vk): return vk == b'catastrophe' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.setup_secure_server() self.fake_ironhouse.add_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.ironhouse.setup_secure_server() self.ironhouse.add_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_auth_validate_fail_timeout(self): port = 5523 self.validated = False async def send_async_sec(): authorized = await self.ironhouse.authenticate( self.fake['curve_key'], '127.0.0.1', port) self.assertEqual(authorized, 'no_reply') self.assertTrue(self.validated) self.ironhouse.cleanup() self.fake_ironhouse.cleanup() self.loop.call_soon_threadsafe(self.loop.stop) def auth_validate_fake(vk): self.validated = True return False def auth_validate(vk): return vk == b'catastrophe' self.fake = genkeys( '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78') self.fake_ironhouse = Ironhouse(self.fake['sk'], wipe_certs=True, auth_validate=auth_validate, auth_port=port, keyname='fake') self.fake_ironhouse.setup_secure_server() self.fake_ironhouse.add_public_key(self.curve_public_key) self.fake_ironhouse.auth_validate = auth_validate_fake self.ironhouse.setup_secure_server() self.ironhouse.add_public_key(self.fake['curve_key']) self.ironhouse.auth_validate = auth_validate self.loop.run_until_complete(asyncio.ensure_future(send_async_sec())) def test_cleanup(self): async def delay(): await asyncio.sleep(0.1) del self.ironhouse self.loop.call_soon_threadsafe(self.loop.stop) self.loop.run_until_complete(asyncio.ensure_future(delay())) def tearDown(self): self.loop.close()
class Network(object): """ High level view of a node instance. This is the object that should be created to start listening as an active node on the network. """ protocol_class = KademliaProtocol def __init__(self, ksize=20, alpha=3, node_id=None, discovery_mode='neighborhood', loop=None, max_peers=64, network_port=None, public_ip=None, *args, **kwargs): """ Create a server instance. This will start listening on the given port. Args: ksize (int): The k parameter from the paper alpha (int): The alpha parameter from the paper node_id: The id for this node on the network. """ self.loop = loop if loop else asyncio.get_event_loop() asyncio.set_event_loop(self.loop) self.vkcache = Bidict() self.authorized_node = {} self.ksize = ksize self.alpha = alpha self.transport = None self.protocol = None self.refresh_loop = None self.save_state_loop = None self.max_peers = max_peers self.network_port = network_port self.heartbeat_port = self.network_port + HEARTBEAT_PORT_OFFSET self.daemon = kwargs.get('daemon') self.ironhouse = Ironhouse(auth_port=self.network_port + AUTH_PORT_OFFSET, *args, **kwargs) self.node = Node(node_id=digest(self.ironhouse.vk), public_key=self.ironhouse.public_key, ip=public_ip or os.getenv('HOST_IP', '127.0.0.1'), port=self.network_port) self.setup_stethoscope() self.ironhouse.setup_secure_server() self.listen() self.saveStateRegularly('state.tmp') async def authenticate(self, node): if len([ n for n in self.bootstrappableNeighbors() if n == (node.ip, node.port, node.public_key) ]) == 1: log.debug('Node {}:{} is already a neighbor'.format( node.ip, node.port)) return True authorized = await self.ironhouse.authenticate( node.public_key, node.ip, node.port + AUTH_PORT_OFFSET) log.debug('{}:{} is {}'.format(node.ip, node.port, authorized)) if authorized == 'authorized': self.protocol.router.addContact(node) self.connect_to_neighbor(node) return True else: return False def setup_stethoscope(self): socket.setdefaulttimeout(0.1) self.stethoscope_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.stethoscope_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.stethoscope_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.stethoscope_sock.setblocking(0) self.stethoscope_sock.bind(('0.0.0.0', self.heartbeat_port)) self.stethoscope_sock.listen(self.max_peers) self.stethoscope_future = asyncio.ensure_future(self.stethoscope()) async def stethoscope(self): self.connections = {} self.poll = poll() log.debug('Listening to heartbeats on {}...'.format( self.heartbeat_port)) try: while True: events = self.poll.poll(1) for fileno, event in events: if event & (POLLIN): conn, node = self.connections[fileno] addr = (node.ip, node.port + HEARTBEAT_PORT_OFFSET) try: log.debug('reconnecting {} - {}'.format( self.network_port, addr)) conn.connect(addr) except Exception as e: log.debug(e.args) if e.args[1] == 'Connection reset by peer': log.info( "Client ({}, {}) disconnected from {}". format(*addr, self.node)) del self.connections[fileno] if self.vkcache.get(node.ip): del self.vkcache[node.ip] self.protocol.router.removeContact(node) self.poll.unregister(fileno) conn.close() self.connection_drop() await asyncio.sleep(0.1) except asyncio.CancelledError: log.info('Network shutting down gracefully.') def connection_drop(self): if self.daemon: callback = ReactorCommand.create_callback( callback=StateInput.CONN_DROPPED, vk=self.ironhouse.vk, ip=self.node.ip) log.debug( "Sending callback failure to mainthread {}".format(callback)) self.daemon.socket.send(callback.serialize()) def connect_to_neighbor(self, node): if self.node.id == node.id: return self.ironhouse.create_from_public_key(node.public_key) self.ironhouse.reconfigure_curve() addr = (node.ip, node.port + HEARTBEAT_PORT_OFFSET) conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connections[conn.fileno()] = (conn, node) try: conn.connect(addr) self.poll.register(conn.fileno(), POLLIN) log.info("[CLIENT SIDE] Client ({}, {}) connected".format(*addr)) return conn except Exception as e: del self.connections[conn.fileno()] conn.close() def lookup_ip_in_cache(self, vk): ip = self.vkcache.get(vk) if ip: log.debug('Found ip {} in cache'.format(ip)) return ip async def lookup_ip(self, node_key): cache_node = self.lookup_ip_in_cache(node_key) if cache_node: return cache_node, True node_id = digest(node_key) if node_id == self.node.id: return self.node nearest = self.protocol.router.findNeighbors(self.node) spider = NodeSpiderCrawl(self.protocol, self.node, nearest, self.ksize, self.alpha) log.debug("Starting lookup for node_key {}".format(node_key)) res_node = await spider.find(node_id=node_id) if type(res_node) == list: res_node = None log.debug('{} resolves to {}'.format(node_key, res_node)) if res_node != None: self.vkcache[node_key] = res_node.ip pk = self.ironhouse.vk2pk(node_key) return res_node, False def stop(self): if self.transport is not None: self.transport.close() if not self.refresh_future.done(): self.refresh_future.set_result('done') if self.refresh_loop: self.refresh_loop.cancel() if self.save_state_loop: self.save_state_loop.cancel() for fileno in self.connections: conn, node = self.connections[fileno] try: self.poll.unregister(fileno) except: log.debug('Already unregistered') conn.close() log.debug('Closed a previously opened connection') self.ironhouse.cleanup() try: self.poll.unregister(self.stethoscope_sock.fileno()) except: log.debug('Stehoscope is already unregistered') self.stethoscope_sock.close() try: self.poll.close() except: pass #log.debug('Not epoll object, no need to close.') self.stethoscope_future.cancel() def _create_protocol(self): return self.protocol_class(self.node, self.ksize, self) def listen(self, port=None, interface='0.0.0.0'): """ Start listening on the given port. Provide interface="::" to accept ipv6 address """ port = self.network_port listen = self.loop.create_datagram_endpoint(self._create_protocol, local_addr=(interface, port)) log.info("Listening to kade network on %s:%i", interface, port) self.transport, self.protocol = self.loop.run_until_complete(listen) # finally, schedule refreshing table self.refresh_table() def refresh_table(self): self.refresh_loop = self.loop.call_later(3600, self.refresh_table) self.refresh_future = asyncio.ensure_future(self._refresh_table()) return self.refresh_future async def _refresh_table(self): """ Refresh buckets that haven't had any lookups in the last hour (per section 2.3 of the paper). """ log.debug("Refreshing routing table") ds = [] for node_id in self.protocol.getRefreshIDs(): node = Node(node_id=node_id) nearest = self.protocol.router.findNeighbors(node, self.alpha) spider = NodeSpiderCrawl(self.protocol, node, nearest, self.ksize, self.alpha) ds.append(spider.find()) # do our crawling await asyncio.gather(*ds) def bootstrappableNeighbors(self): """ Get a :class:`list` of (ip, port) :class:`tuple` pairs suitable for use as an argument to the bootstrap method. The server should have been bootstrapped already - this is just a utility for getting some neighbors and then storing them if this server is going down for a while. When it comes back up, the list of nodes can be used to bootstrap. """ neighbors = self.protocol.router.findNeighbors(self.node) return [tuple(n)[-3:] for n in neighbors] async def bootstrap(self, addrs): """ Bootstrap the server by connecting to other known nodes in the network. Args: addrs: A `list` of (ip, port) `tuple` pairs. Note that only IP addresses are acceptable - hostnames will cause an error. """ log.debug("Attempting to bootstrap node with %i initial contacts", len(addrs)) cos = list(map(self.bootstrap_node, addrs)) gathered = await asyncio.gather(*cos) nodes = [node for node in gathered if node is not None] if len(nodes) == 0: log.warning( 'Unable to find/authenticate with any nodes in the network') return [] spider = NodeSpiderCrawl(self.protocol, self.node, nodes, self.ksize, self.alpha) res = await spider.find() return res async def bootstrap_node(self, addr): result = await self.protocol.ping(addr, self.node.public_key, self.node.id) if result[0]: node_id, public_key = result[1] node = Node(node_id, ip=addr[0], port=addr[1], public_key=public_key) authorized = await self.authenticate(node) if authorized == True: return node return None def saveState(self, fname): """ Save the state of this node (the alpha/ksize/id/immediate neighbors) to a cache file with the given fname. """ log.info("Saving state to %s", fname) data = { 'ksize': self.ksize, 'alpha': self.alpha, 'id': self.node.id, 'neighbors': self.bootstrappableNeighbors() } if len(data['neighbors']) == 0: log.info("No known neighbors, so not writing to cache.") return False with open(fname, 'wb+') as f: pickle.dump(data, f) return True @classmethod def loadState(self, fname): """ Load the state of this node (the alpha/ksize/id/immediate neighbors) from a cache file with the given fname. """ log.info("Loading state from %s", fname) with open(fname, 'rb') as f: data = pickle.load(f) return data def saveStateRegularly(self, fname, frequency=600): """ Save the state of node with a given regularity to the given filename. Args: fname: File name to save retularly to frequency: Frequency in seconds that the state should be saved. By default, 10 minutes. """ self.saveState(fname) self.save_state_loop = self.loop.call_later(frequency, self.saveStateRegularly, fname, frequency)