Exemplo n.º 1
0
    def init_response(bundle: TransactionBundle):
        """
        Create the message object in bundle.response

        :param bundle: The transaction bundle
        """
        # Start building the response
        if isinstance(bundle.request, SolicitMessage):
            bundle.response = AdvertiseMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, (RequestMessage, RenewMessage, RebindMessage,
                                         ReleaseMessage, DeclineMessage, InformationRequestMessage)):
            bundle.response = ReplyMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, ConfirmMessage):
            # Receipt of Confirm Messages: If [...] there were no addresses in any of the IAs sent by the client, the
            # server MUST NOT send a reply to the client.
            for option in bundle.request.get_options_of_type((IANAOption, IATAOption, IAPDOption)):
                if option.get_options_of_type((IAAddressOption, IAPrefixOption)):
                    # Found an address or prefix option
                    break
            else:
                # Not found: ignore request
                raise CannotRespondError("No IAs present in confirm reply")

            bundle.response = ReplyMessage(bundle.request.transaction_id)

        else:
            raise CannotRespondError("Do not know how to reply to {}".format(type(bundle.request).__name__))

        # Build the plain chain of relay reply messages
        bundle.create_outgoing_relay_messages()
Exemplo n.º 2
0
    def init_response(bundle: TransactionBundle):
        """
        Create the message object in bundle.response

        :param bundle: The transaction bundle
        """
        # Start building the response
        if isinstance(bundle.request, SolicitMessage):
            bundle.response = AdvertiseMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, (RequestMessage, RenewMessage, RebindMessage,
                                         ReleaseMessage, DeclineMessage, InformationRequestMessage)):
            bundle.response = ReplyMessage(bundle.request.transaction_id)

        elif isinstance(bundle.request, ConfirmMessage):
            # Receipt of Confirm Messages: If [...] there were no addresses in any of the IAs sent by the client, the
            # server MUST NOT send a reply to the client.
            for option in bundle.request.get_options_of_type((IANAOption, IATAOption, IAPDOption)):
                if option.get_options_of_type((IAAddressOption, IAPrefixOption)):
                    # Found an address or prefix option
                    break
            else:
                # Not found: ignore request
                raise CannotRespondError

            bundle.response = ReplyMessage(bundle.request.transaction_id)

        else:
            logger.warning("Do not know how to reply to {}".format(type(bundle.request).__name__))
            raise CannotRespondError

        # Build the plain chain of relay reply messages
        bundle.create_outgoing_relay_messages()
Exemplo n.º 3
0
    def test_query_messed_up_prefix(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address])

        # Messed-up data, a log message should appear
        with TemporaryDirectory() as tmp_dir_name:
            store = LeasequerySqliteStore(os.path.join(tmp_dir_name, 'lq.sqlite'))
            store.worker_init([])
            store.remember_lease(bundle)

            # Mess up the data in our poor database
            db = sqlite3.connect(store.sqlite_filename)
            db.row_factory = sqlite3.Row
            db.execute("UPDATE prefixes SET first_address='2001:0db8:0000:0000:0000:0000:0000:0000'")
            db.commit()

            with self.assertLogs('', 'NOTSET')as cm:
                nr_found, results = store.find_leases(query)
                results = list(results)

            self.assertEqual(nr_found, 1)
            self.assertEqual(len(results), 1)
            self.assertEqual(len(cm.output), 1)
            self.assertRegex(cm.output[0], 'Ignoring invalid prefix range')
