Fix network starting with no configured IPv4 addresses (#69030)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Toke Høiland-Jørgensen 2022-04-05 08:53:50 +02:00 committed by GitHub
parent 6965a6d13b
commit 8145b103fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 4 deletions

View File

@ -6,11 +6,16 @@ import logging
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError 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 homeassistant.loader import bind_hass
from . import util 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 .models import Adapter
from .network import Network, async_get_network from .network import Network, async_get_network
@ -26,7 +31,7 @@ async def async_get_adapters(hass: HomeAssistant) -> list[Adapter]:
@bind_hass @bind_hass
async def async_get_source_ip( async def async_get_source_ip(
hass: HomeAssistant, target_ip: str = PUBLIC_TARGET_IP hass: HomeAssistant, target_ip: str | UndefinedType = UNDEFINED
) -> str: ) -> str:
"""Get the source ip for a target ip.""" """Get the source ip for a target ip."""
adapters = await async_get_adapters(hass) adapters = await async_get_adapters(hass)
@ -35,7 +40,15 @@ async def async_get_source_ip(
if adapter["enabled"] and (ipv4s := adapter["ipv4"]): if adapter["enabled"] and (ipv4s := adapter["ipv4"]):
all_ipv4s.extend([ipv4["address"] for ipv4 in ipv4s]) all_ipv4s.extend([ipv4["address"] for ipv4 in ipv4s])
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) source_ip = util.async_get_source_ip(target_ip)
if not all_ipv4s: if not all_ipv4s:
_LOGGER.warning( _LOGGER.warning(
"Because the system does not have any enabled IPv4 addresses, source address detection may be inaccurate" "Because the system does not have any enabled IPv4 addresses, source address detection may be inaccurate"

View File

@ -17,6 +17,7 @@ ATTR_ADAPTERS: Final = "adapters"
ATTR_CONFIGURED_ADAPTERS: Final = "configured_adapters" ATTR_CONFIGURED_ADAPTERS: Final = "configured_adapters"
DEFAULT_CONFIGURED_ADAPTERS: list[str] = [] DEFAULT_CONFIGURED_ADAPTERS: list[str] = []
LOOPBACK_TARGET_IP: Final = "127.0.0.1"
MDNS_TARGET_IP: Final = "224.0.0.251" MDNS_TARGET_IP: Final = "224.0.0.251"
PUBLIC_TARGET_IP: Final = "8.8.8.8" PUBLIC_TARGET_IP: Final = "8.8.8.8"
IPV4_BROADCAST_ADDR: Final = "255.255.255.255" IPV4_BROADCAST_ADDR: Final = "255.255.255.255"

View File

@ -27,6 +27,21 @@ def _mock_socket(sockname):
return mock_socket 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): def _mock_socket_exception(exc):
mock_socket = MagicMock() mock_socket = MagicMock()
mock_socket.getsockname = Mock(side_effect=exc) 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() await hass.async_block_till_done()
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError):
await network.async_get_source_ip(hass, MDNS_TARGET_IP) 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"