From 8145b103fbf125d631ad513913c9fc6a3c4a5f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Tue, 5 Apr 2022 08:53:50 +0200 Subject: [PATCH] Fix network starting with no configured IPv4 addresses (#69030) Co-authored-by: J. Nick Koston --- homeassistant/components/network/__init__.py | 21 +++++++++--- homeassistant/components/network/const.py | 1 + tests/components/network/test_init.py | 36 ++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index 8c1a0020cfb..1ad975e1d85 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -6,11 +6,16 @@ import logging from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType from homeassistant.loader import bind_hass from . import util -from .const import IPV4_BROADCAST_ADDR, PUBLIC_TARGET_IP +from .const import ( + IPV4_BROADCAST_ADDR, + LOOPBACK_TARGET_IP, + MDNS_TARGET_IP, + PUBLIC_TARGET_IP, +) from .models import Adapter from .network import Network, async_get_network @@ -26,7 +31,7 @@ async def async_get_adapters(hass: HomeAssistant) -> list[Adapter]: @bind_hass async def async_get_source_ip( - hass: HomeAssistant, target_ip: str = PUBLIC_TARGET_IP + hass: HomeAssistant, target_ip: str | UndefinedType = UNDEFINED ) -> str: """Get the source ip for a target ip.""" adapters = await async_get_adapters(hass) @@ -35,7 +40,15 @@ async def async_get_source_ip( if adapter["enabled"] and (ipv4s := adapter["ipv4"]): all_ipv4s.extend([ipv4["address"] for ipv4 in ipv4s]) - source_ip = util.async_get_source_ip(target_ip) + if target_ip is UNDEFINED: + source_ip = ( + util.async_get_source_ip(PUBLIC_TARGET_IP) + or util.async_get_source_ip(MDNS_TARGET_IP) + or util.async_get_source_ip(LOOPBACK_TARGET_IP) + ) + else: + source_ip = util.async_get_source_ip(target_ip) + if not all_ipv4s: _LOGGER.warning( "Because the system does not have any enabled IPv4 addresses, source address detection may be inaccurate" diff --git a/homeassistant/components/network/const.py b/homeassistant/components/network/const.py index 07c12e63a10..3a166189b85 100644 --- a/homeassistant/components/network/const.py +++ b/homeassistant/components/network/const.py @@ -17,6 +17,7 @@ ATTR_ADAPTERS: Final = "adapters" ATTR_CONFIGURED_ADAPTERS: Final = "configured_adapters" DEFAULT_CONFIGURED_ADAPTERS: list[str] = [] +LOOPBACK_TARGET_IP: Final = "127.0.0.1" MDNS_TARGET_IP: Final = "224.0.0.251" PUBLIC_TARGET_IP: Final = "8.8.8.8" IPV4_BROADCAST_ADDR: Final = "255.255.255.255" diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index 1103c6fa850..dc1ed064dc9 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -27,6 +27,21 @@ def _mock_socket(sockname): return mock_socket +def _mock_cond_socket(sockname): + class CondMockSock(MagicMock): + def connect(self, addr): + """Mock connect that stores addr.""" + self._addr = addr[0] + + def getsockname(self): + """Return addr if it matches the mock sockname.""" + if self._addr == sockname: + return [sockname] + raise AttributeError() + + return CondMockSock() + + def _mock_socket_exception(exc): mock_socket = MagicMock() mock_socket.getsockname = Mock(side_effect=exc) @@ -650,3 +665,24 @@ async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses await hass.async_block_till_done() with pytest.raises(HomeAssistantError): await network.async_get_source_ip(hass, MDNS_TARGET_IP) + + +async def test_async_get_source_ip_no_ip_loopback(hass, hass_storage, caplog): + """Test getting the source ip address when all adapters are disabled no target is specified.""" + hass_storage[STORAGE_KEY] = { + "version": STORAGE_VERSION, + "key": STORAGE_KEY, + "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, + } + + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_cond_socket(_LOOPBACK_IPADDR), + ): + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + + assert await network.async_get_source_ip(hass) == "127.0.0.1"