def find_client_by_remote_id(self, query: LQQueryOption) -> List[int]: """ Get the row ids of the clients we want to return. :param query: The query :return: A list of row ids """ # Get the requested remote ID from the query remote_id_option = query.get_option_of_type(RemoteIdOption) if not remote_id_option: raise ReplyWithLeasequeryError( STATUS_MALFORMED_QUERY, "Remote-ID queries must contain a remote ID") remote_id_str = self.encode_remote_id(remote_id_option) if query.link_address.is_unspecified: cur = self.db.execute( "SELECT client_fk FROM relay_ids WHERE relay_id=?", (remote_id_str, )) return [row['client_fk'] for row in cur] else: cur = self.db.execute( "SELECT id FROM clients " "WHERE link_address=? AND id IN (SELECT client_fk FROM relay_ids WHERE relay_id=?)", (query.link_address.exploded, remote_id_str)) return [row['id'] for row in cur]
def find_client_by_client_id(self, query: LQQueryOption) -> List[int]: """ Get the row ids of the clients we want to return. :param query: The query :return: A list of row ids """ # Get the requested client ID from the query client_id_option = query.get_option_of_type(ClientIdOption) if not client_id_option: raise ReplyWithLeasequeryError( STATUS_MALFORMED_QUERY, "Client-ID queries must contain a client ID") client_id_str = self.encode_duid(client_id_option.duid) if query.link_address.is_unspecified: cur = self.db.execute("SELECT id FROM clients WHERE client_id=?", (client_id_str, )) else: cur = self.db.execute( "SELECT id FROM clients WHERE client_id=? AND link_address=?", (client_id_str, query.link_address.exploded)) return [row['id'] for row in cur]
def find_client_by_address(self, query: LQQueryOption) -> List[int]: """ Get the row ids of the clients we want to return. :param query: The query :return: A list of row ids """ # Get the requested address from the query address_option = query.get_option_of_type(IAAddressOption) if not address_option: raise ReplyWithLeasequeryError( STATUS_MALFORMED_QUERY, "Address queries must contain an address") address = address_option.address.exploded if query.link_address.is_unspecified: cur = self.db.execute( "SELECT client_fk FROM addresses WHERE address=?" " UNION " "SELECT client_fk FROM prefixes WHERE ? BETWEEN first_address AND last_address", (address, address)) return [row['client_fk'] for row in cur] else: cur = self.db.execute( "SELECT id FROM clients WHERE link_address=? AND (" "id IN (SELECT client_fk FROM addresses WHERE address=?)" " OR " "id IN (SELECT client_fk FROM prefixes WHERE ? BETWEEN first_address AND last_address)" ")", (query.link_address.exploded, address, address)) return [row['id'] for row in cur]
def pre(self, bundle: TransactionBundle): """ Make sure we allow this client to make leasequery requests. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Not a leasequery, not our business return # Check access based on relay closest to the client if not any([ bundle.incoming_relay_messages[0].peer_address in allow_from for allow_from in self.allow_from ]): raise ReplyWithLeasequeryError( STATUS_NOT_ALLOWED, "Leasequery not allowed from your address")
def post(self, bundle: TransactionBundle): """ Check for unhandled leasequeries. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Only leasequeries are relevant return unhandled_queries = bundle.get_unhandled_options(LQQueryOption) if unhandled_queries: query = unhandled_queries[0] raise ReplyWithLeasequeryError( STATUS_UNKNOWN_QUERY_TYPE, "This server can't handle query type {}".format( query.query_type))
def pre(self, bundle: TransactionBundle): """ Make sure that bulk leasequery options are not coming in over UDP. :param bundle: The transaction bundle """ if bundle.received_over_tcp: # This is over TCP, so we allow all query types return if not isinstance(bundle.request, LeasequeryMessage): # Not a leasequery question, we don't care return query = bundle.request.get_option_of_type(LQQueryOption) if query.query_type in (QUERY_BY_RELAY_ID, QUERY_BY_LINK_ADDRESS, QUERY_BY_REMOTE_ID): raise ReplyWithLeasequeryError(STATUS_NOT_ALLOWED, "Query type {} is only allowed over bulk leasequery".format( query.query_type))
def handle(self, bundle: TransactionBundle): """ Perform leasequery if requested. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Not a leasequery, not our business return # Extract the query queries = bundle.get_unhandled_options(LQQueryOption) if not queries: # No unhandled queries return query = queries[0] # Get the leases from the store lease_count, leases = self.store.find_leases(query) # A count of -1 means unsupported query, so we stop handling if lease_count < 0: return # Otherwise mark this query as handled bundle.mark_handled(query) # What we do now depends on the protocol if bundle.received_over_tcp: try: if lease_count > 0: # We're doing bulk leasequery, return all the records in separate messages leases_iterator = iter(leases) first_link_address, first_data_option = next( leases_iterator) first_message = bundle.response first_message.options.append(first_data_option) bundle.responses = MessagesList( first_message, self.generate_data_messages( first_message.transaction_id, leases_iterator)) else: # If the server does not find any bindings satisfying a query, it # SHOULD send a LEASEQUERY-REPLY without an OPTION_STATUS_CODE option # and without any OPTION_CLIENT_DATA option. pass except: # Something went wrong (database changes while reading?), abort logger.exception( "Error while building bulk leasequery response") raise ReplyWithLeasequeryError( STATUS_QUERY_TERMINATED, "Error constructing your reply, please try again") else: try: if lease_count == 1: # One entry found, return it leases_iterator = iter(leases) first_link_address, first_data_option = next( leases_iterator) bundle.response.options.append(first_data_option) elif lease_count > 1: # The Client Link option is used only in a LEASEQUERY-REPLY message and # identifies the links on which the client has one or more bindings. # It is used in reply to a query when no link-address was specified and # the client is found to be on more than one link. link_addresses = set( [link_address for link_address, data_option in leases]) bundle.response.options.append( LQClientLink(link_addresses)) except: # Something went wrong (database changes while reading?), abort logger.exception("Error while building leasequery response") raise ReplyWithLeasequeryError( STATUS_UNSPEC_FAIL, "Error constructing your reply, please try again")