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()
    def test_absent_option_echo_request(self):
        relayed_solicit_message = RelayForwardMessage(
            hop_count=1,
            link_address=IPv6Address('2001:db8:ffff:1::1'),
            peer_address=IPv6Address('fe80::3631:c4ff:fe3c:b2f1'),
            options=[
                RelayMessageOption(relayed_message=SolicitMessage(
                    transaction_id=bytes.fromhex('f350d6'),
                    options=[
                        ElapsedTimeOption(elapsed_time=0),
                        ClientIdOption(duid=LinkLayerDUID(hardware_type=1,
                                                          link_layer_address=bytes.fromhex('3431c43cb2f1'))),
                        IANAOption(iaid=bytes.fromhex('c43cb2f1')),
                    ],
                )),
                EchoRequestOption(requested_options=[OPTION_SUBSCRIBER_ID]),
                UnknownOption(option_type=65535),
                InterfaceIdOption(interface_id=b'Fa2/3'),
                RemoteIdOption(enterprise_number=9,
                               remote_id=bytes.fromhex('020023000001000a0003000100211c7d486e')),
            ]
        )

        bundle = TransactionBundle(incoming_message=relayed_solicit_message, received_over_multicast=True)
        self.message_handler.handle(bundle, StatisticsSet())

        self.assertIsInstance(bundle.outgoing_message, RelayReplyMessage)
        self.assertEqual(len(bundle.outgoing_message.options), 2)
        self.assertIsInstance(bundle.outgoing_message.options[0], InterfaceIdOption)
        self.assertIsInstance(bundle.outgoing_message.options[1], RelayMessageOption)
Example #3
0
 def setUp(self):
     self.relayed_solicit_message = RelayForwardMessage(
         hop_count=1,
         link_address=IPv6Address('2001:db8:ffff:1::1'),
         peer_address=IPv6Address('fe80::3631:c4ff:fe3c:b2f1'),
         options=[
             RelayMessageOption(relayed_message=RelayForwardMessage(
                 hop_count=0,
                 link_address=IPv6Address('::'),
                 peer_address=IPv6Address('fe80::3631:c4ff:fe3c:b2f1'),
                 options=[
                     RelayMessageOption(relayed_message=SolicitMessage(
                         transaction_id=bytes.fromhex('f350d6'),
                         options=[
                             ElapsedTimeOption(elapsed_time=0),
                             ClientIdOption(duid=LinkLayerDUID(hardware_type=1,
                                                               link_layer_address=bytes.fromhex('3431c43cb2f1'))),
                             IANAOption(iaid=bytes.fromhex('c43cb2f1')),
                             IAPDOption(iaid=bytes.fromhex('c43cb2f1')),
                             OptionRequestOption(requested_options=[
                                 OPTION_DNS_SERVERS,
                             ]),
                         ],
                     )),
                     InterfaceIdOption(interface_id=b'Fa2/3'),
                     RemoteIdOption(enterprise_number=9,
                                    remote_id=bytes.fromhex('020023000001000a0003000100211c7d486e')),
                 ])
             ),
             InterfaceIdOption(interface_id=b'Gi0/0/0'),
             RemoteIdOption(enterprise_number=9, remote_id=bytes.fromhex('020000000000000a0003000124e9b36e8100')),
             RelayIdOption(duid=LinkLayerDUID(hardware_type=1, link_layer_address=bytes.fromhex('121212121212'))),
         ],
     )
    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()
Example #5
0
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 setUp(self):
     # The following attributes must be overruled by child classes
     # The basics are tested with a simple SolicitMessage
     self.packet_fixture = bytes.fromhex(
         '01'  # message_type
         '58595a'  # transaction_id
         '0001'  # option_type: OPTION_CLIENTID
         '0015'  # option_length
         '0002'  # duid_type: DUID_EN
         '00009d10'  # enterprise_number
         '444843504b6974556e697454657374'  # "DHCPKitUnitTest"
         '0008'  # option_type: OPTION_ELAPSED_TIME
         '0002'  # option_length
         '0000')  # elapsed_time
     self.message_fixture = SolicitMessage(
         transaction_id=b'XYZ',
         options=[
             ClientIdOption(duid=EnterpriseDUID(
                 enterprise_number=40208, identifier=b'DHCPKitUnitTest')),
             ElapsedTimeOption(elapsed_time=0)
         ])
     self.parse_packet()
        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(
                                                  '00137265ca42'))),
        ReconfigureAcceptOption(),
        RecursiveNameServersOption(
            dns_servers=[IPv6Address('2001:4860:4860::8888')]),
    ],
)

reply_packet = codecs.decode(
    '07f350d600030028c43cb2f100000000'
    '000000000005001820010db8ffff0001'
    '000c00000000e09c0000017700000258'
 def setUp(self):
     self.option_bytes = b'\x00\x01\x00\x15\x00\x02\x00\x00\x9d\x100123456789abcde'
     self.option_object = ClientIdOption(EnterpriseDUID(40208, b'0123456789abcde'))
     self.parse_option()
Example #9
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)
Example #10
0
def main(args: Iterable[str]) -> int:
    """
    The main program

    :param args: Command line arguments
    :return: The program exit code
    """
    # Handle command line arguments
    options = handle_args(args)
    set_verbosity_logger(logger, options.verbosity)

    query = options.create(options)

    # Add ORO for relay data
    if options.relay_data:
        query.options.append(OptionRequestOption([OPTION_LQ_RELAY_DATA]))

    # Generate the outgoing message
    transaction_id = random.getrandbits(24).to_bytes(3, 'big')
    message_out = LeasequeryMessage(
        transaction_id,
        [ClientIdOption(EnterpriseDUID(40208, b'LeaseQueryTester')), query])

    # Create client socket
    if options.tcp:
        client = TCPClientSocket(options)
    else:
        # Check permission
        if os.getuid() != 0:
            raise RuntimeError("This tool needs to be run as root")

        client = UDPClientSocket(options)

    destination = client.send(message_out)
    logger.info("Sent to {}:\n{}".format(destination, message_out))

    # Wait for responses
    wait_for_multiple = options.server.is_multicast or options.tcp

    start = time.time()
    deadline = start + 3

    received = 0

    while time.time() < deadline:
        client.set_timeout(deadline - time.time())
        try:
            sender, message_in = client.recv()
            received += 1

            logger.info("Received from {}:\n{}".format(sender, message_in))

            if options.tcp:
                # Check bulk leasequery ending
                if isinstance(message_in, LeasequeryReplyMessage):
                    if not message_in.get_option_of_type(ClientDataOption):
                        # Reply without data, the end
                        break

                if isinstance(message_in, LeasequeryDoneMessage):
                    break

            if not wait_for_multiple:
                break
        except socket.timeout:
            pass

    logger.info(
        gettext.ngettext("{} response received", "{} responses received",
                         received).format(received))

    return 0