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 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
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()
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()
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)
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()
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'
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))
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()
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()
def setUp(self): self.duid_object = LinkLayerDUID( hardware_type=1, link_layer_address=bytes.fromhex('3431c43cb2f1')) self.duid_bytes = bytes.fromhex('000300013431c43cb2f1')
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)