예제 #1
0
    def test_gateway_scan_filter_match(self):
        """Test match function of gateway filter."""
        # pylint: disable=too-many-locals
        gateway_1 = GatewayDescriptor(name='KNX-Interface',
                                      ip_addr='10.1.1.11',
                                      port=3761,
                                      local_interface='en1',
                                      local_ip='110.1.1.100',
                                      supports_tunnelling=True,
                                      supports_routing=False)
        gateway_2 = GatewayDescriptor(name='KNX-Router',
                                      ip_addr='10.1.1.12',
                                      port=3761,
                                      local_interface='en1',
                                      local_ip='10.1.1.100',
                                      supports_tunnelling=False,
                                      supports_routing=True)
        filter_tunnel = GatewayScanFilter(tunnelling=True)
        filter_router = GatewayScanFilter(routing=True)
        filter_name = GatewayScanFilter(name="KNX-Router")
        filter_no_tunnel = GatewayScanFilter(tunnelling=False)

        self.assertTrue(filter_tunnel.match(gateway_1))
        self.assertFalse(filter_tunnel.match(gateway_2))
        self.assertFalse(filter_router.match(gateway_1))
        self.assertTrue(filter_router.match(gateway_2))
        self.assertFalse(filter_name.match(gateway_1))
        self.assertTrue(filter_name.match(gateway_2))
        self.assertFalse(filter_no_tunnel.match(gateway_1))
        self.assertTrue(filter_no_tunnel.match(gateway_2))
예제 #2
0
 def response_rec_callback(
     self, knxipframe: KNXIPFrame, source: HPAI, _: KNXIPTransport
 ) -> None:
     """Verify and handle knxipframe. Callback from internal transport."""
     if not isinstance(knxipframe.body, DescriptionResponse):
         logger.warning("Wrong knxipframe for DescriptionRequest: %s", knxipframe)
         return
     self.response_received_event.set()
     # Set gateway descriptior attribute
     gateway = GatewayDescriptor(
         ip_addr=self.transport.remote_addr[0],
         port=self.transport.remote_addr[1],
         local_ip=self.transport.getsockname()[0],
     )
     gateway.parse_dibs(knxipframe.body.dibs)
     self.gateway_descriptor = gateway
def _gateway_descriptor(ip: str, port: int) -> GatewayDescriptor:
    """Get mock gw descriptor."""
    return GatewayDescriptor(
        "Test",
        ip,
        port,
        "eth0",
        "127.0.0.1",
        supports_routing=True,
        supports_tunnelling=True,
    )
예제 #4
0
 def test_gateway_descriptor(self):
     """Test string representation of GatewayDescriptor."""
     gateway_descriptor = GatewayDescriptor(name='KNX-Interface',
                                            ip_addr='192.168.2.3',
                                            port=1234,
                                            local_interface='en1',
                                            local_ip='192.168.2.50',
                                            supports_tunnelling=True,
                                            supports_routing=False)
     self.assertEqual(
         str(gateway_descriptor),
         '<GatewayDescriptor name="KNX-Interface" addr="192.168.2.3:1234" local="192.168.2.50@en1" routing="False" tunnelling="True" />'
     )
예제 #5
0
 def test_gateway_descriptor(self):
     """Test string representation of GatewayDescriptor."""
     gateway_descriptor = GatewayDescriptor(
         name="KNX-Interface",
         ip_addr="192.168.2.3",
         port=1234,
         local_interface="en1",
         local_ip="192.168.2.50",
         supports_tunnelling=True,
         supports_routing=False,
         individual_address=IndividualAddress("1.1.1"),
     )
     assert str(gateway_descriptor) == "1.1.1 - KNX-Interface @ 192.168.2.3:1234"
예제 #6
0
def _gateway_descriptor(
    ip: str, port: int, supports_tunnelling_tcp: bool = False
) -> GatewayDescriptor:
    """Get mock gw descriptor."""
    return GatewayDescriptor(
        name="Test",
        ip_addr=ip,
        port=port,
        local_interface="eth0",
        local_ip="127.0.0.1",
        supports_routing=True,
        supports_tunnelling=True,
        supports_tunnelling_tcp=supports_tunnelling_tcp,
    )
