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, [])
Example #2
0
 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))
Example #3
0
    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)
Example #5
0
    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')
Example #6
0
    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')
Example #7
0
    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)
Example #8
0
    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)
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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")
Example #13
0
    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")
Example #14
0
    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")