def test_bad_option_length(self):
        with self.assertRaisesRegex(ValueError, 'length does not match'):
            ClientDataOption.parse(
                bytes.fromhex(
                    '002d'  # Option type 45: OPTION_CLIENT_DATA
                    '0018'  # Option length: 24 (should be 25)
                    '0001'  # Option type 1: OPTION_CLIENT_ID
                    '0015'  # Option length: 21
                    '0002'  # DUID type: DUID_EN
                    '00009d10'  # Enterprise ID: 40208
                    '303132333435363738396162636465'  # Identifier: '0123456789abcde'
                ))

        with self.assertRaisesRegex(ValueError,
                                    'longer than the available buffer'):
            ClientDataOption.parse(
                bytes.fromhex(
                    '002d'  # Option type 45: OPTION_CLIENT_DATA
                    '001a'  # Option length: 26 (should be 25)
                    '0001'  # Option type 1: OPTION_CLIENT_ID
                    '0015'  # Option length: 21
                    '0002'  # DUID type: DUID_EN
                    '00009d10'  # Enterprise ID: 40208
                    '303132333435363738396162636465'  # Identifier: '0123456789abcde'
                ))
    def setUp(self):
        self.option_bytes = bytes.fromhex(
            '002d'  # Option type 45: OPTION_CLIENT_DATA
            '0099'  # Option length: 153

            '0001'  # Option type 1: OPTION_CLIENT_ID
            '0015'  # Option length: 21
            '0002'  # DUID type: DUID_EN
            '00009d10'  # Enterprise ID: 40208
            '303132333435363738396162636465'  # Identifier: '0123456789abcde'

            '0005'  # Option type: OPTION_IAADDR
            '0018'  # Option length: 24
            '20010db800000000000000000000cafe'  # IPv6 address: 2001:db8::cafe
            '00000708'  # Preferred lifetime: 1800
            '00000e10'  # Valid lifetime: 3600

            '001a'  # Option type: OPTION_IAPREFIX
            '0019'  # Option length: 25
            '00000708'  # Preferred lifetime: 1800
            '00000e10'  # Valid lifetime: 3600
            '30'  # Prefix length: 48
            '20010db8000100000000000000000000'

            '002e'  # Option type: OPTION_CLT_TIME
            '0004'  # Option length: 4
            '00000384'  # Client-Last-Transaction time: 900

            '002f'  # Option type: OPTION_LQ_RELAY_DATA
            '003b'  # Option length: 59
            '20010db8000000000000000000000002'  # Peer address: 2001:db8::2

            '0c'  # Message type: MSG_RELAY_FORW
            '00'  # Hop count: 0
            '20010db8000000000000000000000002'  # Link address: 2001:db8::2
            'fe800000000000000000000000000022'  # Peer address: fe80::22

            '0012'  # Option type: OPTION_INTERFACE_ID
            '0005'  # Option length: 5
            '4661322f33'  # Interface ID: 'Fa2/3'
        )
        self.option_object = ClientDataOption(options=[
            ClientIdOption(EnterpriseDUID(40208, b'0123456789abcde')),
            IAAddressOption(address=IPv6Address('2001:db8::cafe'), preferred_lifetime=1800, valid_lifetime=3600),
            IAPrefixOption(prefix=IPv6Network('2001:db8:1::/48'), preferred_lifetime=1800, valid_lifetime=3600),
            CLTTimeOption(clt_time=900),
            LQRelayDataOption(peer_address=IPv6Address('2001:db8::2'), relay_message=RelayForwardMessage(
                hop_count=0,
                link_address=IPv6Address('2001:db8::2'),
                peer_address=IPv6Address('fe80::22'),
                options=[
                    InterfaceIdOption(interface_id=b'Fa2/3'),
                ]
            ))
        ])

        self.parse_option()
 def test_parse_wrong_type(self):
     with self.assertRaisesRegex(ValueError,
                                 'does not contain ClientDataOption data'):
         option = ClientDataOption()
         option.load_from(b'00020010ff12000000000000000000000000abcd')
