def setUp(self):
     self.packet_fixture = bytes.fromhex(
         '0e'  # Message type Leasequery
         'e86f0c'  # Transaction ID
         '0001'  # Option type 1: OPTION_CLIENT_ID
         '000a'  # Option length: 10
         '0003'  # DUID type: DUID_LL
         '0001'  # Hardware type: Ethernet
         '001ee6f77d00'  # MAC Address
         '002c'  # Option type 44: OPTION_LQ_QUERY
         '0017'  # Option length: 23
         '01'  # Query type: QUERY_BY_ADDRESS
         'fe800000000000000000000000000001'  # Link address: fe80::1
         '0006'  # Option type: OPTION_ORO
         '0002'  # Option length: 2
         '002f'  # Requested option: OPTION_LQ_RELAY_DATA
     )
     self.message_fixture = LeasequeryMessage(
         transaction_id=bytes.fromhex('e86f0c'),
         options=[
             ClientIdOption(duid=LinkLayerDUID(
                 hardware_type=1,
                 link_layer_address=bytes.fromhex('001ee6f77d00'))),
             LQQueryOption(
                 query_type=QUERY_BY_ADDRESS,
                 link_address=IPv6Address('fe80::1'),
                 options=[
                     OptionRequestOption(
                         requested_options=[OPTION_LQ_RELAY_DATA]),
                 ]),
         ])
     self.parse_packet()
Exemple #2
0
    def test_query_messed_up_prefix(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address])

        # Messed-up data, a log message should appear
        with TemporaryDirectory() as tmp_dir_name:
            store = LeasequerySqliteStore(os.path.join(tmp_dir_name, 'lq.sqlite'))
            store.worker_init([])
            store.remember_lease(bundle)

            # Mess up the data in our poor database
            db = sqlite3.connect(store.sqlite_filename)
            db.row_factory = sqlite3.Row
            db.execute("UPDATE prefixes SET first_address='2001:0db8:0000:0000:0000:0000:0000:0000'")
            db.commit()

            with self.assertLogs('', 'NOTSET')as cm:
                nr_found, results = store.find_leases(query)
                results = list(results)

            self.assertEqual(nr_found, 1)
            self.assertEqual(len(results), 1)
            self.assertEqual(len(cm.output), 1)
            self.assertRegex(cm.output[0], 'Ignoring invalid prefix range')
Exemple #3
0
    def find_leases(
        self, query: LQQueryOption
    ) -> Tuple[int, Iterable[Tuple[IPv6Address, ClientDataOption]]]:
        """
        Find all leases that match the given query.

        :param query: The query
        :return: The number of leases and an iterator over tuples of link-address and corresponding client data
        """
        # Run everything in one transaction
        with self.db:
            if query.query_type == QUERY_BY_ADDRESS:
                client_row_ids = self.find_client_by_address(query)
            elif query.query_type == QUERY_BY_CLIENT_ID:
                client_row_ids = self.find_client_by_client_id(query)
            elif query.query_type == QUERY_BY_RELAY_ID:
                client_row_ids = self.find_client_by_relay_id(query)
            elif query.query_type == QUERY_BY_LINK_ADDRESS:
                client_row_ids = self.find_client_by_link_address(query)
            elif query.query_type == QUERY_BY_REMOTE_ID:
                client_row_ids = self.find_client_by_remote_id(query)
            else:
                # We can't handle this query
                return -1, []

            if not client_row_ids:
                # None found
                return 0, []

            # Generate records for these client IDs
            oro = query.get_option_of_type(OptionRequestOption)
            requested_options = oro.requested_options if oro else []
            return len(client_row_ids), self.generate_client_data_options(
                client_row_ids, requested_options)
Exemple #4
0
    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]
Exemple #5
0
    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]
Exemple #6
0
    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]
Exemple #7
0
    def test_query_by_unspecified_link_address(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_LINK_ADDRESS, link_address=IPv6Address('::'))

        self.query(bundle, query)
Exemple #8
0
    def test_query_by_unknown(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(-1)

        # No valid query provided, no data
        self.query_empty(bundle, query, invalid=True)
Exemple #9
0
    def test_query_by_address_malformed(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_ADDRESS)

        with self.assertRaisesRegex(ReplyWithLeasequeryError, 'Address queries must contain an address'):
            self.query(bundle, query)
Exemple #10
0
    def test_query_by_remote_id_malformed(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_REMOTE_ID)

        with self.assertRaisesRegex(ReplyWithLeasequeryError, 'Remote-ID queries must contain a remote ID'):
            self.query(bundle, query)
def create_link_address_query(options) -> LQQueryOption:
    """
    Create query option for link-address query.

    :param options: Options from the main argument parser
    :return: The Leasequery
    """
    return LQQueryOption(QUERY_BY_LINK_ADDRESS, options.link_address)
Exemple #12
0
    def test_query_by_client_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.response.get_option_of_type(ClientIdOption)

        query = LQQueryOption(QUERY_BY_CLIENT_ID, link_address=IPv6Address('3ffe::'), options=[client_id_option])

        self.query_empty(bundle, query)
Exemple #13
0
    def test_query_by_relay_id_on_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        relay_id_option = bundle.incoming_relay_messages[-1].get_option_of_type(RelayIdOption)

        query = LQQueryOption(QUERY_BY_RELAY_ID, link_address=bundle.link_address, options=[relay_id_option])

        self.query(bundle, query)
def create_relay_id_query(options) -> LQQueryOption:
    """
    Create query option for relay-id query.

    :param options: Options from the main argument parser
    :return: The Leasequery
    """
    return LQQueryOption(QUERY_BY_RELAY_ID, options.link_address,
                         [RelayIdOption(parse_duid(options.duid))])
def create_client_id_query(options) -> LQQueryOption:
    """
    Create query option for client-id query.

    :param options: Options from the main argument parser
    :return: The Leasequery
    """
    return LQQueryOption(QUERY_BY_CLIENT_ID, options.link_address,
                         [ClientIdOption(parse_duid(options.duid))])
def create_client_address_query(options) -> LQQueryOption:
    """
    Create query option for address query.

    :param options: Options from the main argument parser
    :return: The Leasequery
    """
    return LQQueryOption(QUERY_BY_ADDRESS, options.link_address,
                         [IAAddressOption(options.address)])
Exemple #17
0
    def test_query_by_address_with_extra_data(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address, OptionRequestOption([OPTION_DNS_SERVERS])])

        self.query(bundle, query, [RecursiveNameServersOption])
