示例#1
0
    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)
示例#3
0
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
示例#4
0
    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))
示例#5
0
    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
示例#6
0
    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