def test_failed_send_relayed(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)
        link_local_socket.pretend_sendto_fails = True

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        outgoing_message = RelayReplyMessage(hop_count=0,
                                             link_address=IPv6Address('2001:db8::1'),
                                             peer_address=IPv6Address('fe80::babe'),
                                             options=[
                                                 InterfaceIdOption(interface_id=b'eth0'),
                                                 RelayMessageOption(relayed_message=relayed_advertise_message)
                                             ])

        with self.assertLogs(level=logging.DEBUG) as logged:
            success = listening_socket.send_reply(outgoing_message)
            self.assertFalse(success)

        # Nothing should be sent from a multicast socket
        with self.assertRaises(IndexError):
            multicast_socket.read_from_outgoing_queue()

        # It must be on the link local socket
        sent_packet, recipient = link_local_socket.read_from_outgoing_queue()
        self.assertNotEqual(sent_packet, relayed_advertise_packet)

        log_output = '\n'.join(logged.output)
        self.assertRegex(log_output, r'AdvertiseMessage')
        self.assertRegex(log_output, r'to fe80::3631:c4ff:fe3c:b2f1')
        self.assertRegex(log_output, r"via Fa2/3 of relay fe80::babe")
    def test_send_relayed_without_interface_id(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        # Start with a clean parse and then change interface-id
        new_message = Message.parse(relayed_advertise_packet)[1]
        interface_id_option = new_message.inner_relay_message.get_option_of_type(InterfaceIdOption)
        new_message.inner_relay_message.options.remove(interface_id_option)

        outgoing_message = RelayReplyMessage(hop_count=0,
                                             link_address=IPv6Address('2001:db8::1'),
                                             peer_address=IPv6Address('fe80::babe'),
                                             options=[
                                                 InterfaceIdOption(interface_id=b'eth0'),
                                                 RelayMessageOption(relayed_message=new_message)
                                             ])

        with self.assertLogs(level=logging.DEBUG) as logged:
            listening_socket.send_reply(outgoing_message)

        log_output = '\n'.join(logged.output)
        self.assertRegex(log_output, r'Sent AdvertiseMessage')
        self.assertRegex(log_output, r'to fe80::3631:c4ff:fe3c:b2f1')
        self.assertRegex(log_output, r"via relay fe80::babe")
    def test_send_unwrapped(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        with self.assertRaisesRegex(ValueError, r'has to be wrapped'):
            listening_socket.send_reply(advertise_message)
    def test_receive_bad_message(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        multicast_socket.add_to_incoming_queue(b'\x01ThisIsNotAValidDHCPv6Message', ('2001:db8::babe', 546, 0, 42))
        with self.assertRaisesRegex(InvalidPacketError, r"Invalid packet from \('2001:db8::babe', 546, 0, 42\)"):
            listening_socket.recv_request()
    def test_constructor_link_local(self):
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', link_local_socket, global_address=IPv6Address('2001:db8::1'))

        self.assertEqual(listening_socket.interface_name, 'eth0')
        self.assertEqual(listening_socket.interface_id, b'eth0')
        self.assertEqual(listening_socket.interface_index, 42)
        self.assertEqual(listening_socket.listen_socket, link_local_socket)
        self.assertEqual(listening_socket.listen_address, IPv6Address('fe80::1'))
        self.assertEqual(listening_socket.reply_socket, link_local_socket)
        self.assertEqual(listening_socket.reply_address, IPv6Address('fe80::1'))
        self.assertEqual(listening_socket.global_address, IPv6Address('2001:db8::1'))
        self.assertEqual(listening_socket.fileno(), 1608)
    def test_send_empty_wrapper(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        outgoing_message = RelayReplyMessage(hop_count=0,
                                             link_address=IPv6Address('2001:db8::1'),
                                             peer_address=IPv6Address('fe80::babe'),
                                             options=[
                                                 InterfaceIdOption(interface_id=b'eth0'),
                                             ])

        with self.assertRaisesRegex(ValueError, r'not contain a message'):
            listening_socket.send_reply(outgoing_message)
    def test_receive_unknown_message_type(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        multicast_socket.add_to_incoming_queue(b'\xffThisIsNotAValidDHCPv6Message', ('2001:db8::babe', 546, 0, 42))
        received_message = listening_socket.recv_request()

        self.assertIsInstance(received_message, RelayForwardMessage)
        self.assertEqual(received_message.hop_count, 0)
        self.assertEqual(received_message.link_address, IPv6Address('2001:db8::1'))
        self.assertEqual(received_message.peer_address, IPv6Address('2001:db8::babe'))
        self.assertEqual(received_message.get_option_of_type(InterfaceIdOption).interface_id, b'eth0')
        self.assertIsInstance(received_message.relayed_message, UnknownMessage)
        self.assertEqual(received_message.relayed_message.message_type, 255)
    def test_receive_relayed_with_unprintable_interface_id(self):
        global_unicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, '2001:db8::1', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', global_unicast_socket)

        # Start with a clean parse and then change interface-id
        new_message = Message.parse(relayed_solicit_packet)[1]
        new_message.inner_relay_message.get_option_of_type(InterfaceIdOption).interface_id = b'\x80\x81\x82'

        global_unicast_socket.add_to_incoming_queue(bytes(new_message.save()), ('2001:db8::babe', 546, 0, 42))
        with self.assertLogs(level=logging.DEBUG) as logged:
            listening_socket.recv_request()

        log_output = '\n'.join(logged.output)
        self.assertRegex(log_output, r'Received SolicitMessage')
        self.assertRegex(log_output, r'from fe80::3631:c4ff:fe3c:b2f1')
        self.assertRegex(log_output, r"via b'\\x80\\x81\\x82' of relay 2001:db8::babe")
    def test_receive_relayed(self):
        global_unicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, '2001:db8::1', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', global_unicast_socket)

        global_unicast_socket.add_to_incoming_queue(relayed_solicit_packet, ('2001:db8::babe', 546, 0, 42))
        with self.assertLogs(level=logging.DEBUG) as logged:
            received_message = listening_socket.recv_request()

        self.assertIsInstance(received_message, RelayForwardMessage)
        self.assertEqual(received_message.hop_count, 2)
        self.assertEqual(received_message.link_address, IPv6Address('2001:db8::1'))
        self.assertEqual(received_message.peer_address, IPv6Address('2001:db8::babe'))
        self.assertEqual(received_message.get_option_of_type(InterfaceIdOption).interface_id, b'eth0')
        self.assertEqual(received_message.relayed_message, relayed_solicit_message)

        log_output = '\n'.join(logged.output)
        self.assertRegex(log_output, r'Received SolicitMessage')
        self.assertRegex(log_output, r'from fe80::3631:c4ff:fe3c:b2f1')
        self.assertRegex(log_output, r'via Fa2/3 of relay 2001:db8::babe')
    def test_receive_direct(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        multicast_socket.add_to_incoming_queue(solicit_packet, ('2001:db8::babe', 546, 0, 42))
        with self.assertLogs(level=logging.DEBUG) as logged:
            received_message = listening_socket.recv_request()

        self.assertIsInstance(received_message, RelayForwardMessage)
        self.assertEqual(received_message.hop_count, 0)
        self.assertEqual(received_message.link_address, IPv6Address('2001:db8::1'))
        self.assertEqual(received_message.peer_address, IPv6Address('2001:db8::babe'))
        self.assertEqual(received_message.get_option_of_type(InterfaceIdOption).interface_id, b'eth0')
        self.assertEqual(received_message.relayed_message, solicit_message)

        log_output = '\n'.join(logged.output)
        self.assertRegex(log_output, r'Received SolicitMessage')
        self.assertRegex(log_output, r'from 2001:db8::babe')
    def test_send_badly_wrapped(self):
        multicast_socket = MockSocket(AF_INET6, IPPROTO_UDP, All_DHCP_Relay_Agents_and_Servers, SERVER_PORT, 42, 1608)
        link_local_socket = MockSocket(AF_INET6, IPPROTO_UDP, 'fe80::1%eth0', SERVER_PORT, 42, 1608)

        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8::1'))

        with self.assertRaisesRegex(ValueError, r'link-address does not match'):
            listening_socket.send_reply(relayed_advertise_message)

        # Fix the link-address so the test continues to the interface-id
        # noinspection PyTypeChecker
        listening_socket = ListeningSocket('eth0', multicast_socket, link_local_socket,
                                           global_address=IPv6Address('2001:db8:ffff:1::1'))

        with self.assertRaisesRegex(ValueError, r'interface-id in the reply does not match'):
            listening_socket.send_reply(relayed_advertise_message)