Exemple #18
0
    def test_query_by_address_with_relay_data(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address, OptionRequestOption([OPTION_LQ_RELAY_DATA])])

        self.query(bundle, query, [LQRelayDataOption])
Exemple #19
0
    def test_query_by_address_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, link_address=IPv6Address('3ffe::'), options=[ia_address])

        self.query_empty(bundle, query)
Exemple #20
0
    def test_query_by_remote_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        # Test every remote-id
        for relay_message in bundle.incoming_relay_messages:
            for option in relay_message.get_options_of_type(RemoteIdOption):
                with self.subTest(msg="{}:{}".format(option.enterprise_number, normalise_hex(option.remote_id))):
                    query = LQQueryOption(QUERY_BY_REMOTE_ID, link_address=IPv6Address('3ffe::'), options=[option])
                    self.query_empty(bundle, query)
def create_remote_id_query(options) -> LQQueryOption:
    """
    Create query option for remote-id query.

    :param options: Options from the main argument parser
    :return: The Leasequery
    """
    return LQQueryOption(QUERY_BY_REMOTE_ID, options.link_address, [
        RemoteIdOption(int(options.enterprise_nr),
                       bytes.fromhex(options.remote_id))
    ])
Exemple #22
0
    def test_query_by_relay_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.response.get_option_of_type(ClientIdOption)

        query = LQQueryOption(QUERY_BY_RELAY_ID, link_address=IPv6Address('3ffe::'),
                              options=[RelayIdOption(duid=client_id_option.duid)])

        # Our test data doesn't have a relay-id, so no results expected
        self.query_empty(bundle, query)
    def test_bad_option_length(self):
        with self.assertRaisesRegex(ValueError, 'shorter than the minimum length'):
            LQQueryOption.parse(bytes.fromhex('002c001001fe800000000000000000000000000001'))

        with self.assertRaisesRegex(ValueError, 'longer than the available buffer'):
            LQQueryOption.parse(bytes.fromhex('002c001201fe800000000000000000000000000001'))

        with self.assertRaisesRegex(ValueError, 'length does not match'):
            LQQueryOption.parse(bytes.fromhex('002c001601fe80000000000000000000000000000100060002002f'))
    def setUp(self):
        self.option_bytes = bytes.fromhex(
            '002c'  # Option type 44: OPTION_LQ_QUERY
            '0017'  # Option length: 23
            '01'  # Query type: QUERY_BY_ADDRESS
            'fe800000000000000000000000000001'  # Link address: fe80::1
            '0006'  # Option type: OPTION_ORO
            '0002'  # Option length: 2
            '002f'  # Requested option: OPTION_LQ_RELAY_DATA
        )
        self.option_object = LQQueryOption(
            query_type=QUERY_BY_ADDRESS,
            link_address=IPv6Address('fe80::1'),
            options=[
                OptionRequestOption(requested_options=[OPTION_LQ_RELAY_DATA]),
            ])

        self.parse_option()
    def test_bad_option_length(self):
        with self.assertRaisesRegex(ValueError,
                                    'shorter than the minimum length'):
            LQQueryOption.parse(
                bytes.fromhex('002c001001fe800000000000000000000000000001'))

        with self.assertRaisesRegex(ValueError,
                                    'longer than the available buffer'):
            LQQueryOption.parse(
                bytes.fromhex('002c001201fe800000000000000000000000000001'))

        with self.assertRaisesRegex(ValueError, 'length does not match'):
            LQQueryOption.parse(
                bytes.fromhex(
                    '002c001601fe80000000000000000000000000000100060002002f'))
 def test_parse_wrong_type(self):
     with self.assertRaisesRegex(ValueError, 'does not contain LQQueryOption data'):
         option = LQQueryOption()
         option.load_from(b'00020010ff12000000000000000000000000abcd')
 def test_parse_wrong_type(self):
     with self.assertRaisesRegex(ValueError,
                                 'does not contain LQQueryOption data'):
         option = LQQueryOption()
         option.load_from(b'00020010ff12000000000000000000000000abcd')