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 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))
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_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()
# 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(
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)
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") ]))
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)