diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 29658c19c5b..37141d6017a 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure Axis devices.""" +from ipaddress import ip_address + import voluptuous as vol from homeassistant import config_entries @@ -11,6 +13,7 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, ) +from homeassistant.util.network import is_link_local from .const import CONF_MODEL, DOMAIN from .device import get_device @@ -129,7 +132,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if serial_number[:6] not in AXIS_OUI: return self.async_abort(reason="not_axis_device") - if discovery_info[CONF_HOST].startswith("169.254"): + if is_link_local(ip_address(discovery_info[CONF_HOST])): return self.async_abort(reason="link_local_address") await self.async_set_unique_id(serial_number) diff --git a/homeassistant/components/doorbird/config_flow.py b/homeassistant/components/doorbird/config_flow.py index 410fb13a212..aa712a63ed0 100644 --- a/homeassistant/components/doorbird/config_flow.py +++ b/homeassistant/components/doorbird/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DoorBird integration.""" +from ipaddress import ip_address import logging import urllib @@ -8,6 +9,7 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback +from homeassistant.util.network import is_link_local from .const import CONF_EVENTS, DOORBIRD_OUI from .const import DOMAIN # pylint:disable=unused-import @@ -90,7 +92,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if macaddress[:6] != DOORBIRD_OUI: return self.async_abort(reason="not_doorbird_device") - if discovery_info[CONF_HOST].startswith("169.254"): + if is_link_local(ip_address(discovery_info[CONF_HOST])): return self.async_abort(reason="link_local_address") await self.async_set_unique_id(macaddress) diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index cc028478e51..e4d376dc487 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -1,18 +1,41 @@ """Network utilities.""" -from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network +from ipaddress import IPv4Address, IPv6Address, ip_network from typing import Union -# IP addresses of loopback interfaces -LOCAL_IPS = (ip_address("127.0.0.1"), ip_address("::1")) +# RFC6890 - IP addresses of loopback interfaces +LOOPBACK_NETWORKS = ( + ip_network("127.0.0.0/8"), + ip_network("::1/128"), + ip_network("::ffff:127.0.0.0/104"), +) -# RFC1918 - Address allocation for Private Internets -LOCAL_NETWORKS = ( +# RFC6890 - Address allocation for Private Internets +PRIVATE_NETWORKS = ( + ip_network("fd00::/8"), ip_network("10.0.0.0/8"), ip_network("172.16.0.0/12"), ip_network("192.168.0.0/16"), ) +# RFC6890 - Link local ranges +LINK_LOCAL_NETWORK = ip_network("169.254.0.0/16") + + +def is_loopback(address: Union[IPv4Address, IPv6Address]) -> bool: + """Check if an address is a loopback address.""" + return any(address in network for network in LOOPBACK_NETWORKS) + + +def is_private(address: Union[IPv4Address, IPv6Address]) -> bool: + """Check if an address is a private address.""" + return any(address in network for network in PRIVATE_NETWORKS) + + +def is_link_local(address: Union[IPv4Address, IPv6Address]) -> bool: + """Check if an address is link local.""" + return address in LINK_LOCAL_NETWORK + def is_local(address: Union[IPv4Address, IPv6Address]) -> bool: - """Check if an address is local.""" - return address in LOCAL_IPS or any(address in network for network in LOCAL_NETWORKS) + """Check if an address is loopback or private.""" + return is_loopback(address) or is_private(address) diff --git a/tests/util/test_network.py b/tests/util/test_network.py new file mode 100644 index 00000000000..c4c33c8d187 --- /dev/null +++ b/tests/util/test_network.py @@ -0,0 +1,40 @@ +"""Test Home Assistant volume utility functions.""" + +from ipaddress import ip_address + +import homeassistant.util.network as network_util + + +def test_is_loopback(): + """Test loopback addresses.""" + assert network_util.is_loopback(ip_address("127.0.0.2")) + assert network_util.is_loopback(ip_address("127.0.0.1")) + assert network_util.is_loopback(ip_address("::1")) + assert network_util.is_loopback(ip_address("::ffff:127.0.0.0")) + assert network_util.is_loopback(ip_address("0:0:0:0:0:0:0:1")) + assert network_util.is_loopback(ip_address("0:0:0:0:0:ffff:7f00:1")) + assert not network_util.is_loopback(ip_address("104.26.5.238")) + assert not network_util.is_loopback(ip_address("2600:1404:400:1a4::356e")) + + +def test_is_private(): + """Test private addresses.""" + assert network_util.is_private(ip_address("192.168.0.1")) + assert network_util.is_private(ip_address("172.16.12.0")) + assert network_util.is_private(ip_address("10.5.43.3")) + assert network_util.is_private(ip_address("fd12:3456:789a:1::1")) + assert not network_util.is_private(ip_address("127.0.0.1")) + assert not network_util.is_private(ip_address("::1")) + + +def test_is_link_local(): + """Test link local addresses.""" + assert network_util.is_link_local(ip_address("169.254.12.3")) + assert not network_util.is_link_local(ip_address("127.0.0.1")) + + +def test_is_local(): + """Test local addresses.""" + assert network_util.is_local(ip_address("192.168.0.1")) + assert network_util.is_local(ip_address("127.0.0.1")) + assert not network_util.is_local(ip_address("208.5.4.2"))