def test_bad_option_length(self):
        with self.assertRaisesRegex(ValueError,
                                    'shorter than the minimum length'):
            IAAddressOption.parse(
                bytes.fromhex(
                    '0005001720010db800010023045678900bc0cafe0001518000093a80')
            )

        with self.assertRaisesRegex(ValueError, 'length does not match'):
            IAAddressOption.parse(
                bytes.fromhex(
                    '0005001920010db800010023045678900bc0cafe0001518000093a8000140000'
                ))
Exemplo n.º 2
0
 def setUp(self):
     self.option_bytes = bytes.fromhex(
         '0003'  # option_type: OPTION_IA_NA
         '0044'  # option_length
         '41424344'  # iaid: ABCD
         '00000029'  # t1: 41
         '0000002a'  # t2: 42
         '0005'  # option_type: OPTION_IAADDR
         '0018'  # option_length
         '20010db8000000000000000000000001'  # address: 2001:db8::1
         '00000000'  # preferred_lifetime
         '00000000'  # valid_lifetime
         '000d'  # option_type: OPTION_STATUS_CODE
         '0018'  # option_length
         '0000'  # status_code
         '45766572797468696e6720697320617765736f6d6521')  # status_message
     self.option_object = IANAOption(
         iaid=b'ABCD',
         t1=41,
         t2=42,
         options=[
             IAAddressOption(address=IPv6Address('2001:db8::1')),
             StatusCodeOption(status_code=STATUS_SUCCESS,
                              status_message='Everything is awesome!')
         ])
     self.parse_option()
    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()
Exemplo n.º 4
0
    def handle_request(self, bundle: TransactionBundle):
        """
        Handle a client requesting addresses (also handles SolicitMessage)

        :param bundle: The request bundle
        """
        # Get the assignment
        assignment = self.get_assignment(bundle)

        # Try to assign the prefix first: it's not dependent on the link
        if assignment.prefix:
            unanswered_iapd_options = bundle.get_unhandled_options(IAPDOption)
            found_option = self.find_iapd_option_for_prefix(
                unanswered_iapd_options, assignment.prefix)
            if found_option:
                # Answer to this option
                logger.log(DEBUG_HANDLING,
                           "Assigning prefix {}".format(assignment.prefix))
                response_option = IAPDOption(
                    found_option.iaid,
                    options=[
                        IAPrefixOption(
                            prefix=assignment.prefix,
                            preferred_lifetime=self.prefix_preferred_lifetime,
                            valid_lifetime=self.prefix_valid_lifetime)
                    ])
                bundle.response.options.append(response_option)
                bundle.mark_handled(found_option)
            else:
                logger.log(
                    DEBUG_HANDLING,
                    "Prefix {} reserved, but client did not ask for it".format(
                        assignment.prefix))

        if assignment.address:
            unanswered_iana_options = bundle.get_unhandled_options(IANAOption)
            found_option = self.find_iana_option_for_address(
                unanswered_iana_options, assignment.address)
            if found_option:
                # Answer to this option
                logger.log(DEBUG_HANDLING,
                           "Assigning address {}".format(assignment.address))
                response_option = IANAOption(
                    found_option.iaid,
                    options=[
                        IAAddressOption(
                            address=assignment.address,
                            preferred_lifetime=self.address_preferred_lifetime,
                            valid_lifetime=self.address_valid_lifetime)
                    ])
                bundle.response.options.append(response_option)
                bundle.mark_handled(found_option)
            else:
                logger.log(
                    DEBUG_HANDLING,
                    "Address {} reserved, but client did not ask for it".
                    format(assignment.address))
Exemplo n.º 5
0
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)])
 def setUp(self):
     self.option_bytes = bytes.fromhex(
         '0005003520010db800010023045678900bc0cafe0001518000093a80'
         '000d0019000457686572652064696420796f752067657420746861743f')
     self.option_object = IAAddressOption(
         address=IPv6Address('2001:db8:1:23:456:7890:bc0:cafe'),
         preferred_lifetime=86400,
         valid_lifetime=7 * 86400,
         options=[
             StatusCodeOption(STATUS_NOT_ON_LINK, 'Where did you get that?')
         ])
     self.parse_option()
Exemplo n.º 7
0
#     Reconfigure Accept
#         Option: Reconfigure Accept (20)
#         Length: 0
#     DNS recursive name server
#         Option: DNS recursive name server (23)
#         Length: 16
#         Value: 20014860486000000000000000008888
#          1 DNS server address: 2001:4860:4860::8888 (2001:4860:4860::8888)

