Example #1
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'))),
         ],
     )
Example #2
0
def duid_ll(section) -> LinkLayerDUID:
    """
    Create a LinkLayerDUID from the data provided in the config section.

    :param section: The section data
    :return: The DUID object
    """
    duid = LinkLayerDUID(hardware_type=section.hardware_type,
                         link_layer_address=section.link_layer_address)
    duid.validate()
    return duid
Example #3
0
    def test_validate_hardware_type(self):
        good_duid_object = LinkLayerDUID(0, b'demo')
        good_duid_object.validate()

        bad_duid_object = LinkLayerDUID(-1, b'demo')
        with self.assertRaisesRegex(ValueError, 'unsigned 16 bit integer'):
            bad_duid_object.validate()

        bad_duid_object = LinkLayerDUID(2 ** 16, b'demo')
        with self.assertRaisesRegex(ValueError, 'unsigned 16 bit integer'):
            bad_duid_object.validate()
Example #4
0
    def test_validate_length(self):
        good_duid_object = LinkLayerDUID(0, 124 * b'x')
        good_duid_object.validate()

        bad_duid_object = LinkLayerDUID(0, 125 * b'x')
        with self.assertRaisesRegex(ValueError,
                                    'cannot be longer than 124 bytes'):
            bad_duid_object.validate()
 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()
Example #6
0
def determine_local_duid() -> LinkLayerDUID:
    """
    Calculate our own DUID based on one of our MAC addresses

    :return: The server DUID
    """
    for interface_name in netifaces.interfaces():
        link_addresses = netifaces.ifaddresses(interface_name).get(netifaces.AF_LINK, [])
        link_addresses = [link_address['addr'] for link_address in link_addresses if link_address.get('addr')]

        for link_address in link_addresses:
            try:
                # Build a DUID from this address
                ll_addr = bytes.fromhex(normalise_hex(link_address))
                if len(ll_addr) != 6 or ll_addr == b'\x00\x00\x00\x00\x00\x00':
                    # If it is not 6 bytes long then it is not an ethernet MAC address, and all-zeroes is just a fake
                    continue

                # Assume it's ethernet, build a DUID
                duid = LinkLayerDUID(hardware_type=1, link_layer_address=ll_addr)

                logger.debug("Using server DUID based on {} link address: {}".format(interface_name, link_address))

                return duid
            except ValueError:
                # Try the next one
                pass

    # We didn't find a useful server DUID
    raise ValueError("Cannot find a usable server DUID")
    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 #8
0
    def test_validate_length(self):
        good_duid_object = LinkLayerDUID(0, 126 * b'x')
        good_duid_object.validate()

        bad_duid_object = LinkLayerDUID(0, 127 * b'x')
        with self.assertRaisesRegex(ValueError, 'cannot be longer than 126 bytes'):
            bad_duid_object.validate()
Example #9
0
def parse_duid(duid_str: str) -> DUID:
    """
    Parse a string representing a DUID into a real DUID

    :param duid_str: The string representation
    :return: The DUID object
    """
    duid_parts = duid_str.split(':')
    duid_type = duid_parts[0]
    if duid_type == 'enterprise':
        if len(duid_parts) == 3:
            hardware_type = int(duid_parts[1], 10)
            if duid_parts[2][:2] == '0x':
                identifier = bytes.fromhex(duid_parts[2][2:])
            else:
                identifier = duid_parts[2].encode('utf-8')
            return EnterpriseDUID(hardware_type, identifier)
        else:
            logger.critical(
                "Enterprise DUIDs must have format 'enterprise:<enterprise-nr>:<identifier>'"
            )
            raise ValueError

    elif duid_type == 'linklayer':
        if len(duid_parts) == 3:
            hardware_type = int(duid_parts[1], 10)
            address = bytes.fromhex(duid_parts[2])
            return LinkLayerDUID(hardware_type, address)
        else:
            logger.critical(
                "Link Layer DUIDs must have format 'linklayer:<hardware-type>:<address-hex>'"
            )
            raise ValueError

    elif duid_type == 'linklayer-time':
        if len(duid_parts) == 4:
            hardware_type = int(duid_parts[1], 10)
            timestamp = int(duid_parts[2], 10)
            address = bytes.fromhex(duid_parts[3])
            return LinkLayerTimeDUID(hardware_type, timestamp, address)
        else:
            logger.critical(
                "Link Layer + Time DUIDs must have format "
                "'linklayer-time:<hardware-type>:<time>:<address-hex>'")
            raise ValueError

    else:
        logger.critical("Unknown DUID type: {}".format(duid_type))
        raise ValueError
        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'
Example #11
0
 def test_wrong_parser(self):
     with self.assertRaisesRegex(ValueError,
                                 'does not contain LinkLayerDUID'):
         duid = LinkLayerDUID()
         duid.load_from(self.duid_bytes, length=len(self.duid_bytes))
