From db8aa4658aaac4578483a26020447731f25608f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Jul 2021 11:07:52 -0500 Subject: [PATCH] Skip each ssdp listener that fails to bind (#53670) --- homeassistant/components/ssdp/__init__.py | 16 +++++- tests/components/ssdp/test_init.py | 62 +++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index f0ba8b7dcea9d2..31ebb0d1a92539 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -256,9 +256,21 @@ async def async_start(self) -> None: self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STARTED, self.flow_dispatcher.async_start ) - await asyncio.gather( - *(listener.async_start() for listener in self._ssdp_listeners) + results = await asyncio.gather( + *(listener.async_start() for listener in self._ssdp_listeners), + return_exceptions=True, ) + failed_listeners = [] + for idx, result in enumerate(results): + if isinstance(result, Exception): + _LOGGER.warning( + "Failed to setup listener for %s: %s", + self._ssdp_listeners[idx].source_ip, + result, + ) + failed_listeners.append(self._ssdp_listeners[idx]) + for listener in failed_listeners: + self._ssdp_listeners.remove(listener) self._cancel_scan = async_track_time_interval( self.hass, self.async_scan, SCAN_INTERVAL ) diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 568a2261fee001..34ca1b7228e272 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -790,3 +790,65 @@ def _callback(*_): (IPv4Address("192.168.1.5"), IPv4Address("255.255.255.255")), (IPv4Address("192.168.1.5"), None), } + + +async def test_bind_failure_skips_adapter(hass, caplog): + """Test that an adapter with a bind failure is skipped.""" + mock_get_ssdp = { + "mock-domain": [ + { + ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", + } + ] + } + create_args = [] + did_search = 0 + + @callback + def _callback(*_): + nonlocal did_search + did_search += 1 + pass + + def _generate_failing_ssdp_listener(*args, **kwargs): + create_args.append([args, kwargs]) + listener = SSDPListener(*args, **kwargs) + + async def _async_callback(*_): + if kwargs["source_ip"] == IPv6Address("2001:db8::"): + raise OSError + pass + + listener.async_start = _async_callback + listener.async_search = _callback + return listener + + with patch( + "homeassistant.components.ssdp.async_get_ssdp", + return_value=mock_get_ssdp, + ), patch( + "homeassistant.components.ssdp.SSDPListener", + new=_generate_failing_ssdp_listener, + ), patch( + "homeassistant.components.ssdp.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ): + assert await async_setup_component(hass, ssdp.DOMAIN, {ssdp.DOMAIN: {}}) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + argset = set() + for argmap in create_args: + argset.add((argmap[1].get("source_ip"), argmap[1].get("target_ip"))) + + assert argset == { + (IPv6Address("2001:db8::"), None), + (IPv4Address("192.168.1.5"), IPv4Address("255.255.255.255")), + (IPv4Address("192.168.1.5"), None), + } + assert "Failed to setup listener for" in caplog.text + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) + await hass.async_block_till_done() + assert did_search == 2