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 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 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 post(self, bundle: TransactionBundle): """ Upgrade the response from a AdvertiseMessage to a ReplyMessage if appropriate :param bundle: The transaction bundle """ # Does this transaction even allow rapid commit? if not bundle.allow_rapid_commit: return # We only look for SolicitMessages that have a RapidCommitOption if not isinstance( bundle.request, SolicitMessage ) or not bundle.request.get_option_of_type(RapidCommitOption): return # And only if the current response is an AdvertiseMessage if not isinstance(bundle.response, AdvertiseMessage): return # Ok, this looks promising, do extra checks if requested if not self.rapid_commit_rejections: # Ok, we don't want to rapid-commit rejections. Check for them. if bundle.get_unhandled_options( (IANAOption, IATAOption, IAPDOption)): # Unhandled options. We are post-processing, so they are not going to be answered anymore return # Did we already refuse anything? ia_options = [ option for option in bundle.response.options if isinstance(option, (IANAOption, IATAOption)) ] for option in ia_options: status = option.get_option_of_type(StatusCodeOption) if status and status.status_code == STATUS_NO_ADDRS_AVAIL: # Refusal: don't do anything return iapd_options = [ option for option in bundle.response.options if isinstance(option, IAPDOption) ] for option in iapd_options: status = option.get_option_of_type(StatusCodeOption) if status and status.status_code == STATUS_NO_PREFIX_AVAIL: # Refusal: don't do anything return # It seems the request and response qualify: upgrade to ReplyMessage bundle.response = ReplyMessage(bundle.response.transaction_id, [RapidCommitOption()] + bundle.response.options)
def post(self, bundle: TransactionBundle): """ Check for unhandled leasequeries. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Only leasequeries are relevant return unhandled_queries = bundle.get_unhandled_options(LQQueryOption) if unhandled_queries: query = unhandled_queries[0] raise ReplyWithLeasequeryError(STATUS_UNKNOWN_QUERY_TYPE, "This server can't handle query type {}".format(query.query_type))
def post(self, bundle: TransactionBundle): """ Check for unhandled leasequeries. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Only leasequeries are relevant return unhandled_queries = bundle.get_unhandled_options(LQQueryOption) if unhandled_queries: query = unhandled_queries[0] raise ReplyWithLeasequeryError( STATUS_UNKNOWN_QUERY_TYPE, "This server can't handle query type {}".format( query.query_type))
def handle_confirm(self, bundle: TransactionBundle): """ Handle a client requesting confirmation :param bundle: The request bundle """ # Get the assignment assignment = self.get_assignment(bundle) # Collect unanswered options unanswered_iana_options = bundle.get_unhandled_options(IANAOption) # See if there are any addresses on a link that I am responsible for for option in unanswered_iana_options: for suboption in option.get_options_of_type(IAAddressOption): if suboption.address == assignment.address: # This is the address from the assignment: it's ok bundle.mark_handled(option) continue
def post(self, bundle: TransactionBundle): """ Upgrade the response from a AdvertiseMessage to a ReplyMessage if appropriate :param bundle: The transaction bundle """ # Does this transaction even allow rapid commit? if not bundle.allow_rapid_commit: return # We only look for SolicitMessages that have a RapidCommitOption if not isinstance(bundle.request, SolicitMessage) or not bundle.request.get_option_of_type(RapidCommitOption): return # And only if the current response is an AdvertiseMessage if not isinstance(bundle.response, AdvertiseMessage): return # Ok, this looks promising, do extra checks if requested if not self.rapid_commit_rejections: # Ok, we don't want to rapid-commit rejections. Check for them. if bundle.get_unhandled_options((IANAOption, IATAOption, IAPDOption)): # Unhandled options. We are post-processing, so they are not going to be answered anymore return # Did we already refuse anything? ia_options = [option for option in bundle.response.options if isinstance(option, (IANAOption, IATAOption))] for option in ia_options: status = option.get_option_of_type(StatusCodeOption) if status and status.status_code == STATUS_NOADDRSAVAIL: # Refusal: don't do anything return iapd_options = [option for option in bundle.response.options if isinstance(option, IAPDOption)] for option in iapd_options: status = option.get_option_of_type(StatusCodeOption) if status and status.status_code == STATUS_NOPREFIXAVAIL: # Refusal: don't do anything return # It seems the request and response qualify: upgrade to ReplyMessage bundle.response = ReplyMessage(bundle.response.transaction_id, [RapidCommitOption()] + bundle.response.options)
def handle_release_decline(self, bundle: TransactionBundle): """ Handle a client releasing or declining resources. Doesn't really need to do anything because assignments are static. Just mark the right options as handled. :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 bundle.mark_handled(option) for option in unanswered_iana_options: if assignment.address in option.get_addresses(): bundle.mark_handled(option)
def handle_release_decline(self, bundle: TransactionBundle): """ Handle a client releasing or declining resources. Doesn't really need to do anything because assignments are static. Just mark the right options as handled. :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 bundle.mark_handled(option) for option in unanswered_iana_options: if assignment.address in option.get_addresses(): bundle.mark_handled(option)
def handle(self, bundle: TransactionBundle): """ Make sure that every :class:`.IAPDOption` is answered. :param bundle: The transaction bundle """ for option in bundle.get_unhandled_options(IAPDOption): if isinstance(bundle.request, (SolicitMessage, RequestMessage)): # If the delegating router will not assign any prefixes to any IA_PDs in a subsequent Request from the # requesting router, the delegating router MUST send an Advertise message to the requesting router that # includes the IA_PD with no prefixes in the IA_PD and a Status Code option in the IA_PD containing # status code NoPrefixAvail and a status message for the user # # We do the same for unanswered requests bundle.response.options.append( IAPDOption(option.iaid, options=[StatusCodeOption(STATUS_NO_PREFIX_AVAIL, "No prefixes available")]) ) elif isinstance(bundle.request, RenewMessage): # Renew message: If the delegating router cannot find a binding for the requesting router's IA_PD the # delegating router returns the IA_PD containing no prefixes with a Status Code option set to # NoBinding in the Reply message. prefixes = ", ".join(map(str, option.get_prefixes())) logger.warning("No handler renewed {}: sending NoBinding status".format(prefixes)) bundle.response.options.append( IAPDOption( option.iaid, options=[StatusCodeOption(STATUS_NO_BINDING, "No prefixes assigned to you")] ) ) elif isinstance(bundle.request, RebindMessage): # Rebind message: If the delegating router cannot find a binding for the requesting router's IA_PD and # the delegating router determines that the prefixes in the IA_PD are not appropriate for the link to # which the requesting router's interface is attached according to the delegating routers explicit # configuration, the delegating router MAY send a Reply message to the requesting router containing # the IA_PD with the lifetimes of the prefixes in the IA_PD set to zero. This Reply constitutes an # explicit notification to the requesting router that the prefixes in the IA_PD are no longer valid. # # If the delegating router is unable to determine if the prefix is not appropriate for the link, the # Rebind message is discarded. # # The authoritative flag indicates whether this option may claim whether it is able to determine if a # prefix is appropriate for the link. if not self.authoritative: raise CannotRespondError("Server is not authoritative and cannot reject rebind") prefixes = ", ".join(map(str, option.get_prefixes())) logger.warning("No handler answered rebind of {}: withdrawing prefixes".format(prefixes)) reply_suboptions = [] for suboption in option.get_options_of_type(IAPrefixOption): reply_suboptions.append(IAPrefixOption(suboption.prefix, preferred_lifetime=0, valid_lifetime=0)) bundle.response.options.append(IAPDOption(option.iaid, options=reply_suboptions)) 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( IAPDOption( option.iaid, options=[StatusCodeOption(STATUS_NO_BINDING, "No prefixes assigned to you")] ) )
def handle(self, bundle: TransactionBundle): """ Perform leasequery if requested. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Not a leasequery, not our business return # Extract the query queries = bundle.get_unhandled_options(LQQueryOption) if not queries: # No unhandled queries return query = queries[0] # Get the leases from the store lease_count, leases = self.store.find_leases(query) # A count of -1 means unsupported query, so we stop handling if lease_count < 0: return # Otherwise mark this query as handled bundle.mark_handled(query) # What we do now depends on the protocol if bundle.received_over_tcp: try: if lease_count > 0: # We're doing bulk leasequery, return all the records in separate messages leases_iterator = iter(leases) first_link_address, first_data_option = next( leases_iterator) first_message = bundle.response first_message.options.append(first_data_option) bundle.responses = MessagesList( first_message, self.generate_data_messages( first_message.transaction_id, leases_iterator)) else: # If the server does not find any bindings satisfying a query, it # SHOULD send a LEASEQUERY-REPLY without an OPTION_STATUS_CODE option # and without any OPTION_CLIENT_DATA option. pass except: # Something went wrong (database changes while reading?), abort logger.exception( "Error while building bulk leasequery response") raise ReplyWithLeasequeryError( STATUS_QUERY_TERMINATED, "Error constructing your reply, please try again") else: try: if lease_count == 1: # One entry found, return it leases_iterator = iter(leases) first_link_address, first_data_option = next( leases_iterator) bundle.response.options.append(first_data_option) elif lease_count > 1: # The Client Link option is used only in a LEASEQUERY-REPLY message and # identifies the links on which the client has one or more bindings. # It is used in reply to a query when no link-address was specified and # the client is found to be on more than one link. link_addresses = set( [link_address for link_address, data_option in leases]) bundle.response.options.append( LQClientLink(link_addresses)) except: # Something went wrong (database changes while reading?), abort logger.exception("Error while building leasequery response") raise ReplyWithLeasequeryError( STATUS_UNSPEC_FAIL, "Error constructing your reply, please try again")
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 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_NOADDRSAVAIL, "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 addresses = ', '.join(map(str, option.get_addresses())) logger.warning("No handler confirmed {}: sending NotOnLink status".format(addresses)) force_status(bundle.response.options, StatusCodeOption(STATUS_NOTONLINK, "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_NOBINDING, "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 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_NOBINDING, "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_NOBINDING, "No addresses assigned to you") ]))
class TransactionBundleTestCase(unittest.TestCase): def setUp(self): self.bundle = TransactionBundle(relayed_solicit_message, received_over_multicast=False) self.shallow_bundle = TransactionBundle(solicit_message, received_over_multicast=True) self.deep_bundle = TransactionBundle(RelayForwardMessage( hop_count=0, link_address=IPv6Address('2001:db8:ffff:2::1'), peer_address=IPv6Address('fe80::3631:c4ff:fe3c:b2f1'), options=[ RelayMessageOption(relayed_message=relayed_solicit_message), ] ), received_over_multicast=False, marks=['some', 'marks']) self.ia_bundle = TransactionBundle(SolicitMessage(options=[ IANAOption(b'0001'), IANAOption(b'0002'), IATAOption(b'0003'), IATAOption(b'0004'), IAPDOption(b'0005'), IAPDOption(b'0006'), ]), received_over_multicast=False) self.option_handlers = [ InterfaceIdOptionHandler(), ] def test_str(self): bundle_str = str(self.bundle) self.assertEqual(bundle_str, "SolicitMessage from 000300013431c43cb2f1 at fe80::3631:c4ff:fe3c:b2f1 " "via LDRA -> 2001:db8:ffff:1::1") bundle_str = str(self.shallow_bundle) self.assertEqual(bundle_str, "SolicitMessage from 000300013431c43cb2f1") bundle_str = str(self.deep_bundle) self.assertRegex(bundle_str, "^SolicitMessage from 000300013431c43cb2f1 at fe80::3631:c4ff:fe3c:b2f1 " "via LDRA -> 2001:db8:ffff:1::1 -> 2001:db8:ffff:2::1 with marks .*$") bundle_str = str(self.ia_bundle) self.assertEqual(bundle_str, "SolicitMessage from unknown") def test_shallow_bundle(self): self.shallow_bundle.response = advertise_message self.shallow_bundle.create_outgoing_relay_messages() self.assertEqual(self.shallow_bundle.outgoing_message, advertise_message) self.assertEqual(self.shallow_bundle.outgoing_relay_messages, []) def test_request(self): self.assertEqual(self.bundle.request, solicit_message) def test_incoming_relay_messages(self): self.assertEqual(len(self.bundle.incoming_relay_messages), 2) self.assertEqual(self.bundle.incoming_relay_messages[0].hop_count, 0) self.assertEqual(self.bundle.incoming_relay_messages[1].hop_count, 1) def test_bad_response(self): self.bundle.response = SolicitMessage() with self.assertLogs() as cm: self.assertIsNone(self.bundle.outgoing_message) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'server should not send') def test_outgoing_message(self): # Set the response and let the option handlers do their work # Which in this case is copy the InterfaceId to the response self.bundle.response = advertise_message self.bundle.create_outgoing_relay_messages() for option_handler in self.option_handlers: option_handler.handle(self.bundle) self.assertEqual(self.bundle.outgoing_message, relayed_advertise_message) def test_direct_outgoing_message(self): self.ia_bundle.response = advertise_message self.assertEqual(self.ia_bundle.outgoing_message, advertise_message) def test_auto_create_outgoing_relay_messages(self): self.bundle.response = advertise_message self.assertIsInstance(self.bundle.outgoing_message, RelayReplyMessage) def test_no_outgoing_message(self): self.assertIsNone(self.bundle.outgoing_message) def test_get_unhandled_options(self): unanswered_options = self.ia_bundle.get_unhandled_options((IANAOption, IATAOption)) self.assertEqual(len(unanswered_options), 4) self.assertIn(IANAOption(b'0001'), unanswered_options) self.assertIn(IANAOption(b'0002'), unanswered_options) self.assertIn(IATAOption(b'0003'), unanswered_options) self.assertIn(IATAOption(b'0004'), unanswered_options) def test_marks(self): self.assertEqual(self.bundle.marks, set()) self.bundle.marks.add('one') self.bundle.marks.add('two') self.assertEqual(self.bundle.marks, {'one', 'two'}) self.bundle.marks.add('two') self.assertEqual(self.bundle.marks, {'one', 'two'}) def test_mark_handled(self): self.ia_bundle.mark_handled(IANAOption(b'0001')) self.ia_bundle.mark_handled(IATAOption(b'0004')) unanswered_options = self.ia_bundle.get_unhandled_options((IANAOption, IATAOption)) self.assertEqual(len(unanswered_options), 2) self.assertIn(IANAOption(b'0002'), unanswered_options) self.assertIn(IATAOption(b'0003'), unanswered_options) def test_unanswered_iana_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IANAOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IANAOption(b'0001'), unanswered_options) self.assertIn(IANAOption(b'0002'), unanswered_options) def test_unanswered_iata_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IATAOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IATAOption(b'0003'), unanswered_options) self.assertIn(IATAOption(b'0004'), unanswered_options) def test_unanswered_iapd_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IAPDOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IAPDOption(b'0005'), unanswered_options) self.assertIn(IAPDOption(b'0006'), unanswered_options) def test_unknown_message(self): with self.assertLogs() as cm: TransactionBundle(UnknownMessage(1608, b'Unknown'), False) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'unrecognised message') def test_wrong_way(self): with self.assertLogs() as cm: TransactionBundle(ReplyMessage(), False) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'server should not receive') def test_link_address(self): self.assertEqual(self.bundle.link_address, IPv6Address('2001:db8:ffff:1::1')) self.assertEqual(self.ia_bundle.link_address, IPv6Address('::'))
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): """ Perform leasequery if requested. :param bundle: The transaction bundle """ if not isinstance(bundle.request, LeasequeryMessage): # Not a leasequery, not our business return # Extract the query queries = bundle.get_unhandled_options(LQQueryOption) if not queries: # No unhandled queries return query = queries[0] # Get the leases from the store lease_count, leases = self.store.find_leases(query) # A count of -1 means unsupported query, so we stop handling if lease_count < 0: return # Otherwise mark this query as handled bundle.mark_handled(query) # What we do now depends on the protocol if bundle.received_over_tcp: try: if lease_count > 0: # We're doing bulk leasequery, return all the records in separate messages leases_iterator = iter(leases) first_link_address, first_data_option = next(leases_iterator) first_message = bundle.response first_message.options.append(first_data_option) bundle.responses = MessagesList(first_message, self.generate_data_messages(first_message.transaction_id, leases_iterator)) else: # If the server does not find any bindings satisfying a query, it # SHOULD send a LEASEQUERY-REPLY without an OPTION_STATUS_CODE option # and without any OPTION_CLIENT_DATA option. pass except: # Something went wrong (database changes while reading?), abort logger.exception("Error while building bulk leasequery response") raise ReplyWithLeasequeryError(STATUS_QUERY_TERMINATED, "Error constructing your reply, please try again") else: try: if lease_count == 1: # One entry found, return it leases_iterator = iter(leases) first_link_address, first_data_option = next(leases_iterator) bundle.response.options.append(first_data_option) elif lease_count > 1: # The Client Link option is used only in a LEASEQUERY-REPLY message and # identifies the links on which the client has one or more bindings. # It is used in reply to a query when no link-address was specified and # the client is found to be on more than one link. link_addresses = set([link_address for link_address, data_option in leases]) bundle.response.options.append(LQClientLink(link_addresses)) except: # Something went wrong (database changes while reading?), abort logger.exception("Error while building leasequery response") raise ReplyWithLeasequeryError(STATUS_UNSPEC_FAIL, "Error constructing your reply, please try again")
class TransactionBundleTestCase(unittest.TestCase): def setUp(self): self.bundle = TransactionBundle(relayed_solicit_message, received_over_multicast=False) self.shallow_bundle = TransactionBundle(solicit_message, received_over_multicast=True) self.deep_bundle = TransactionBundle(RelayForwardMessage( hop_count=0, link_address=IPv6Address('2001:db8:ffff:2::1'), peer_address=IPv6Address('fe80::3631:c4ff:fe3c:b2f1'), options=[ RelayMessageOption(relayed_message=relayed_solicit_message), ] ), received_over_multicast=False, marks=['some', 'marks']) self.ia_bundle = TransactionBundle(SolicitMessage(options=[ IANAOption(b'0001'), IANAOption(b'0002'), IATAOption(b'0003'), IATAOption(b'0004'), IAPDOption(b'0005'), IAPDOption(b'0006'), ]), received_over_multicast=False) self.option_handlers = [ InterfaceIdOptionHandler(), ] def test_str(self): bundle_str = str(self.bundle) self.assertEqual(bundle_str, "SolicitMessage from 0001000a000300013431c43cb2f1 at fe80::3631:c4ff:fe3c:b2f1 " "via 2001:db8:ffff:1::1") bundle_str = str(self.shallow_bundle) self.assertEqual(bundle_str, "SolicitMessage from 0001000a000300013431c43cb2f1") bundle_str = str(self.deep_bundle) self.assertRegex(bundle_str, "^SolicitMessage from 0001000a000300013431c43cb2f1 at fe80::3631:c4ff:fe3c:b2f1 " "via 2001:db8:ffff:1::1 -> 2001:db8:ffff:2::1 with marks .*$") bundle_str = str(self.ia_bundle) self.assertEqual(bundle_str, "SolicitMessage from unknown") def test_shallow_bundle(self): self.shallow_bundle.response = advertise_message self.shallow_bundle.create_outgoing_relay_messages() self.assertEqual(self.shallow_bundle.outgoing_message, advertise_message) self.assertEqual(self.shallow_bundle.outgoing_relay_messages, []) def test_request(self): self.assertEqual(self.bundle.request, solicit_message) def test_incoming_relay_messages(self): self.assertEqual(len(self.bundle.incoming_relay_messages), 2) self.assertEqual(self.bundle.incoming_relay_messages[0].hop_count, 0) self.assertEqual(self.bundle.incoming_relay_messages[1].hop_count, 1) def test_no_response(self): self.assertRaisesRegex(ValueError, 'Cannot create outgoing', self.bundle.create_outgoing_relay_messages) def test_bad_response(self): self.bundle.response = SolicitMessage() with self.assertLogs() as cm: self.assertIsNone(self.bundle.outgoing_message) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'server should not send') def test_outgoing_message(self): # Set the response and let the option handlers do their work # Which in this case is copy the InterfaceId to the response self.bundle.response = advertise_message self.bundle.create_outgoing_relay_messages() for option_handler in self.option_handlers: option_handler.handle(self.bundle) self.assertEqual(self.bundle.outgoing_message, relayed_advertise_message) def test_direct_outgoing_message(self): self.ia_bundle.response = advertise_message self.assertEqual(self.ia_bundle.outgoing_message, advertise_message) def test_auto_create_outgoing_relay_messages(self): self.bundle.response = advertise_message self.assertIsInstance(self.bundle.outgoing_message, RelayReplyMessage) def test_no_outgoing_message(self): self.assertIsNone(self.bundle.outgoing_message) def test_get_unhandled_options(self): unanswered_options = self.ia_bundle.get_unhandled_options((IANAOption, IATAOption)) self.assertEqual(len(unanswered_options), 4) self.assertIn(IANAOption(b'0001'), unanswered_options) self.assertIn(IANAOption(b'0002'), unanswered_options) self.assertIn(IATAOption(b'0003'), unanswered_options) self.assertIn(IATAOption(b'0004'), unanswered_options) def test_marks(self): self.assertEqual(self.bundle.marks, set()) self.bundle.marks.add('one') self.bundle.marks.add('two') self.assertEqual(self.bundle.marks, {'one', 'two'}) self.bundle.marks.add('two') self.assertEqual(self.bundle.marks, {'one', 'two'}) def test_mark_handled(self): self.ia_bundle.mark_handled(IANAOption(b'0001')) self.ia_bundle.mark_handled(IATAOption(b'0004')) unanswered_options = self.ia_bundle.get_unhandled_options((IANAOption, IATAOption)) self.assertEqual(len(unanswered_options), 2) self.assertIn(IANAOption(b'0002'), unanswered_options) self.assertIn(IATAOption(b'0003'), unanswered_options) def test_unanswered_iana_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IANAOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IANAOption(b'0001'), unanswered_options) self.assertIn(IANAOption(b'0002'), unanswered_options) def test_unanswered_iata_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IATAOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IATAOption(b'0003'), unanswered_options) self.assertIn(IATAOption(b'0004'), unanswered_options) def test_unanswered_iapd_options(self): unanswered_options = self.ia_bundle.get_unhandled_options(IAPDOption) self.assertEqual(len(unanswered_options), 2) self.assertIn(IAPDOption(b'0005'), unanswered_options) self.assertIn(IAPDOption(b'0006'), unanswered_options) def test_unknown_message(self): with self.assertLogs() as cm: TransactionBundle(UnknownMessage(1608, b'Unknown'), False) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'unrecognised message') def test_wrong_way(self): with self.assertLogs() as cm: TransactionBundle(ReplyMessage(), False) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], 'server should not receive') def test_link_address(self): self.assertEqual(self.bundle.link_address, IPv6Address('2001:db8:ffff:1::1')) self.assertEqual(self.ia_bundle.link_address, IPv6Address('::'))