예제 #7
0
    def test_parser(self, raw, expected):
        """Test parsing GatewayDescriptor objects from real-world responses."""
        response = KNXIPFrame()
        response.from_knx(raw)
        assert isinstance(response.body,
                          (SearchResponse, SearchResponseExtended))

        descriptor = GatewayDescriptor(
            ip_addr=response.body.control_endpoint.ip_addr,
            port=response.body.control_endpoint.port,
        )
        descriptor.parse_dibs(response.body.dibs)

        assert descriptor.supports_routing is expected["supports_routing"]
        assert descriptor.supports_tunnelling is expected["supports_tunnelling"]
        assert descriptor.supports_tunnelling_tcp is expected[
            "supports_tunnelling_tcp"]

        assert descriptor.supports_secure is expected["supports_secure"]

        assert descriptor.routing_requires_secure is expected[
            "routing_requires_secure"]
        assert (descriptor.tunnelling_requires_secure is
                expected["tunnelling_requires_secure"])
예제 #8
0
    def test_search_response_reception(self):
        """Test function of gateway scanner."""
        # pylint: disable=protected-access
        xknx = XKNX(loop=self.loop)
        gateway_scanner = GatewayScanner(xknx)
        search_response = fake_router_search_response(xknx)
        udp_client = unittest.mock.create_autospec(UDPClient)
        udp_client.local_addr = ("192.168.42.50", 0, "en1")
        udp_client.getsockname.return_value = ("192.168.42.50", 0)
        router_gw_descriptor = GatewayDescriptor(name="Gira KNX/IP-Router",
                                                 ip_addr="192.168.42.10",
                                                 port=3671,
                                                 local_interface="en1",
                                                 local_ip="192.168.42.50",
                                                 supports_tunnelling=True,
                                                 supports_routing=True)

        self.assertEqual(gateway_scanner.found_gateways, [])
        gateway_scanner._response_rec_callback(search_response, udp_client)
        self.assertEqual(str(gateway_scanner.found_gateways[0]),
                         str(router_gw_descriptor))
예제 #9
0
    def setUp(self):
        """Set up test class."""
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self.gateway_desc_interface = GatewayDescriptor(
            name="KNX-Interface",
            ip_addr="10.1.1.11",
            port=3761,
            local_interface="en1",
            local_ip="110.1.1.100",
            supports_tunnelling=True,
            supports_routing=False,
        )
        self.gateway_desc_router = GatewayDescriptor(
            name="KNX-Router",
            ip_addr="10.1.1.12",
            port=3761,
            local_interface="en1",
            local_ip="10.1.1.100",
            supports_tunnelling=False,
            supports_routing=True,
        )
        self.gateway_desc_both = GatewayDescriptor(
            name="Gira KNX/IP-Router",
            ip_addr="192.168.42.10",
            port=3671,
            local_interface="en1",
            local_ip="192.168.42.50",
            supports_tunnelling=True,
            supports_routing=True,
        )

        self.fake_interfaces = ["lo0", "en0", "en1"]
        self.fake_ifaddresses = {
            "lo0": {
                2: [{
                    "addr": "127.0.0.1",
                    "netmask": "255.0.0.0",
                    "peer": "127.0.0.1"
                }],
                30: [
                    {
                        "addr": "::1",
                        "netmask":
                        "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128",
                        "peer": "::1",
                        "flags": 0,
                    },
                    {
                        "addr": "fe80::1%lo0",
                        "netmask": "ffff:ffff:ffff:ffff::/64",
                        "flags": 0,
                    },
                ],
            },
            "en0": {
                18: [{
                    "addr": "FF:FF:00:00:00:00"
                }]
            },
            "en1": {
                18: [{
                    "addr": "FF:FF:00:00:00:01"
                }],
                30: [{
                    "addr": "fe80::1234:1234:1234:1234%en1",
                    "netmask": "ffff:ffff:ffff:ffff::/64",
                    "flags": 1024,
                }],
                2: [{
                    "addr": "10.1.1.2",
                    "netmask": "255.255.255.0",
                    "broadcast": "10.1.1.255",
                }],
            },
        }