Example #12
0
 def test_validate_link_layer(self):
     # noinspection PyTypeChecker
     bad_duid_object = LinkLayerDUID(0, 'demo')
     with self.assertRaisesRegex(ValueError, 'sequence of bytes'):
         bad_duid_object.validate()
Example #13
0
    def test_validate_hardware_type(self):
        good_duid_object = LinkLayerDUID(0, b'demo')
        good_duid_object.validate()

        bad_duid_object = LinkLayerDUID(-1, b'demo')
        with self.assertRaisesRegex(ValueError, 'unsigned 16 bit integer'):
            bad_duid_object.validate()

        bad_duid_object = LinkLayerDUID(2**16, b'demo')
        with self.assertRaisesRegex(ValueError, 'unsigned 16 bit integer'):
            bad_duid_object.validate()
Example #14
0
 def setUp(self):
     self.duid_object = LinkLayerDUID(
         hardware_type=1, link_layer_address=bytes.fromhex('3431c43cb2f1'))
     self.duid_bytes = bytes.fromhex('000300013431c43cb2f1')
Example #15
0
    def test_remember_lease_again(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.request.get_option_of_type(ClientIdOption)
        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)
        ia_pd_option = bundle.response.get_option_of_type(IAPDOption)
        ia_prefix = ia_pd_option.get_option_of_type(IAPrefixOption)

        with TemporaryDirectory() as tmp_dir_name:
            store = LeasequerySqliteStore(os.path.join(tmp_dir_name, 'lq.sqlite'))
            store.worker_init([])
            store.remember_lease(bundle)

            # Change some of the relay information and remember again
            bundle.incoming_relay_messages[-1].get_option_of_type(RelayIdOption).duid = LinkLayerDUID(
                hardware_type=1, link_layer_address=bytes.fromhex('343434343434'))
            bundle.incoming_relay_messages[0].get_option_of_type(RemoteIdOption).enterprise_number = 10
            store.remember_lease(bundle)

            remote_ids = set()
            for relay_message in bundle.incoming_relay_messages:
                for option in relay_message.get_options_of_type(RemoteIdOption):
                    remote_ids.add("{}:{}".format(option.enterprise_number, normalise_hex(option.remote_id)))

            # Check that the data ended up in the database
            db = sqlite3.connect(store.sqlite_filename)
            db.row_factory = sqlite3.Row

            rows = list(db.execute("SELECT * FROM clients"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            client_row = row['id']
            self.assertEqual(row['client_id'], normalise_hex(client_id_option.duid.save()))
            self.assertEqual(row['link_address'], bundle.link_address.exploded)
            self.assertAlmostEqual(row['last_interaction'], time.time(), delta=5)
            # print({key: row[key] for key in rows[0].keys()})

            rows = list(db.execute("SELECT * FROM addresses"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            self.assertEqual(row['client_fk'], client_row)
            self.assertEqual(row['address'], ia_address.address.exploded)
            self.assertAlmostEqual(row['preferred_lifetime_end'], time.time() + ia_address.preferred_lifetime, delta=5)
            self.assertAlmostEqual(row['valid_lifetime_end'], time.time() + ia_address.valid_lifetime, delta=5)
            self.assertEqual(row['options'], b'')

            rows = list(db.execute("SELECT * FROM prefixes"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            self.assertEqual(row['client_fk'], client_row)
            self.assertEqual(row['first_address'], ia_prefix.prefix[0].exploded)
            self.assertEqual(row['last_address'], ia_prefix.prefix[-1].exploded)
            self.assertAlmostEqual(row['preferred_lifetime_end'], time.time() + ia_address.preferred_lifetime, delta=5)
            self.assertAlmostEqual(row['valid_lifetime_end'], time.time() + ia_address.valid_lifetime, delta=5)
            self.assertEqual(row['options'], b'')

            rows = list(db.execute("SELECT * FROM remote_ids"))
            self.assertEqual(len(rows), len(remote_ids))
            self.assertSetEqual({row['remote_id'] for row in rows}, remote_ids)

            rows = list(db.execute("SELECT * FROM relay_ids"))
            self.assertEqual(len(rows), 1)
Example #16
0
 def test_wrong_parser(self):
     with self.assertRaisesRegex(ValueError, 'does not contain LinkLayerDUID'):
         duid = LinkLayerDUID()
         duid.load_from(self.duid_bytes, length=len(self.duid_bytes))
Example #17
0
 def test_validate_link_layer(self):
     # noinspection PyTypeChecker
     bad_duid_object = LinkLayerDUID(0, 'demo')
     with self.assertRaisesRegex(ValueError, 'sequence of bytes'):
         bad_duid_object.validate()