def test_very_rapid_solicit_message(self): bundle = TransactionBundle(incoming_message=solicit_message, received_over_multicast=True, marks=['one']) self.very_rapid_message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsInstance(result, ReplyMessage) self.assertEqual(result.transaction_id, solicit_message.transaction_id) self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption)) self.assertEqual( result.get_option_of_type(ServerIdOption).duid, self.duid) self.assertEqual( result.get_option_of_type(IANAOption).get_option_of_type( StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL) self.assertEqual( result.get_option_of_type(IAPDOption).get_option_of_type( StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL) # Check if the handlers are called correctly for method_name in ['pre', 'handle', 'post']: method = getattr(self.dummy_handler, method_name) self.assertEqual(method.call_count, 1) args, kwargs = method.call_args self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], TransactionBundle) # Check the types and values at various stages # In the pre phase there is no response yet bundle = self.dummy_handler.pre.call_args[0][0] self.assertEqual(bundle.request, solicit_message) self.assertEqual(bundle.incoming_relay_messages, []) self.assertEqual(bundle.marks, {'one', 'pre-setup'}) self.assertIsNone(bundle.response) self.assertIsNone(bundle.outgoing_relay_messages) # In the handle phase there is an AdvertiseMessage bundle = self.dummy_handler.handle.call_args[0][0] self.assertEqual(bundle.request, solicit_message) self.assertEqual(bundle.incoming_relay_messages, []) self.assertEqual(bundle.marks, {'one', 'pre-setup', 'pre-cleanup', 'handle-setup'}) self.assertIsInstance(bundle.response, AdvertiseMessage) self.assertEqual(bundle.outgoing_relay_messages, []) # In the post phase there is a ReplyMessage(rapid commit rejections) bundle = self.dummy_handler.post.call_args[0][0] self.assertEqual(bundle.request, solicit_message) self.assertEqual(bundle.incoming_relay_messages, []) self.assertEqual( bundle.marks, { 'one', 'pre-setup', 'pre-cleanup', 'handle-setup', 'handle-cleanup', 'post-setup' }) self.assertIsInstance(bundle.response, ReplyMessage) self.assertEqual(bundle.outgoing_relay_messages, [])
def test_accept_unicast_message(self): bundle = TransactionBundle(incoming_message=solicit_message, received_over_multicast=False, marks=['unicast-me']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsInstance(result, AdvertiseMessage) self.assertIsNone(result.get_option_of_type(StatusCodeOption))
def test_empty_message(self): with self.assertLogs(level=logging.WARNING) as cm: bundle = TransactionBundle(incoming_message=RelayForwardMessage(), received_over_multicast=True) result = self.message_handler.handle(bundle, StatisticsSet()) self.assertIsNone(result) self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], '^WARNING:.*:A server should not receive')
def test_empty_confirm_message(self): bundle = TransactionBundle( incoming_message=ConfirmMessage(transaction_id=b'abcd'), received_over_multicast=True, marks=['one']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message # ConfirmMessage without IANAOption/IATAOption/IAPDOption must be ignored self.assertIsNone(result)
def test_badly_rejected_multicast_message(self): with self.assertLogs(level=logging.DEBUG) as cm: bundle = TransactionBundle(incoming_message=solicit_message, received_over_multicast=True, marks=['reject-me']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsNone(result) self.assertEqual(len(cm.output), 3) self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage') self.assertRegex(cm.output[1], '^DEBUG:.*:.*multicast is required') self.assertRegex(cm.output[2], '^ERROR:.*:Not telling client to use multicast')
def test_ignorable_multicast_message(self): with self.assertLogs(level=logging.DEBUG) as cm: bundle = TransactionBundle(incoming_message=solicit_message, received_over_multicast=True, marks=['ignore-me']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsNone(result) self.assertEqual(len(cm.output), 3) self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage') self.assertRegex(cm.output[1], '^INFO:.*:Configured to ignore SolicitMessage') self.assertRegex(cm.output[2], '^WARNING:.*:.*ignoring')
def test_empty_confirm_message(self): with self.assertLogs() as cm: bundle = TransactionBundle( incoming_message=ConfirmMessage(transaction_id=b'abcd'), received_over_multicast=True, marks=['one']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], '^WARNING:.*:No IAs present in confirm reply') # ConfirmMessage without IANAOption/IATAOption/IAPDOption must be ignored self.assertIsNone(result)
def test_reject_unicast_message(self): with self.assertLogs(level=logging.DEBUG) as cm: bundle = TransactionBundle(incoming_message=solicit_message, received_over_multicast=False) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsInstance(result, ReplyMessage) self.assertEqual( result.get_option_of_type(StatusCodeOption).status_code, STATUS_USE_MULTICAST) self.assertEqual(len(cm.output), 3) self.assertRegex(cm.output[0], '^DEBUG:.*:Handling SolicitMessage') self.assertRegex(cm.output[1], '^INFO:.*:Rejecting unicast SolicitMessage') self.assertRegex(cm.output[2], '^DEBUG:.*:.*multicast is required')
def test_not_implemented_message(self): class NotImplementedMessage(ClientServerMessage): message_type = 255 from_client_to_server = True with self.assertLogs() as cm: bundle = TransactionBundle( incoming_message=NotImplementedMessage(transaction_id=b'abcd'), received_over_multicast=True, marks=['one']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], '^WARNING:.*:Do not know how to reply') self.assertIsNone(result)
def test_request_message(self): bundle = TransactionBundle(incoming_message=request_message, received_over_multicast=True, marks=['one']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertIsInstance(result, ReplyMessage) self.assertEqual(result.transaction_id, solicit_message.transaction_id) self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption)) self.assertEqual( result.get_option_of_type(ServerIdOption).duid, self.duid) self.assertEqual( result.get_option_of_type(IANAOption).get_option_of_type( StatusCodeOption).status_code, STATUS_NO_ADDRS_AVAIL) self.assertEqual( result.get_option_of_type(IAPDOption).get_option_of_type( StatusCodeOption).status_code, STATUS_NO_PREFIX_AVAIL)
def test_confirm_message(self): with self.assertLogs() as cm: bundle = TransactionBundle(incoming_message=confirm_message, received_over_multicast=True, marks=['one']) self.message_handler.handle(bundle, StatisticsSet()) result = bundle.outgoing_message self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], '^WARNING:.*:No handler confirmed') self.assertIsInstance(result, ReplyMessage) self.assertEqual(result.transaction_id, request_message.transaction_id) self.assertEqual(result.get_option_of_type(ClientIdOption), solicit_message.get_option_of_type(ClientIdOption)) self.assertEqual( result.get_option_of_type(ServerIdOption).duid, self.duid) self.assertEqual( result.get_option_of_type(StatusCodeOption).status_code, STATUS_NOT_ON_LINK)
def handle(self, bundle: TransactionBundle, statistics: StatisticsSet): """ The main dispatcher for incoming messages. :param bundle: The transaction bundle :param statistics: Container for shared memory with statistics counters :returns: The message to reply with """ if not bundle.request: # Nothing to do... return None # Update the allow_rapid_commit flag bundle.allow_rapid_commit = self.allow_rapid_commit # Count the incoming message type statistics.count_message_in(bundle.request.message_type) # Log what we are doing (low-detail, so not DEBUG_HANDLING here) logger.debug("Handling {}".format(bundle)) # Collect the handlers handlers = self.get_handlers(bundle) # Analyse pre for handler in handlers: # noinspection PyBroadException try: handler.analyse_pre(bundle) except: # Ignore all errors, analysis isn't that important logger.exception("{} pre analysis failed".format(handler.__class__.__name__)) try: # Pre-process the request for handler in handlers: handler.pre(bundle) # Init the response self.init_response(bundle) # Process the request for handler in handlers: logger.log(DEBUG_HANDLING, "Applying {}".format(handler)) handler.handle(bundle) # Post-process the request for handler in handlers: handler.post(bundle) except ForOtherServerError as e: # Specific form of CannotRespondError that should have its own log message message = str(e) or 'Message is for another server' logger.debug("{}: ignoring".format(message)) statistics.count_for_other_server() bundle.response = None except CannotRespondError as e: message = str(e) or 'Cannot respond to this message' logger.warning("{}: ignoring".format(message)) statistics.count_do_not_respond() bundle.response = None except UseMulticastError: logger.debug("Unicast request received when multicast is required: informing client") statistics.count_use_multicast() bundle.response = self.construct_use_multicast_reply(bundle) except ReplyWithStatusError as e: # Leasequery has its own reply message type if isinstance(e, ReplyWithLeasequeryError): bundle.response = self.construct_leasequery_status_reply(bundle, e.option) else: bundle.response = self.construct_plain_status_reply(bundle, e.option) logger.warning("Replying with {}".format(e)) # Update the right counter based on the status code if e.option.status_code == STATUS_UNKNOWN_QUERY_TYPE: statistics.count_unknown_query_type() elif e.option.status_code == STATUS_MALFORMED_QUERY: statistics.count_malformed_query() elif e.option.status_code == STATUS_NOT_ALLOWED: statistics.count_not_allowed() else: statistics.count_other_error() # Analyse post for handler in handlers: # noinspection PyBroadException try: handler.analyse_post(bundle) except: # Ignore all errors, analysis isn't that important logger.exception("{} post analysis failed".format(handler.__class__.__name__)) if bundle.response: logger.log(DEBUG_HANDLING, "Responding with {}".format(bundle.response.__class__.__name__)) # Count the outgoing message type statistics.count_message_out(bundle.response.message_type) else: logger.log(DEBUG_HANDLING, "Not responding")
def handle(self, bundle: TransactionBundle, statistics: StatisticsSet): """ The main dispatcher for incoming messages. :param bundle: The transaction bundle :param statistics: Container for shared memory with statistics counters :returns: The message to reply with """ if not bundle.request: # Nothing to do... return None # Update the allow_rapid_commit flag bundle.allow_rapid_commit = self.allow_rapid_commit # Count the incoming message type statistics.count_message_in(bundle.request.message_type) # Log what we are doing (low-detail, so not DEBUG_HANDLING here) logger.debug("Handling {}".format(bundle)) # Collect the handlers handlers = self.get_handlers(bundle) # Analyse pre for handler in handlers: # noinspection PyBroadException try: handler.analyse_pre(bundle) except: # Ignore all errors, analysis isn't that important pass try: # Pre-process the request for handler in handlers: handler.pre(bundle) # Init the response self.init_response(bundle) # Process the request for handler in handlers: logger.log(DEBUG_HANDLING, "Applying {}".format(handler)) handler.handle(bundle) # Post-process the request for handler in handlers: handler.post(bundle) except ForOtherServerError as e: # Specific form of CannotRespondError that should have its own log message message = str(e) or 'Message is for another server' logger.debug("{}: ignoring".format(message)) statistics.count_for_other_server() bundle.response = None except CannotRespondError as e: message = str(e) or 'Cannot respond to this message' logger.warning("{}: ignoring".format(message)) statistics.count_do_not_respond() bundle.response = None except UseMulticastError: logger.debug("Unicast request received when multicast is required: informing client") statistics.count_use_multicast() bundle.response = self.construct_use_multicast_reply(bundle) # Analyse post for handler in handlers: # noinspection PyBroadException try: handler.analyse_post(bundle) except: # Ignore all errors, analysis isn't that important pass if bundle.response: logger.log(DEBUG_HANDLING, "Responding with {}".format(bundle.response.__class__.__name__)) # Count the outgoing message type statistics.count_message_out(bundle.response.message_type) else: logger.log(DEBUG_HANDLING, "Not responding")
def handle(self, bundle: TransactionBundle, statistics: StatisticsSet): """ The main dispatcher for incoming messages. :param bundle: The transaction bundle :param statistics: Container for shared memory with statistics counters """ if not bundle.request: # Nothing to do... return # Update the allow_rapid_commit flag bundle.allow_rapid_commit = self.allow_rapid_commit # Count the incoming message type statistics.count_message_in(bundle.request.message_type) # Log what we are doing (low-detail, so not DEBUG_HANDLING here) logger.debug("Handling {}".format(bundle)) # Collect the handlers handlers = self.get_handlers(bundle) # Analyse pre for handler in handlers: # noinspection PyBroadException try: handler.analyse_pre(bundle) except: # Ignore all errors, analysis isn't that important logger.exception("{} pre analysis failed".format( handler.__class__.__name__)) try: # Pre-process the request for handler in handlers: handler.pre(bundle) # Init the response self.init_response(bundle) # Process the request for handler in handlers: logger.log(DEBUG_HANDLING, "Applying {}".format(handler)) handler.handle(bundle) # Post-process the request for handler in handlers: handler.post(bundle) except ForOtherServerError as e: # Specific form of CannotRespondError that should have its own log message message = str(e) or 'Message is for another server' logger.debug("{}: ignoring".format(message)) statistics.count_for_other_server() bundle.response = None except CannotRespondError as e: message = str(e) or 'Cannot respond to this message' logger.warning("{}: ignoring".format(message)) statistics.count_do_not_respond() bundle.response = None except UseMulticastError: logger.debug( "Unicast request received when multicast is required: informing client" ) statistics.count_use_multicast() bundle.response = self.construct_use_multicast_reply(bundle) except ReplyWithStatusError as e: # Leasequery has its own reply message type if isinstance(e, ReplyWithLeasequeryError): bundle.response = self.construct_leasequery_status_reply( bundle, e.option) else: bundle.response = self.construct_plain_status_reply( bundle, e.option) logger.warning("Replying with {}".format(e)) # Update the right counter based on the status code if e.option.status_code == STATUS_UNKNOWN_QUERY_TYPE: statistics.count_unknown_query_type() elif e.option.status_code == STATUS_MALFORMED_QUERY: statistics.count_malformed_query() elif e.option.status_code == STATUS_NOT_ALLOWED: statistics.count_not_allowed() else: statistics.count_other_error() # Analyse post for handler in handlers: # noinspection PyBroadException try: handler.analyse_post(bundle) except: # Ignore all errors, analysis isn't that important logger.exception("{} post analysis failed".format( handler.__class__.__name__)) if bundle.response: logger.log( DEBUG_HANDLING, "Responding with {}".format( bundle.response.__class__.__name__)) # Count the outgoing message type statistics.count_message_out(bundle.response.message_type) else: logger.log(DEBUG_HANDLING, "Not responding")