예제 #10
0
class TestGatewayScanner:
    """Test class for xknx/io/GatewayScanner objects."""

    gateway_desc_interface = GatewayDescriptor(
        name="KNX-Interface",
        ip_addr="10.1.1.11",
        port=3761,
        local_interface="en1",
        local_ip="110.1.1.100",
        supports_tunnelling=True,
        supports_routing=False,
    )
    gateway_desc_router = GatewayDescriptor(
        name="KNX-Router",
        ip_addr="10.1.1.12",
        port=3761,
        local_interface="en1",
        local_ip="10.1.1.100",
        supports_tunnelling=False,
        supports_routing=True,
    )
    gateway_desc_both = GatewayDescriptor(
        name="Gira KNX/IP-Router",
        ip_addr="192.168.42.10",
        port=3671,
        local_interface="en1",
        local_ip="192.168.42.50",
        supports_tunnelling=True,
        supports_routing=True,
        individual_address=IndividualAddress("1.1.0"),
    )
    gateway_desc_neither = GatewayDescriptor(
        name="AC/S 1.1.1 Application Control",
        ip_addr="10.1.1.15",
        port=3671,
        local_interface="en1",
        local_ip="192.168.42.50",
        supports_tunnelling=False,
        supports_routing=False,
    )

    fake_interfaces = ["lo0", "en0", "en1"]
    fake_ifaddresses = {
        "lo0": {
            2: [{
                "addr": "127.0.0.1",
                "netmask": "255.0.0.0",
                "peer": "127.0.0.1"
            }],
            30: [
                {
                    "addr": "::1",
                    "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128",
                    "peer": "::1",
                    "flags": 0,
                },
                {
                    "addr": "fe80::1%lo0",
                    "netmask": "ffff:ffff:ffff:ffff::/64",
                    "flags": 0,
                },
            ],
        },
        "en0": {
            18: [{
                "addr": "FF:FF:00:00:00:00"
            }]
        },
        "en1": {
            18: [{
                "addr": "FF:FF:00:00:00:01"
            }],
            30: [{
                "addr": "fe80::1234:1234:1234:1234%en1",
                "netmask": "ffff:ffff:ffff:ffff::/64",
                "flags": 1024,
            }],
            2: [{
                "addr": "10.1.1.2",
                "netmask": "255.255.255.0",
                "broadcast": "10.1.1.255",
            }],
        },
    }

    def test_gateway_scan_filter_match(self):
        """Test match function of gateway filter."""
        filter_default = GatewayScanFilter()
        filter_tunnel = GatewayScanFilter(tunnelling=True)
        filter_router = GatewayScanFilter(routing=True)
        filter_name = GatewayScanFilter(name="KNX-Router")
        filter_no_tunnel = GatewayScanFilter(tunnelling=False)
        filter_no_router = GatewayScanFilter(routing=False)
        filter_tunnel_and_router = GatewayScanFilter(tunnelling=True,
                                                     routing=True)

        assert filter_default.match(self.gateway_desc_interface)
        assert filter_default.match(self.gateway_desc_router)
        assert filter_default.match(self.gateway_desc_both)
        assert not filter_default.match(self.gateway_desc_neither)

        assert filter_tunnel.match(self.gateway_desc_interface)
        assert not filter_tunnel.match(self.gateway_desc_router)
        assert filter_tunnel.match(self.gateway_desc_both)
        assert not filter_tunnel.match(self.gateway_desc_neither)

        assert not filter_router.match(self.gateway_desc_interface)
        assert filter_router.match(self.gateway_desc_router)
        assert filter_router.match(self.gateway_desc_both)
        assert not filter_router.match(self.gateway_desc_neither)

        assert not filter_name.match(self.gateway_desc_interface)
        assert filter_name.match(self.gateway_desc_router)
        assert not filter_name.match(self.gateway_desc_both)
        assert not filter_name.match(self.gateway_desc_neither)

        assert not filter_no_tunnel.match(self.gateway_desc_interface)
        assert filter_no_tunnel.match(self.gateway_desc_router)
        assert not filter_no_tunnel.match(self.gateway_desc_both)
        assert not filter_no_tunnel.match(self.gateway_desc_neither)

        assert filter_no_router.match(self.gateway_desc_interface)
        assert not filter_no_router.match(self.gateway_desc_router)
        assert not filter_no_router.match(self.gateway_desc_both)
        assert not filter_no_router.match(self.gateway_desc_neither)

        assert not filter_tunnel_and_router.match(self.gateway_desc_interface)
        assert not filter_tunnel_and_router.match(self.gateway_desc_router)
        assert filter_tunnel_and_router.match(self.gateway_desc_both)
        assert not filter_tunnel_and_router.match(self.gateway_desc_neither)

    def test_search_response_reception(self):
        """Test function of gateway scanner."""
        xknx = XKNX()
        gateway_scanner = GatewayScanner(xknx)
        test_search_response = fake_router_search_response(xknx)
        udp_client_mock = create_autospec(UDPClient)
        udp_client_mock.local_addr = ("192.168.42.50", 0)
        udp_client_mock.getsockname.return_value = ("192.168.42.50", 0)

        assert gateway_scanner.found_gateways == []
        gateway_scanner._response_rec_callback(
            test_search_response,
            udp_client_mock,
            interface="en1",
        )

        assert str(gateway_scanner.found_gateways[0]) == str(
            self.gateway_desc_both)
        assert len(gateway_scanner.found_gateways) == 1

        gateway_scanner._response_rec_callback(
            test_search_response,
            udp_client_mock,
            interface="eth1",
        )
        assert len(gateway_scanner.found_gateways) == 1

    @patch("xknx.io.gateway_scanner.netifaces", autospec=True)
    async def test_scan_timeout(self, netifaces_mock):
        """Test gateway scanner timeout."""
        xknx = XKNX()
        # No interface shall be found
        netifaces_mock.interfaces.return_value = []

        gateway_scanner = GatewayScanner(xknx)
        gateway_scanner._response_received_event.wait = MagicMock(
            side_effect=asyncio.TimeoutError())
        timed_out_scan = await gateway_scanner.scan()
        # Unsuccessfull scan() returns None
        assert timed_out_scan == []

    @patch("xknx.io.gateway_scanner.netifaces", autospec=True)
    @patch("xknx.io.GatewayScanner._search_interface", autospec=True)
    async def test_send_search_requests(self, _search_interface_mock,
                                        netifaces_mock):
        """Test finding all valid interfaces to send search requests to. No requests are sent."""
        xknx = XKNX()

        netifaces_mock.interfaces.return_value = self.fake_interfaces
        netifaces_mock.ifaddresses = lambda interface: self.fake_ifaddresses[
            interface]
        netifaces_mock.AF_INET = 2

        async def async_none():
            return None

        _search_interface_mock.return_value = asyncio.ensure_future(
            async_none())

        gateway_scanner = GatewayScanner(xknx, timeout_in_seconds=0)

        test_scan = await gateway_scanner.scan()

        assert _search_interface_mock.call_count == 2
        expected_calls = [
            ((gateway_scanner, "lo0", "127.0.0.1"), ),
            ((gateway_scanner, "en1", "10.1.1.2"), ),
        ]
        assert _search_interface_mock.call_args_list == expected_calls
        assert test_scan == []
