Example #1
0
def validate_and_extract_destination(
        value: Any) -> Tuple[NodeID, Optional[Endpoint]]:
    node_id: NodeID
    endpoint: Optional[Endpoint]

    if is_hex_node_id(value):
        node_id = NodeID(decode_hex(value))
        endpoint = None
    elif value.startswith("enode://"):
        raw_node_id, _, raw_endpoint = value[8:].partition("@")

        validate_hex_node_id(raw_node_id)
        validate_endpoint(raw_endpoint)

        node_id = NodeID(decode_hex(raw_node_id))

        raw_ip_address, _, raw_port = raw_endpoint.partition(":")
        ip_address = ipaddress.ip_address(raw_ip_address)
        port = int(raw_port)
        endpoint = Endpoint(ip_address.packed, port)
    elif value.startswith("enr:"):
        enr = ENR.from_repr(value)
        node_id = enr.node_id
        endpoint = Endpoint.from_enr(enr)
    else:
        raise RPCError(f"Unrecognized node identifier: {value}")

    return node_id, endpoint
Example #2
0
    async def _manage_routing_table(self) -> None:
        # First load all the bootnode ENRs into our database
        for enr in self._bootnodes:
            try:
                self.enr_db.set_enr(enr)
            except OldSequenceNumber:
                pass

        # Now repeatedly try to bond with each bootnode until one succeeds.
        async with trio.open_nursery() as nursery:
            while self.manager.is_running:
                for enr in self._bootnodes:
                    if enr.node_id == self.local_node_id:
                        continue
                    endpoint = Endpoint.from_enr(enr)
                    nursery.start_soon(self._bond, enr.node_id, endpoint)

                with trio.move_on_after(10):
                    await self._routing_table_ready.wait()
                    break

        # TODO: Need better logic here for more quickly populating the
        # routing table.  Should start off aggressively filling in the
        # table, only backing off once the table contains some minimum
        # number of records **or** searching for new records fails to find
        # new nodes.  Maybe use a TokenBucket
        async for _ in every(30):
            async with trio.open_nursery() as nursery:
                target_node_id = NodeID(secrets.token_bytes(32))
                found_enrs = await self.recursive_find_nodes(target_node_id)
                for enr in found_enrs:
                    endpoint = Endpoint.from_enr(enr)
                    nursery.start_soon(self._bond, enr.node_id, endpoint)
Example #3
0
    async def endpoint_for_node_id(self, node_id: NodeID) -> Endpoint:
        try:
            enr = self.enr_db.get_enr(node_id)
        except KeyError:
            enr = await self.lookup_enr(node_id)

        return Endpoint.from_enr(enr)
Example #4
0
    async def lookup_enr(
        self, node_id: NodeID, *, enr_seq: int = 0, endpoint: Optional[Endpoint] = None
    ) -> ENRAPI:
        if node_id == self.local_node_id:
            raise Exception(f"Cannot lookup local ENR: node_id={node_id.hex()}")

        try:
            enr = self.enr_db.get_enr(node_id)
        except KeyError:
            if endpoint is None:
                # Try to use a recursive network lookup to find the desired
                # node.
                async with self.recursive_find_nodes(node_id) as enr_aiter:
                    async for found_enr in enr_aiter:
                        if found_enr.node_id == node_id:
                            endpoint = Endpoint.from_enr(found_enr)
                            break
                    else:
                        # we weren't given an endpoint and we don't have an enr which would give
                        # us an endpoint, there's no way to reach this node.
                        raise KeyError(f"Could not find ENR: node_id={node_id.hex()}")
        else:
            if enr.sequence_number >= enr_seq:
                return enr

        enr = await self._fetch_enr(node_id, endpoint=endpoint)
        try:
            self.enr_db.set_enr(enr)
        except OldSequenceNumber:
            pass

        return enr
Example #5
0
    def _endpoint_for_enr(self, enr: ENRAPI) -> Endpoint:
        try:
            ip_address = enr[IP_V4_ADDRESS_ENR_KEY]
            port = enr[UDP_PORT_ENR_KEY]
        except KeyError:
            raise Exception("Missing endpoint address information: ")

        return Endpoint(ip_address, port)