Exemplo n.º 4
0
    def test_remember_lease_differently(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.request.get_option_of_type(ClientIdOption)
        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)
        ia_pd_option = bundle.response.get_option_of_type(IAPDOption)
        ia_prefix = ia_pd_option.get_option_of_type(IAPrefixOption)

        remote_ids = set()
        for relay_message in bundle.incoming_relay_messages:
            for option in relay_message.get_options_of_type(RemoteIdOption):
                remote_ids.add("{}:{}".format(option.enterprise_number, normalise_hex(option.remote_id)))

        with TemporaryDirectory() as tmp_dir_name:
            store = LeasequerySqliteStore(os.path.join(tmp_dir_name, 'lq.sqlite'))
            store.worker_init([])
            store.remember_lease(bundle)

            store.remember_lease(bundle)

            # Check that the data ended up in the database
            db = sqlite3.connect(store.sqlite_filename)
            db.row_factory = sqlite3.Row

            rows = list(db.execute("SELECT * FROM clients"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            client_row = row['id']
            self.assertEqual(row['client_id'], normalise_hex(client_id_option.duid.save()))
            self.assertEqual(row['link_address'], bundle.link_address.exploded)
            self.assertAlmostEqual(row['last_interaction'], time.time(), delta=5)
            # print({key: row[key] for key in rows[0].keys()})

            rows = list(db.execute("SELECT * FROM addresses"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            self.assertEqual(row['client_fk'], client_row)
            self.assertEqual(row['address'], ia_address.address.exploded)
            self.assertAlmostEqual(row['preferred_lifetime_end'], time.time() + ia_address.preferred_lifetime, delta=5)
            self.assertAlmostEqual(row['valid_lifetime_end'], time.time() + ia_address.valid_lifetime, delta=5)
            self.assertEqual(row['options'], b'')

            rows = list(db.execute("SELECT * FROM prefixes"))
            self.assertEqual(len(rows), 1)
            row = rows[0]
            self.assertEqual(row['client_fk'], client_row)
            self.assertEqual(row['first_address'], ia_prefix.prefix[0].exploded)
            self.assertEqual(row['last_address'], ia_prefix.prefix[-1].exploded)
            self.assertAlmostEqual(row['preferred_lifetime_end'], time.time() + ia_address.preferred_lifetime, delta=5)
            self.assertAlmostEqual(row['valid_lifetime_end'], time.time() + ia_address.valid_lifetime, delta=5)
            self.assertEqual(row['options'], b'')

            rows = list(db.execute("SELECT * FROM remote_ids"))
            self.assertEqual(len(rows), len(remote_ids))
            self.assertSetEqual({row['remote_id'] for row in rows}, remote_ids)

            rows = list(db.execute("SELECT * FROM relay_ids"))
            self.assertEqual(len(rows), 1)
Exemplo n.º 5
0
    def test_query_by_unspecified_link_address(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_LINK_ADDRESS, link_address=IPv6Address('::'))

        self.query(bundle, query)
Exemplo n.º 6
0
    def test_query_by_unknown(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(-1)

        # No valid query provided, no data
        self.query_empty(bundle, query, invalid=True)
Exemplo n.º 7
0
    def test_query_by_remote_id_malformed(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_REMOTE_ID)

        with self.assertRaisesRegex(ReplyWithLeasequeryError, 'Remote-ID queries must contain a remote ID'):
            self.query(bundle, query)
Exemplo n.º 8
0
    def test_query_by_address_malformed(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        query = LQQueryOption(QUERY_BY_ADDRESS)

        with self.assertRaisesRegex(ReplyWithLeasequeryError, 'Address queries must contain an address'):
            self.query(bundle, query)
Exemplo n.º 9
0
    def test_query_by_relay_id_on_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        relay_id_option = bundle.incoming_relay_messages[-1].get_option_of_type(RelayIdOption)

        query = LQQueryOption(QUERY_BY_RELAY_ID, link_address=bundle.link_address, options=[relay_id_option])

        self.query(bundle, query)
Exemplo n.º 10
0
    def test_query_by_client_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.response.get_option_of_type(ClientIdOption)

        query = LQQueryOption(QUERY_BY_CLIENT_ID, link_address=IPv6Address('3ffe::'), options=[client_id_option])

        self.query_empty(bundle, query)
Exemplo n.º 11
0
    def test_query_by_address_with_relay_data(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address, OptionRequestOption([OPTION_LQ_RELAY_DATA])])

        self.query(bundle, query, [LQRelayDataOption])
Exemplo n.º 12
0
    def test_query_by_address_with_extra_data(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, options=[ia_address, OptionRequestOption([OPTION_DNS_SERVERS])])

        self.query(bundle, query, [RecursiveNameServersOption])
Exemplo n.º 13
0
    def test_query_by_address_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        ia_na_option = bundle.response.get_option_of_type(IANAOption)
        ia_address = ia_na_option.get_option_of_type(IAAddressOption)

        query = LQQueryOption(QUERY_BY_ADDRESS, link_address=IPv6Address('3ffe::'), options=[ia_address])

        self.query_empty(bundle, query)
Exemplo n.º 14
0
    def test_query_by_remote_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        # Test every remote-id
        for relay_message in bundle.incoming_relay_messages:
            for option in relay_message.get_options_of_type(RemoteIdOption):
                with self.subTest(msg="{}:{}".format(option.enterprise_number, normalise_hex(option.remote_id))):
                    query = LQQueryOption(QUERY_BY_REMOTE_ID, link_address=IPv6Address('3ffe::'), options=[option])
                    self.query_empty(bundle, query)
Exemplo n.º 15
0
    def test_query_by_relay_id_on_wrong_link(self):
        bundle = TransactionBundle(self.relayed_solicit_message, received_over_multicast=False)
        bundle.response = reply_message

        client_id_option = bundle.response.get_option_of_type(ClientIdOption)

        query = LQQueryOption(QUERY_BY_RELAY_ID, link_address=IPv6Address('3ffe::'),
                              options=[RelayIdOption(duid=client_id_option.duid)])

        # Our test data doesn't have a relay-id, so no results expected
        self.query_empty(bundle, query)
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    def test_remember_lease_non_interesting(self):
        bundle = TransactionBundle(confirm_message, received_over_multicast=False)
        bundle.response = reply_message

        with TemporaryDirectory() as tmp_dir_name:
            store = LeasequerySqliteStore(os.path.join(tmp_dir_name, 'lq.sqlite'))
            store.worker_init([])
            store.remember_lease(bundle)

            # Check that nothing ended up in the database
            db = sqlite3.connect(store.sqlite_filename)
            rows = list(db.execute("SELECT 1 FROM clients"))
            self.assertEqual(len(rows), 0)
Exemplo n.º 18
0
    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)
Exemplo n.º 19
0
    def handle(self, incoming_message: Message, received_over_multicast: bool,
               marks: Iterable[str] = None) -> Optional[Message]:
        """
        The main dispatcher for incoming messages.

        :param incoming_message: The parsed incoming request
        :param received_over_multicast: Whether the request was received over multicast
        :param marks: Marks to add to the transaction bundle, usually set by the listener
        :returns: The message to reply with
        """

        # Create the transaction
        bundle = TransactionBundle(incoming_message=incoming_message,
                                   received_over_multicast=received_over_multicast,
                                   allow_rapid_commit=self.allow_rapid_commit)

        if not bundle.request:
            # Nothing to do...
            return None

        # Add the marks so the filters can take them into account
        if marks:
            bundle.marks.update(marks)

        # 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)

        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 CannotRespondError:
            logger.debug("Cannot respond to this message: ignoring")
            bundle.response = None

        except UseMulticastError:
            logger.debug("Unicast request received when multicast is required: informing client")
            bundle.response = self.construct_use_multicast_reply(bundle)

        if bundle.response:
            logger.log(DEBUG_HANDLING, "Responding with {}".format(bundle.response.__class__.__name__))
        else:
            logger.log(DEBUG_HANDLING, "Not responding")

        return bundle.outgoing_message
Exemplo n.º 20
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")
Exemplo n.º 21
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")
Exemplo n.º 22
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")