예제 #11
0
    def setUp(self):
        """Set up test class."""
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self.gateway_desc_interface = GatewayDescriptor(
            name='KNX-Interface',
            ip_addr='10.1.1.11',
            port=3761,
            local_interface='en1',
            local_ip='110.1.1.100',
            supports_tunnelling=True,
            supports_routing=False)
        self.gateway_desc_router = GatewayDescriptor(name='KNX-Router',
                                                     ip_addr='10.1.1.12',
                                                     port=3761,
                                                     local_interface='en1',
                                                     local_ip='10.1.1.100',
                                                     supports_tunnelling=False,
                                                     supports_routing=True)
        self.gateway_desc_both = GatewayDescriptor(name="Gira KNX/IP-Router",
                                                   ip_addr="192.168.42.10",
                                                   port=3671,
                                                   local_interface="en1",
                                                   local_ip="192.168.42.50",
                                                   supports_tunnelling=True,
                                                   supports_routing=True)

        self.fake_interfaces = ['lo0', 'en0', 'en1']
        self.fake_ifaddresses = {
            'lo0': {
                2: [{
                    'addr': '127.0.0.1',
                    'netmask': '255.0.0.0',
                    'peer': '127.0.0.1'
                }],
                30: [{
                    'addr': '::1',
                    'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128',
                    'peer': '::1',
                    'flags': 0
                }, {
                    'addr': 'fe80::1%lo0',
                    'netmask': 'ffff:ffff:ffff:ffff::/64',
                    'flags': 0
                }]
            },
            'en0': {
                18: [{
                    'addr': 'FF:FF:00:00:00:00'
                }]
            },
            'en1': {
                18: [{
                    'addr': 'FF:FF:00:00:00:01'
                }],
                30: [{
                    'addr': 'fe80::1234:1234:1234:1234%en1',
                    'netmask': 'ffff:ffff:ffff:ffff::/64',
                    'flags': 1024
                }],
                2: [{
                    'addr': '10.1.1.2',
                    'netmask': '255.255.255.0',
                    'broadcast': '10.1.1.255'
                }]
            }
        }