Exemplo n.º 4
0
    def generate_client_data_options(self, client_row_ids: Iterable[int], requested_options: Iterable[int]) \
            -> Iterable[Tuple[IPv6Address, ClientDataOption]]:
        """
        Create a generator for the data of the specified client rows/

        :param client_row_ids: The list of client rows what we are interested in
        :param requested_options: Option types explicitly requested by the leasequery client
        :return: The client data options for those rows
        """
        # Some helper variables
        relay_data_requested = OPTION_LQ_RELAY_DATA in requested_options
        extra_data_requested = any([
            requested_option for requested_option in requested_options
            if requested_option != OPTION_LQ_RELAY_DATA
        ])

        # Determine which columns we are interested in, no point in dragging in large chunks of data for nothing
        selected_columns = [
            "id", "client_id", "link_address", "last_interaction"
        ]
        if extra_data_requested:
            selected_columns.append("options")
        if relay_data_requested:
            selected_columns.append("relay_data")

        now = int(time.time())
        client_cur = self.db.execute(
            "SELECT {} FROM clients WHERE id IN ({})".format(
                ', '.join(selected_columns),
                ', '.join(map(str, client_row_ids))))
        for client_row in client_cur:
            # This is the first part of the tuple we yield
            link_address = IPv6Address(client_row['link_address'])

            # Reconstruct the DUID of the client
            duid = self.decode_duid(client_row['client_id'])
            client_id_option = ClientIdOption(duid)

            # How long ago did we speak to this client?
            clt_option = CLTTimeOption(now - client_row['last_interaction'])

            # Get the requested options
            if extra_data_requested:
                stored_options = self.decode_options(client_row['options'])
                stored_options = self.filter_requested_options(
                    stored_options, requested_options)
            else:
                stored_options = []

            # Get the relay data
            if relay_data_requested:
                relay_data_option = self.build_relay_data_option_from_relay_data(
                    client_row['relay_data'])
            else:
                relay_data_option = None

            # Build all the options for this client
            options = [client_id_option, clt_option
                       ] + stored_options  # type: List[Option]
            if relay_data_option:
                options.append(relay_data_option)

            # Add all addresses
            address_cur = self.db.execute(
                "SELECT address, preferred_lifetime_end, valid_lifetime_end, options "
                "FROM addresses WHERE client_fk=? AND valid_lifetime_end>?",
                (client_row['id'], now))
            for address_row in address_cur:
                options.append(
                    IAAddressOption(
                        address=IPv6Address(address_row['address']),
                        preferred_lifetime=max(
                            0, address_row['preferred_lifetime_end'] - now),
                        valid_lifetime=max(
                            0, address_row['valid_lifetime_end'] - now),
                        options=self.decode_options(address_row['options'])))

            # Add all prefixes
            prefix_cur = self.db.execute(
                "SELECT first_address, last_address, "
                "preferred_lifetime_end, valid_lifetime_end, options "
                "FROM prefixes WHERE client_fk=? AND valid_lifetime_end>?",
                (client_row['id'], now))
            for prefix_row in prefix_cur:
                prefixes = list(
                    summarize_address_range(
                        IPv6Address(prefix_row['first_address']),
                        IPv6Address(prefix_row['last_address'])))
                if len(prefixes) != 1:
                    logger.error(
                        "Ignoring invalid prefix range in leasequery db: {} - {}"
                        .format(prefix_row['first_address'],
                                prefix_row['last_address']))
                    continue

                options.append(
                    IAPrefixOption(
                        prefix=prefixes[0],
                        preferred_lifetime=max(
                            0, prefix_row['preferred_lifetime_end'] - now),
                        valid_lifetime=max(
                            0, prefix_row['valid_lifetime_end'] - now),
                        options=self.decode_options(prefix_row['options'])))

            # We got everything, yield it
            yield link_address, ClientDataOption(options)