reply_message = ReplyMessage(
    transaction_id=bytes.fromhex('f350d6'),
    options=[
        IANAOption(iaid=bytes.fromhex('c43cb2f1'),
                   options=[
                       IAAddressOption(
                           address=IPv6Address('2001:db8:ffff:1:c::e09c'),
                           preferred_lifetime=375,
                           valid_lifetime=600),
                   ]),
        IAPDOption(iaid=bytes.fromhex('c43cb2f1'),
                   options=[
                       IAPrefixOption(
                           prefix=IPv6Network('2001:db8:ffcc:fe00::/56'),
                           preferred_lifetime=375,
                           valid_lifetime=600),
                   ]),
        ClientIdOption(duid=LinkLayerDUID(hardware_type=1,
                                          link_layer_address=bytes.fromhex(
                                              '3431c43cb2f1'))),
        ServerIdOption(duid=LinkLayerTimeDUID(hardware_type=1,
                                              time=488458703,
                                              link_layer_address=bytes.fromhex(
Exemplo n.º 8
0
 def test_bad_option_length(self):
     with self.assertRaisesRegex(ValueError, 'length does not match'):
         IAAddressOption.parse(bytes.fromhex('0005000020010db800010023045678900bc0cafe0001518000093a80'))
Exemplo n.º 9
0
    def handle_renew_rebind(self, bundle: TransactionBundle):
        """
        Handle a client renewing/rebinding addresses

        :param bundle: The request bundle
        """
        # Get the assignment
        assignment = self.get_assignment(bundle)

        # Collect unanswered options
        unanswered_iana_options = bundle.get_unhandled_options(IANAOption)
        unanswered_iapd_options = bundle.get_unhandled_options(IAPDOption)

        for option in unanswered_iapd_options:
            if assignment.prefix and prefix_overlaps_prefixes(
                    assignment.prefix, option.get_prefixes()):
                # Overlap with our assigned prefix: take responsibility
                response_suboptions = []
                for suboption in option.get_options_of_type(IAPrefixOption):
                    if suboption.prefix == assignment.prefix:
                        # This is the correct option, renew it
                        logger.log(
                            DEBUG_HANDLING,
                            "Renewing prefix {}".format(assignment.prefix))
                        response_suboptions.append(
                            IAPrefixOption(
                                prefix=assignment.prefix,
                                preferred_lifetime=self.
                                prefix_preferred_lifetime,
                                valid_lifetime=self.prefix_valid_lifetime))
                    else:
                        # This isn't right
                        logger.log(
                            DEBUG_HANDLING,
                            "Withdrawing prefix {}".format(suboption.prefix))
                        response_suboptions.append(
                            IAPrefixOption(prefix=suboption.prefix,
                                           preferred_lifetime=0,
                                           valid_lifetime=0))

                response_option = IAPDOption(option.iaid,
                                             options=response_suboptions)
                bundle.response.options.append(response_option)
                bundle.mark_handled(option)

        for option in unanswered_iana_options:
            response_suboptions = []
            for suboption in option.get_options_of_type(IAAddressOption):
                if suboption.address == assignment.address:
                    # This is the correct option, renew it
                    logger.log(
                        DEBUG_HANDLING,
                        "Renewing address {}".format(assignment.address))
                    response_suboptions.append(
                        IAAddressOption(
                            address=assignment.address,
                            preferred_lifetime=self.address_preferred_lifetime,
                            valid_lifetime=self.address_valid_lifetime))
                else:
                    # This isn't right
                    logger.log(
                        DEBUG_HANDLING,
                        "Withdrawing address {}".format(suboption.address))
                    response_suboptions.append(
                        IAAddressOption(address=suboption.address,
                                        preferred_lifetime=0,
                                        valid_lifetime=0))

            response_option = IANAOption(option.iaid,
                                         options=response_suboptions)
            bundle.response.options.append(response_option)
            bundle.mark_handled(option)
Exemplo n.º 10
0
    def handle(self, bundle: TransactionBundle):
        """
        Make sure that every :class:`.IANAOption` and :class:`.IATAOption` is answered.

        :param bundle: The transaction bundle
        """
        for option in bundle.get_unhandled_options((IANAOption, IATAOption)):
            ia_class = type(option)

            if isinstance(bundle.request, (SolicitMessage, RequestMessage)):
                # If the server will not assign any addresses to any IAs in a subsequent Request from the client, the
                # server MUST send an Advertise message to the client that includes only a Status Code option with code
                # NoAddrsAvail and a status message for the user
                #
                # We do the same for unanswered requests
                bundle.response.options.append(
                    ia_class(option.iaid,
                             options=[
                                 StatusCodeOption(STATUS_NO_ADDRS_AVAIL,
                                                  "No addresses available")
                             ]))

            elif isinstance(bundle.request, ConfirmMessage):
                # When the server receives a Confirm message, the server determines whether the addresses in the
                # Confirm message are appropriate for the link to which the client is attached.  If all of the
                # addresses in the Confirm message pass this test, the server returns a status of Success.  If any of
                # the addresses do not pass this test, the server returns a status of NotOnLink.  If the server is
                # unable to perform this test (for example, the server does not have information about prefixes on the
                # link to which the client is connected), or there were no addresses in any of the IAs sent by the
                # client, the server MUST NOT send a reply to the client.
                #
                # The "there were no addresses in any of the IAs sent by the client" check is done by the message
                # handler.
                if not self.authoritative:
                    raise CannotRespondError(
                        "Server is not authoritative and cannot reject confirm"
                    )

                addresses = ', '.join(map(str, option.get_addresses()))
                logger.warning(
                    "No handler confirmed {}: sending NotOnLink status".format(
                        addresses))

                force_status(
                    bundle.response.options,
                    StatusCodeOption(
                        STATUS_NOT_ON_LINK,
                        "Those addresses are not appropriate on this link"))

            elif isinstance(bundle.request, RenewMessage):
                # If the server cannot find a client entry for the IA the server returns the IA containing no addresses
                # with a Status Code option set to NoBinding in the Reply message.
                #
                # If the server finds that any of the addresses are not appropriate for the link to which the client is
                # attached, the server returns the address to the client with lifetimes of 0.
                addresses = ', '.join(map(str, option.get_addresses()))

                if self.authoritative:
                    logger.warning(
                        "No handler renewed {}: withdrawing addresses".format(
                            addresses))

                    reply_suboptions = []
                    for suboption in option.get_options_of_type(
                            IAAddressOption):
                        reply_suboptions.append(
                            IAAddressOption(suboption.address,
                                            preferred_lifetime=0,
                                            valid_lifetime=0))

                    bundle.response.options.append(
                        ia_class(option.iaid, options=reply_suboptions))
                else:
                    logger.warning(
                        "No handler renewed {}: sending NoBinding status".
                        format(addresses))

                    bundle.response.options.append(
                        ia_class(option.iaid,
                                 options=[
                                     StatusCodeOption(
                                         STATUS_NO_BINDING,
                                         "No addresses assigned to you")
                                 ]))

            elif isinstance(bundle.request, RebindMessage):
                # If the server cannot find a client entry for the IA and the server determines that the addresses in
                # the IA are not appropriate for the link to which the client's interface is attached according to the
                # server's explicit configuration information, the server MAY send a Reply message to the client
                # containing the client's IA, with the lifetimes for the addresses in the IA set to zero.  This Reply
                # constitutes an explicit notification to the client that the addresses in the IA are no longer valid.
                # In this situation, if the server does not send a Reply message it silently discards the Rebind
                # message.
                #
                # If the server finds that any of the addresses are no longer appropriate for the link to which the
                # client is attached, the server returns the address to the client with lifetimes of 0.
                if not self.authoritative:
                    raise CannotRespondError(
                        "Server is not authoritative and cannot reject rebind")

                addresses = ', '.join(map(str, option.get_addresses()))
                logger.warning(
                    "No handler answered rebind of {}: withdrawing addresses".
                    format(addresses))

                reply_suboptions = []
                for suboption in option.get_options_of_type(IAAddressOption):
                    reply_suboptions.append(
                        IAAddressOption(suboption.address,
                                        preferred_lifetime=0,
                                        valid_lifetime=0))

                bundle.response.options.append(
                    ia_class(option.iaid, options=reply_suboptions))

            elif isinstance(bundle.request, DeclineMessage):
                # For each IA in the Decline message for which the server has no binding information, the server adds
                # an IA option using the IAID from the Release message and includes a Status Code option with the value
                # NoBinding in the IA option.  No other options are included in the IA option.
                bundle.response.options.append(
                    ia_class(option.iaid,
                             options=[
                                 StatusCodeOption(
                                     STATUS_NO_BINDING,
                                     "No addresses assigned to you")
                             ]))

            elif isinstance(bundle.request, ReleaseMessage):
                # For each IA in the Release message for which the server has no binding information, the server adds an
                # IA option using the IAID from the Release message, and includes a Status Code option with the value
                # NoBinding in the IA option.  No other options are included in the IA option.
                bundle.response.options.append(
                    ia_class(option.iaid,
                             options=[
                                 StatusCodeOption(
                                     STATUS_NO_BINDING,
                                     "No addresses assigned to you")
                             ]))
Exemplo n.º 11
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)