예제 #12
0
def _gateway_descriptor(ip: str, port: int) -> GatewayDescriptor:
    """Get mock gw descriptor."""
    return GatewayDescriptor("Test", ip, port, "eth0", "127.0.0.1", True)
예제 #13
0
class TestGatewayScanner:
    """Test class for xknx/io/GatewayScanner objects."""

    gateway_desc_interface = GatewayDescriptor(
        name="KNX-Interface",
        ip_addr="10.1.1.11",
        port=3761,
        local_interface="en1",
        local_ip="10.1.1.100",
        supports_tunnelling=True,
        supports_routing=False,
    )
    gateway_desc_router = GatewayDescriptor(
        name="KNX-Router",
        ip_addr="10.1.1.12",
        port=3761,
        local_interface="en1",
        local_ip="10.1.1.100",
        supports_tunnelling=False,
        supports_routing=True,
    )
    gateway_desc_both = GatewayDescriptor(
        name="Gira KNX/IP-Router",
        ip_addr="192.168.42.10",
        port=3671,
        local_interface="en1",
        local_ip="192.168.42.50",
        supports_tunnelling=True,
        supports_tunnelling_tcp=True,
        supports_routing=True,
        individual_address=IndividualAddress("1.1.0"),
    )
    gateway_desc_both.tunnelling_requires_secure = False
    gateway_desc_neither = GatewayDescriptor(
        name="AC/S 1.1.1 Application Control",
        ip_addr="10.1.1.15",
        port=3671,
        local_interface="en1",
        local_ip="192.168.42.50",
        supports_tunnelling=False,
        supports_routing=False,
    )
    gateway_desc_secure_tunnel = GatewayDescriptor(
        name="KNX-Interface",
        ip_addr="10.1.1.11",
        port=3761,
        local_interface="en1",
        local_ip="10.1.1.111",
        supports_routing=False,
        supports_tunnelling=True,
        supports_tunnelling_tcp=True,
        supports_secure=True,
    )
    gateway_desc_secure_tunnel.tunnelling_requires_secure = True

    def test_gateway_scan_filter_match(self):
        """Test match function of gateway filter."""
        filter_default = GatewayScanFilter()
        filter_tunnel = GatewayScanFilter(tunnelling=True)
        filter_tcp_tunnel = GatewayScanFilter(tunnelling_tcp=True, secure=None)
        filter_secure_tunnel = GatewayScanFilter(tunnelling_tcp=True,
                                                 secure=True)
        filter_router = GatewayScanFilter(routing=True)
        filter_name = GatewayScanFilter(name="KNX-Router")
        filter_no_tunnel = GatewayScanFilter(tunnelling=False)
        filter_no_router = GatewayScanFilter(routing=False)
        filter_tunnel_and_router = GatewayScanFilter(tunnelling=True,
                                                     routing=True)

        assert filter_default.match(self.gateway_desc_interface)
        assert filter_default.match(self.gateway_desc_router)
        assert filter_default.match(self.gateway_desc_both)
        assert not filter_default.match(self.gateway_desc_neither)
        assert not filter_default.match(self.gateway_desc_secure_tunnel)

        assert filter_tunnel.match(self.gateway_desc_interface)
        assert not filter_tunnel.match(self.gateway_desc_router)
        assert filter_tunnel.match(self.gateway_desc_both)
        assert not filter_tunnel.match(self.gateway_desc_neither)
        assert not filter_tunnel.match(self.gateway_desc_secure_tunnel)

        assert not filter_tcp_tunnel.match(self.gateway_desc_interface)
        assert not filter_tcp_tunnel.match(self.gateway_desc_router)
        assert filter_tcp_tunnel.match(self.gateway_desc_both)
        assert not filter_tcp_tunnel.match(self.gateway_desc_neither)
        assert filter_tcp_tunnel.match(self.gateway_desc_secure_tunnel)

        assert not filter_secure_tunnel.match(self.gateway_desc_interface)
        assert not filter_secure_tunnel.match(self.gateway_desc_router)
        assert not filter_secure_tunnel.match(self.gateway_desc_both)
        assert not filter_secure_tunnel.match(self.gateway_desc_neither)
        assert filter_secure_tunnel.match(self.gateway_desc_secure_tunnel)

        assert not filter_router.match(self.gateway_desc_interface)
        assert filter_router.match(self.gateway_desc_router)
        assert filter_router.match(self.gateway_desc_both)
        assert not filter_router.match(self.gateway_desc_neither)
        assert not filter_router.match(self.gateway_desc_secure_tunnel)

        assert not filter_name.match(self.gateway_desc_interface)
        assert filter_name.match(self.gateway_desc_router)
        assert not filter_name.match(self.gateway_desc_both)
        assert not filter_name.match(self.gateway_desc_neither)
        assert not filter_name.match(self.gateway_desc_secure_tunnel)

        assert not filter_no_tunnel.match(self.gateway_desc_interface)
        assert filter_no_tunnel.match(self.gateway_desc_router)
        assert not filter_no_tunnel.match(self.gateway_desc_both)
        assert not filter_no_tunnel.match(self.gateway_desc_neither)
        assert not filter_no_tunnel.match(self.gateway_desc_secure_tunnel)

        assert filter_no_router.match(self.gateway_desc_interface)
        assert not filter_no_router.match(self.gateway_desc_router)
        assert not filter_no_router.match(self.gateway_desc_both)
        assert not filter_no_router.match(self.gateway_desc_neither)
        assert not filter_no_router.match(self.gateway_desc_secure_tunnel)

        assert not filter_tunnel_and_router.match(self.gateway_desc_interface)
        assert not filter_tunnel_and_router.match(self.gateway_desc_router)
        assert filter_tunnel_and_router.match(self.gateway_desc_both)
        assert not filter_tunnel_and_router.match(self.gateway_desc_neither)
        assert not filter_tunnel_and_router.match(
            self.gateway_desc_secure_tunnel)

    def test_search_response_reception(self):
        """Test function of gateway scanner."""
        xknx = XKNX()
        gateway_scanner = GatewayScanner(xknx)
        test_search_response = fake_router_search_response()
        udp_transport_mock = create_autospec(UDPTransport)
        udp_transport_mock.local_addr = ("192.168.42.50", 0)
        udp_transport_mock.getsockname.return_value = ("192.168.42.50", 0)

        assert not gateway_scanner.found_gateways
        gateway_scanner._response_rec_callback(
            test_search_response,
            HPAI("192.168.42.50", 0),
            udp_transport_mock,
            interface="en1",
        )
        assert len(gateway_scanner.found_gateways) == 1

        gateway_scanner._response_rec_callback(
            test_search_response,
            HPAI("192.168.42.230", 0),
            udp_transport_mock,
            interface="eth1",
        )
        assert len(gateway_scanner.found_gateways) == 1

        assert str(gateway_scanner.found_gateways[
            test_search_response.body.control_endpoint]) == str(
                self.gateway_desc_both)

    @patch("xknx.io.gateway_scanner.UDPTransport.connect")
    @patch("xknx.io.gateway_scanner.UDPTransport.send")
    @patch(
        "xknx.io.gateway_scanner.UDPTransport.getsockname",
        return_value=("10.1.1.2", 56789),
    )
    async def test_scan_timeout(
        self,
        getsockname_mock,
        udp_transport_send_mock,
        udp_transport_connect_mock,
        time_travel,
    ):
        """Test gateway scanner timeout."""
        xknx = XKNX()
        gateway_scanner = GatewayScanner(xknx)
        timed_out_scan_task = asyncio.create_task(gateway_scanner.scan())
        await time_travel(gateway_scanner.timeout_in_seconds)
        # Unsuccessfull scan() returns empty list
        assert await timed_out_scan_task == []

    @patch("xknx.io.gateway_scanner.UDPTransport.connect")
    @patch("xknx.io.gateway_scanner.UDPTransport.send")
    async def test_async_scan_timeout(
        self,
        udp_transport_send_mock,
        udp_transport_connect_mock,
        time_travel,
    ):
        """Test gateway scanner timeout for async generator."""
        async def test():
            xknx = XKNX()
            async for _ in GatewayScanner(xknx).async_scan():
                break
            else:
                return True

        # timeout
        with patch(
                "xknx.io.util.get_default_local_ip",
                return_value="10.1.1.2",
        ), patch(
                "xknx.io.gateway_scanner.UDPTransport.getsockname",
                return_value=("10.1.1.2", 56789),
        ):
            timed_out_scan_task = asyncio.create_task(test())
            await time_travel(3)
            assert await timed_out_scan_task
        # no matching interface found
        with patch(
                "xknx.io.util.get_default_local_ip",
                return_value=None,
        ):
            timed_out_scan_task = asyncio.create_task(test())
            await time_travel(3)
            with pytest.raises(XKNXException):
                await timed_out_scan_task

    @patch("xknx.io.gateway_scanner.UDPTransport.connect")
    @patch("xknx.io.gateway_scanner.UDPTransport.send")
    async def test_async_scan_exit(
        self,
        udp_transport_send_mock,
        udp_transport_connect_mock,
        time_travel,
    ):
        """Test gateway scanner timeout for async generator."""
        xknx = XKNX()
        test_search_response = fake_router_search_response()
        udp_transport_mock = Mock()
        udp_transport_mock.local_addr = ("10.1.1.2", 56789)

        gateway_scanner = GatewayScanner(xknx, local_ip="10.1.1.2")

        async def test():
            async for gateway in gateway_scanner.async_scan():
                assert isinstance(gateway, GatewayDescriptor)
                return True
            return False

        with patch(
                "xknx.io.gateway_scanner.UDPTransport.getsockname",
                return_value=("10.1.1.2", 56789),
        ), patch("xknx.io.gateway_scanner.UDPTransport.register_callback"
                 ) as register_callback_mock:
            scan_task = asyncio.create_task(test())
            await time_travel(0)
            _fished_response_rec_callback = register_callback_mock.call_args.args[
                0]
            _fished_response_rec_callback(
                test_search_response,
                HPAI("192.168.42.50", 0),
                udp_transport_mock,
            )
            assert await scan_task
            await time_travel(0)  # for task cleanup

    @patch("xknx.io.gateway_scanner.UDPTransport.connect")
    @patch("xknx.io.gateway_scanner.UDPTransport.send")
    async def test_send_search_requests(
        self,
        udp_transport_send_mock,
        udp_transport_connect_mock,
    ):
        """Test if both search requests are sent per interface."""
        xknx = XKNX()
        gateway_scanner = GatewayScanner(xknx, timeout_in_seconds=0)
        with patch(
                "xknx.io.util.get_default_local_ip",
                return_value="10.1.1.2",
        ), patch(
                "xknx.io.util.get_local_interface_name",
                return_value="en_0123",
        ), patch(
                "xknx.io.gateway_scanner.UDPTransport.getsockname",
                return_value=("10.1.1.2", 56789),
        ):
            await gateway_scanner.scan()

        assert udp_transport_connect_mock.call_count == 1
        assert udp_transport_send_mock.call_count == 2
        frame_1 = udp_transport_send_mock.call_args_list[0].args[0]
        frame_2 = udp_transport_send_mock.call_args_list[1].args[0]
        assert isinstance(frame_1.body, SearchRequestExtended)
        assert isinstance(frame_2.body, SearchRequest)
        assert frame_1.body.discovery_endpoint == HPAI(ip_addr="10.1.1.2",
                                                       port=56789)