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)
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()
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()
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)
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