Example #6
0
    def extract_params(
            self, request: RPCRequest) -> Tuple[NodeID, Optional[Endpoint]]:
        try:
            raw_params = request["params"]
        except KeyError as err:
            raise RPCError(f"Missiing call params: {err}")

        if len(raw_params) != 1:
            raise RPCError(f"`ddht_ping` endpoint expects a single parameter: "
                           f"Got {len(raw_params)} params: {raw_params}")

        value = raw_params[0]

        node_id: NodeID
        endpoint: Optional[Endpoint]

        if is_hex_node_id(value):
            node_id = NodeID(decode_hex(value))
            endpoint = None
        elif value.startswith("enode://"):
            raw_node_id, _, raw_endpoint = value[8:].partition("@")

            validate_hex_node_id(raw_node_id)
            validate_endpoint(raw_endpoint)

            node_id = NodeID(decode_hex(raw_node_id))

            raw_ip_address, _, raw_port = raw_endpoint.partition(":")
            ip_address = ipaddress.ip_address(raw_ip_address)
            port = int(raw_port)
            endpoint = Endpoint(ip_address.packed, port)
        elif value.startswith("enr:"):
            enr = ENR.from_repr(value)
            node_id = enr.node_id
            endpoint = Endpoint.from_enr(enr)
        else:
            raise RPCError(f"Unrecognized node identifier: {value}")

        return node_id, endpoint
async def test_datagram_sender(socket_pair):
    sending_socket, receiving_socket = socket_pair
    receiver_endpoint = receiving_socket.getsockname()
    sender_endpoint = sending_socket.getsockname()

    send_channel, receive_channel = trio.open_memory_channel(1)
    async with background_trio_service(
            DatagramSender(receive_channel, sending_socket)):
        outbound_datagram = OutboundDatagram(
            b"some packet",
            Endpoint(inet_aton(receiver_endpoint[0]), receiver_endpoint[1]),
        )
        await send_channel.send(outbound_datagram)

        with trio.fail_after(0.5):
            data, sender = await receiving_socket.recvfrom(1024)
        assert data == outbound_datagram.datagram
        assert sender == sender_endpoint
    async def ping(self, node_id: NodeID) -> None:
        local_enr = self.get_local_enr()
        ping = PingMessage(
            request_id=self.message_dispatcher.get_free_request_id(node_id),
            enr_seq=local_enr.sequence_number,
        )

        try:
            with trio.fail_after(REQUEST_RESPONSE_TIMEOUT):
                inbound_message = await self.message_dispatcher.request(node_id, ping)
        except ValueError as value_error:
            self.logger.debug(
                "Failed to send ping to %s: %s", encode_hex(node_id), value_error
            )
        except trio.TooSlowError:
            self.logger.debug("Ping to %s timed out", encode_hex(node_id))
        else:
            if not isinstance(inbound_message.message, PongMessage):
                self.logger.debug(
                    "Peer %s responded to Ping with %s instead of Pong",
                    encode_hex(node_id),
                    inbound_message.message.__class__.__name__,
                )
            else:
                self.logger.debug("Received Pong from %s", encode_hex(node_id))

                self.update_routing_table(node_id)

                pong = inbound_message.message
                local_endpoint = Endpoint(
                    ip_address=pong.packet_ip, port=pong.packet_port
                )
                endpoint_vote = EndpointVote(
                    endpoint=local_endpoint, node_id=node_id, timestamp=time.monotonic()
                )
                await self.endpoint_vote_send_channel.send(endpoint_vote)

                await self.maybe_request_remote_enr(inbound_message)
Example #9
0
    async def get_endpoint_from_enr_db(self,
                                       receiver_node_id: NodeID) -> Endpoint:
        try:
            enr = self.enr_db.get_enr(receiver_node_id)
        except KeyError:
            raise ValueError(
                f"No ENR for peer {encode_hex(receiver_node_id)} known")

        try:
            ip_address = enr[IP_V4_ADDRESS_ENR_KEY]
        except KeyError:
            raise ValueError(
                f"ENR for peer {encode_hex(receiver_node_id)} does not contain an IP address"
            )

        try:
            udp_port = enr[UDP_PORT_ENR_KEY]
        except KeyError:
            raise ValueError(
                f"ENR for peer {encode_hex(receiver_node_id)} does not contain a UDP port"
            )

        return Endpoint(ip_address, udp_port)
