def __init__(self, env, master_server, loop): self.loop = loop self.env = env self.master_server = master_server master_server.network = self # cannot say this is a good design self.cancel_token = CancelToken("p2pserver") if env.cluster_config.P2P.BOOT_NODES: bootstrap_nodes = env.cluster_config.P2P.BOOT_NODES.split(",") else: bootstrap_nodes = [] if env.cluster_config.P2P.PRIV_KEY: privkey = keys.PrivateKey( bytes.fromhex(env.cluster_config.P2P.PRIV_KEY)) else: privkey = ecies.generate_privkey() if env.cluster_config.P2P.PREFERRED_NODES: preferred_nodes = env.cluster_config.P2P.PREFERRED_NODES.split(",") else: preferred_nodes = [] if len(str(env.quark_chain_config.NETWORK_ID)) > 4: raise Exception("NETWORK_ID too long for discovery") self.server = QuarkServer( privkey=privkey, port=env.cluster_config.P2P_PORT, network_id=env.quark_chain_config.NETWORK_ID, bootstrap_nodes=tuple( [Node.from_uri(enode) for enode in bootstrap_nodes]), preferred_nodes=[ Node.from_uri(enode) for enode in preferred_nodes ], token=self.cancel_token, max_peers=env.cluster_config.P2P.MAX_PEERS, upnp=env.cluster_config.P2P.UPNP, allow_dial_in_ratio=env.cluster_config.P2P.ALLOW_DIAL_IN_RATIO, crawling_routing_table_path=env.cluster_config.P2P. CRAWLING_ROUTING_TABLE_FILE_PATH, ) QuarkPeer.env = env SecurePeer.env = env SecurePeer.network = self SecurePeer.master_server = master_server # used in HelloCommand.peer_id which is hash256 self.self_id = privkey.public_key.to_bytes()[:32] self.ip = ipaddress.ip_address( socket.gethostbyname(socket.gethostname())) self.port = env.cluster_config.P2P_PORT
async def main(): parser = argparse.ArgumentParser() # do not use "localhost", use the private ip if you run this from EC2 parser.add_argument( "--remote", default="enode://28698cd33c5c78514ce1d8a7228e0071f341d75509dc48f12e26f9e22584740a5b6bf8a447eab8679e8744d283dd4173ddbdc52f44a7cb5ff508ecbd04b500f0@127.0.0.1:38291", type=str, ) args = parser.parse_args() factory = get_quark_peer_factory() remote = Node.from_uri(args.remote) version = await handshake_for_version(remote, factory) print(version)
async def handshake( remote: kademlia.Node, privkey: datatypes.PrivateKey, token: CancelToken ) -> Tuple[bytes, bytes, BasePreImage, BasePreImage, asyncio.StreamReader, asyncio.StreamWriter]: # noqa: E501 """ Perform the auth handshake with given remote. Returns the established secrets and the StreamReader/StreamWriter pair already connected to the remote. """ use_eip8 = False initiator = HandshakeInitiator(remote, privkey, use_eip8, token) reader, writer = await initiator.connect() opened_connections[remote.__repr__()] = (reader, writer) aes_secret, mac_secret, egress_mac, ingress_mac = await _handshake( initiator, reader, writer, token) return aes_secret, mac_secret, egress_mac, ingress_mac, reader, writer
async def _receive_handshake(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: msg = await self.wait(reader.read(ENCRYPTED_AUTH_MSG_LEN), timeout=REPLY_TIMEOUT) ip, socket, *_ = writer.get_extra_info("peername") remote_address = Address(ip, socket) self.logger.debug("Receiving handshake from %s", remote_address) got_eip8 = False try: ephem_pubkey, initiator_nonce, initiator_pubkey = decode_authentication( msg, self.privkey) except DecryptionError: # Try to decode as EIP8 got_eip8 = True msg_size = big_endian_to_int(msg[:2]) remaining_bytes = msg_size - ENCRYPTED_AUTH_MSG_LEN + 2 msg += await self.wait(reader.read(remaining_bytes), timeout=REPLY_TIMEOUT) try: ephem_pubkey, initiator_nonce, initiator_pubkey = decode_authentication( msg, self.privkey) except DecryptionError as e: self.logger.debug("Failed to decrypt handshake: %s", e) return initiator_remote = Node(initiator_pubkey, remote_address) responder = HandshakeResponder(initiator_remote, self.privkey, got_eip8, self.cancel_token) responder_nonce = numpy.random.bytes(HASH_LEN) auth_ack_msg = responder.create_auth_ack_message(responder_nonce) auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg) # Use the `writer` to send the reply to the remote writer.write(auth_ack_ciphertext) await self.wait(writer.drain()) # Call `HandshakeResponder.derive_shared_secrets()` and use return values to create `Peer` aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce=initiator_nonce, responder_nonce=responder_nonce, remote_ephemeral_pubkey=ephem_pubkey, auth_init_ciphertext=msg, auth_ack_ciphertext=auth_ack_ciphertext, ) connection = PeerConnection( reader=reader, writer=writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) # Create and register peer in peer_pool peer = self.peer_pool.get_peer_factory().create_peer( remote=initiator_remote, connection=connection, inbound=True) if self.peer_pool.is_full: await peer.disconnect(DisconnectReason.too_many_peers) return elif not self.peer_pool.is_valid_connection_candidate(peer.remote): await peer.disconnect(DisconnectReason.useless_peer) return total_peers = len(self.peer_pool) inbound_peer_count = len([ peer for peer in self.peer_pool.connected_nodes.values() if peer.inbound ]) if total_peers > 1 and inbound_peer_count / total_peers > DIAL_IN_OUT_RATIO: # make sure to have at least 1/4 outbound connections await peer.disconnect(DisconnectReason.too_many_peers) else: # We use self.wait() here as a workaround for # https://github.com/ethereum/py-evm/issues/670. await self.wait(self.do_handshake(peer))
async def connect(self, remote: Node) -> BasePeer: """ Connect to the given remote and return a Peer instance when successful. Returns None if the remote is unreachable, times out or is useless. """ if remote.pubkey == self.privkey.public_key: Logger.warning_every_n( "Skipping {} that has the same public key as local node, quite possible we are trying to connect to ourselves" .format(remote), 100, ) return None if remote in self.connected_nodes: self.logger.debug("Skipping %s; already connected to it", remote) return None if self.chk_dialout_blacklist(remote.address): Logger.warning_every_n( "failed to connect {} at least once, will not connect again; discovery should have removed it" .format(remote.address), 100) return None blacklistworthy_exceptions = ( HandshakeFailure, # after secure handshake handshake, when negotiating p2p command, eg. parsing hello failed; no matching p2p capabilities PeerConnectionLost, # conn lost while reading TimeoutError, # eg. read timeout (raised by CancelToken) UnreachablePeer, # ConnectionRefusedError, OSError ) expected_exceptions = ( HandshakeDisconnectedFailure, # during secure handshake, disconnected before getting ack; or got Disconnect cmd for some known reason ) try: self.logger.debug("Connecting to %s...", remote) # We use self.wait() as well as passing our CancelToken to handshake() as a workaround # for https://github.com/ethereum/py-evm/issues/670. peer = await self.wait(handshake(remote, self.get_peer_factory())) return peer except OperationCancelled: # Pass it on to instruct our main loop to stop. raise except BadAckMessage: # This is kept separate from the `expected_exceptions` to be sure that we aren't # silencing an error in our authentication code. Logger.error_every_n("Got bad auth ack from {}".format(remote), 100) # dump the full stacktrace in the debug logs self.logger.debug("Got bad auth ack from %r", remote, exc_info=True) self.dialout_blacklist(remote.address) except MalformedMessage: # This is kept separate from the `expected_exceptions` to be sure that we aren't # silencing an error in how we decode messages during handshake. Logger.error_every_n( "Got malformed response from {} during handshake".format( remote), 100) # dump the full stacktrace in the debug logs self.logger.debug("Got malformed response from %r", remote, exc_info=True) self.dialout_blacklist(remote.address) except blacklistworthy_exceptions as e: self.logger.debug("Could not complete handshake with %r: %s", remote, repr(e)) Logger.error_every_n( "Could not complete handshake with {}: {}".format( repr(remote), repr(e)), 100) self.dialout_blacklist(remote.address) except expected_exceptions as e: self.logger.debug("Disconnected during handshake %r: %s", remote, repr(e)) Logger.error_every_n( "Disconnected during handshake {}: {}".format( repr(remote), repr(e)), 100) except Exception: self.logger.exception( "Unexpected error during auth/p2p handshake with %r", remote) self.dialout_blacklist(remote.address) if remote.__repr__() in auth.opened_connections: reader, writer = auth.opened_connections[remote.__repr__()] reader.feed_eof() writer.close() Logger.error_every_n( "Closing connection to {}".format(remote.__repr__()), 100) del auth.opened_connections[remote.__repr__()] return None
async def connect(self, remote: Node) -> BasePeer: """ Connect to the given remote and return a Peer instance when successful. Returns None if the remote is unreachable, times out or is useless. """ if remote.pubkey == self.privkey.public_key: Logger.warning_every_n( "Skipping {} that has the same public key as local node, quite possible we are trying to connect to ourselves" .format(remote), 100, ) return None if remote in self.connected_nodes: self.logger.debug("Skipping %s; already connected to it", remote) return None expected_exceptions = ( HandshakeFailure, PeerConnectionLost, TimeoutError, UnreachablePeer, ) try: self.logger.debug("Connecting to %s...", remote) # We use self.wait() as well as passing our CancelToken to handshake() as a workaround # for https://github.com/ethereum/py-evm/issues/670. peer = await self.wait(handshake(remote, self.get_peer_factory())) return peer except OperationCancelled: # Pass it on to instruct our main loop to stop. raise except BadAckMessage: # This is kept separate from the `expected_exceptions` to be sure that we aren't # silencing an error in our authentication code. self.logger.error("Got bad auth ack from %r", remote) # dump the full stacktrace in the debug logs self.logger.debug("Got bad auth ack from %r", remote, exc_info=True) except MalformedMessage: # This is kept separate from the `expected_exceptions` to be sure that we aren't # silencing an error in how we decode messages during handshake. self.logger.error( "Got malformed response from %r during handshake", remote) # dump the full stacktrace in the debug logs self.logger.debug("Got malformed response from %r", remote, exc_info=True) except expected_exceptions as e: self.logger.debug("Could not complete handshake with %r: %s", remote, repr(e)) except Exception: self.logger.exception( "Unexpected error during auth/p2p handshake with %r", remote) if remote.__repr__() in auth.opened_connections: reader, writer = auth.opened_connections[remote.__repr__()] reader.feed_eof() writer.close() self.logger.error("Closing connection to %r", remote.__repr__()) del auth.opened_connections[remote.__repr__()] return None