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_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_bad_option_length(self): with self.assertRaisesRegex(ValueError, 'shorter than the minimum length'): IANAOption.parse(bytes.fromhex('0003000041424344000000290000002a')) with self.assertRaisesRegex(ValueError, 'length does not match'): IANAOption.parse( bytes.fromhex('0003000d41424344000000290000002a00140000'))
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 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 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 test_validate_IAID_uniqueness(self): # The first one should be fine self.message.options.append(IANAOption(iaid=b'test')) self.message.validate() # Adding a different type with the same IAID is allowed self.message.options.append(IATAOption(iaid=b'test')) self.message.validate() # But adding another one with the same IAID is not allowed self.message.options.append(IATAOption(iaid=b'test')) with self.assertRaisesRegex(ValueError, 'not unique'): self.message.validate()
# 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,
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)
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'))), RapidCommitOption(), IANAOption(iaid=bytes.fromhex('c43cb2f1')), IAPDOption(iaid=bytes.fromhex('c43cb2f1'), options=[ IAPrefixOption( prefix=IPv6Network('::/0')), ]), ReconfigureAcceptOption(), OptionRequestOption(requested_options=[ OPTION_DNS_SERVERS, OPTION_NTP_SERVER, OPTION_SNTP_SERVERS, OPTION_IA_PD, OPTION_IA_NA, OPTION_VENDOR_OPTS, OPTION_SOL_MAX_RT, OPTION_INF_MAX_RT,
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 test_bad_option_length(self): with self.assertRaisesRegex(ValueError, 'length does not match'): IANAOption.parse(bytes.fromhex('0003000041424344000000290000002a'))
def test_bad_option_length(self): with self.assertRaisesRegex(ValueError, 'shorter than the minimum length'): IANAOption.parse(bytes.fromhex('0003000041424344000000290000002a')) with self.assertRaisesRegex(ValueError, 'length does not match'): IANAOption.parse(bytes.fromhex('0003000d41424344000000290000002a00140000'))