Exemplo n.º 1
0
 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(),
     ]
Exemplo n.º 2
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)
    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)
Exemplo n.º 4
0
    def test_rapid_solicit_message(self):
        bundle = TransactionBundle(incoming_message=solicit_message,
                                   received_over_multicast=True,
                                   marks=['one', 'two'])
        self.rapid_message_handler.handle(bundle, StatisticsSet())
        result = bundle.outgoing_message

        self.assertIsInstance(result, AdvertiseMessage)
        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', 'two', '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', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup'})
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])

        # In the post phase there is still an AdvertiseMessage (rapid commit, but no 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', 'two', 'pre-setup', 'pre-cleanup', 'handle-setup',
                'handle-cleanup', 'post-setup'
            })
        self.assertIsInstance(bundle.response, AdvertiseMessage)
        self.assertEqual(bundle.outgoing_relay_messages, [])
Exemplo n.º 5
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.º 6
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.º 7
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))
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_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.º 10
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.º 11
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.º 12
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.º 13
0
    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)
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_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')
Exemplo n.º 16
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.º 17
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.º 18
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.º 19
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.º 20
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.º 21
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')
Exemplo n.º 22
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')
Exemplo n.º 23
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)
Exemplo n.º 24
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')
Exemplo n.º 25
0
    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)
Exemplo n.º 26
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)
Exemplo n.º 27
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)
Exemplo n.º 28
0
def parse_incoming_request(
        incoming_packet: IncomingPacketBundle) -> TransactionBundle:
    """
    Parse the incoming packet and add a RelayServerMessage around it containing the meta-data received from the
    listener.

    :param incoming_packet: The received packet
    :return: The parsed message in a transaction bundle
    """
    # Parse message and validate
    length, incoming_message = Message.parse(incoming_packet.data)
    incoming_message.validate()

    # Determine the next hop count and construct useful log messages
    if isinstance(incoming_message, RelayForwardMessage):
        next_hop_count = incoming_message.hop_count + 1
    else:
        next_hop_count = 0

    # Collect the relay options
    relay_options = []
    """:type: List[Option]"""

    relay_options.append(
        InterfaceIdOption(
            interface_id=incoming_packet.interface_name.encode('utf-8')))
    relay_options.extend(incoming_packet.extra_options)
    relay_options.append(RelayMessageOption(relayed_message=incoming_message))

    # Pretend to be an internal relay and wrap the message like a relay would
    wrapped_message = RelayForwardMessage(
        hop_count=next_hop_count,
        link_address=incoming_packet.link_address,
        peer_address=incoming_packet.sender,
        options=relay_options)

    # Create the transaction bundle
    return TransactionBundle(
        incoming_message=wrapped_message,
        received_over_multicast=incoming_packet.received_over_multicast,
        marks=incoming_packet.marks)
Exemplo n.º 29
0
 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')
Exemplo n.º 30
0
 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')