Example #10
0
async def DatagramReceiver(
    manager: ManagerAPI,
    sock: SocketType,
    inbound_datagram_send_channel: SendChannel[InboundDatagram],
) -> None:
    """Read datagrams from a socket and send them to a channel."""
    logger = logging.getLogger("ddht.DatagramReceiver")

    async with inbound_datagram_send_channel:
        while manager.is_running:
            datagram, (
                ip_address,
                port) = await sock.recvfrom(DISCOVERY_DATAGRAM_BUFFER_SIZE)
            endpoint = Endpoint(inet_aton(ip_address), port)
            logger.debug("Received %d bytes from %s", len(datagram), endpoint)
            inbound_datagram = InboundDatagram(datagram, endpoint)
            try:
                await inbound_datagram_send_channel.send(inbound_datagram)
            except trio.BrokenResourceError:
                logger.debug(
                    "DatagramReceiver exiting due to `trio.BrokenResourceError`"
                )
                manager.cancel()
                return
Example #11
0
    async def run(self) -> None:
        self.manager.run_daemon_child_service(Canary())

        identity_scheme_registry = default_identity_scheme_registry

        message_type_registry = v51_registry

        enr_database_file = self._boot_info.base_dir / ENR_DATABASE_FILENAME
        enr_db: QueryableENRDatabaseAPI = QueryableENRDB(
            sqlite3.connect(enr_database_file), identity_scheme_registry)

        local_private_key = get_local_private_key(self._boot_info)

        enr_manager = ENRManager(
            enr_db=enr_db,
            private_key=local_private_key,
        )

        port = self._boot_info.port
        events = Events()

        if b"udp" not in enr_manager.enr:
            enr_manager.update((b"udp", port))
        elif enr_manager.enr[b"udp"] != port:
            enr_manager.update((b"udp", port))

        listen_on_ip_address: AnyIPAddress
        if self._boot_info.listen_on is None:
            listen_on_ip_address = DEFAULT_LISTEN
        else:
            listen_on_ip_address = self._boot_info.listen_on
            # Update the ENR if an explicit listening address was provided
            enr_manager.update(
                (IP_V4_ADDRESS_ENR_KEY, listen_on_ip_address.packed))

        listen_on = Endpoint(listen_on_ip_address.packed, self._boot_info.port)

        if self._boot_info.is_upnp_enabled:
            upnp_service = UPnPService(port)
            self.manager.run_daemon_child_service(upnp_service)
            self.manager.run_daemon_task(self._update_enr_ip_from_upnp,
                                         enr_manager, upnp_service)

        bootnodes = self._boot_info.bootnodes

        self.client = Client(
            local_private_key=local_private_key,
            listen_on=listen_on,
            enr_db=enr_db,
            session_cache_size=self._args.session_cache_size,
            events=events,
            message_type_registry=message_type_registry,
        )
        self.network = Network(
            client=self.client,
            bootnodes=bootnodes,
        )

        if self._boot_info.is_rpc_enabled:
            handlers = merge(
                get_core_rpc_handlers(enr_manager, self.network.routing_table),
                get_v51_rpc_handlers(self.network),
            )
            self.rpc_server = RPCServer(self._boot_info.ipc_path, handlers)
            self.manager.run_daemon_child_service(self.rpc_server)

        self.logger.info("Starting DDHT...")
        self.logger.info("Protocol-Version: %s",
                         self._boot_info.protocol_version.value)
        self.logger.info("DDHT base dir   : %s", self._boot_info.base_dir)
        self.logger.info("Listening on    : %s", listen_on)
        self.logger.info("Local Node ID   : %s",
                         encode_hex(enr_manager.enr.node_id))
        self.logger.info(
            "Local ENR       : seq=%d enr=%s",
            enr_manager.enr.sequence_number,
            enr_manager.enr,
        )

        self.manager.run_daemon_child_service(self.network)

        self._ready.set()

        await self.manager.wait_finished()
Example #12
0
    async def _manage_routing_table(self) -> None:
        # First load all the bootnode ENRs into our database
        for enr in self._bootnodes:
            try:
                self.enr_db.set_enr(enr)
            except OldSequenceNumber:
                pass

        # Now repeatedly try to bond with each bootnode until one succeeds.
        while self.manager.is_running:
            with trio.move_on_after(20):
                async with trio.open_nursery() as nursery:
                    for enr in self._bootnodes:
                        if enr.node_id == self.local_node_id:
                            continue
                        endpoint = Endpoint.from_enr(enr)
                        nursery.start_soon(self._bond, enr.node_id, endpoint)

                    await self._routing_table_ready.wait()
                    break

        # Now we enter into an infinite loop that continually probes the
        # network to beep the routing table fresh.  We both perform completely
        # random lookups, as well as targeted lookups on the outermost routing
        # table buckets which are not full.
        #
        # The `TokenBucket` allows us to burst at the beginning, making quick
        # successive probes, then slowing down once the
        #
        # TokenBucket starts with 10 tokens, refilling at 1 token every 30
        # seconds.
        token_bucket = TokenBucket(1 / 30, 10)

        async with trio.open_nursery() as nursery:
            while self.manager.is_running:
                await token_bucket.take()

                # Get the logarithmic distance to the "largest" buckets
                # that are not full.
                non_full_bucket_distances = tuple(
                    idx + 1
                    for idx, bucket in enumerate(self.routing_table.buckets)
                    if len(bucket) < self.routing_table.bucket_size  # noqa: E501
                )[-16:]

                # Probe one of the not-full-buckets with a weighted preference
                # towards the largest buckets.
                distance_to_probe = weighted_choice(non_full_bucket_distances)
                target_node_id = at_log_distance(self.local_node_id, distance_to_probe)

                async with self.recursive_find_nodes(target_node_id) as enr_aiter:
                    async for enr in enr_aiter:
                        if enr.node_id == self.local_node_id:
                            continue

                        try:
                            self.enr_db.set_enr(enr)
                        except OldSequenceNumber:
                            pass

                        nursery.start_soon(self._bond, enr.node_id)
Example #13
0
    async def run(self) -> None:
        identity_scheme_registry = default_identity_scheme_registry

        message_type_registry = v51_registry

        enr_database_dir = self._boot_info.base_dir / ENR_DATABASE_DIR_NAME
        enr_database_dir.mkdir(exist_ok=True)
        enr_db = ENRDB(LevelDB(enr_database_dir), identity_scheme_registry)

        local_private_key = get_local_private_key(self._boot_info)

        enr_manager = ENRManager(
            enr_db=enr_db,
            private_key=local_private_key,
        )

        port = self._boot_info.port
        events = Events()

        if b"udp" not in enr_manager.enr:
            enr_manager.update((b"udp", port))

        listen_on_ip_address: AnyIPAddress
        if self._boot_info.listen_on is None:
            listen_on_ip_address = DEFAULT_LISTEN
        else:
            listen_on_ip_address = self._boot_info.listen_on
            # Update the ENR if an explicit listening address was provided
            enr_manager.update(
                (IP_V4_ADDRESS_ENR_KEY, listen_on_ip_address.packed))

        listen_on = Endpoint(listen_on_ip_address.packed, self._boot_info.port)

        if self._boot_info.is_upnp_enabled:
            upnp_service = UPnPService(port)
            self.manager.run_daemon_child_service(upnp_service)
            self.manager.run_daemon_task(self._update_enr_ip_from_upnp,
                                         enr_manager, upnp_service)

        bootnodes = self._boot_info.bootnodes

        client = Client(
            local_private_key=local_private_key,
            listen_on=listen_on,
            enr_db=enr_db,
            events=events,
            message_type_registry=message_type_registry,
        )
        network = Network(
            client=client,
            bootnodes=bootnodes,
        )

        logger.info("Protocol-Version: %s",
                    self._boot_info.protocol_version.value)
        logger.info("DDHT base dir: %s", self._boot_info.base_dir)
        logger.info("Starting discovery service...")
        logger.info("Listening on %s:%d", listen_on, port)
        logger.info("Local Node ID: %s", encode_hex(enr_manager.enr.node_id))
        logger.info("Local ENR: seq=%d enr=%s",
                    enr_manager.enr.sequence_number, enr_manager.enr)

        await run_trio_service(network)
Example #14
0
 def endpoint(self) -> Endpoint:
     return Endpoint(
         self.enr[IP_V4_ADDRESS_ENR_KEY],
         self.enr[UDP_PORT_ENR_KEY],
     )
Example #15
0
 def _endpoint_for_node_id(self, node_id: NodeID) -> Endpoint:
     enr = self.enr_db.get_enr(node_id)
     return Endpoint.from_enr(enr)