From c0864d7f320703e057dda10fd23144f9016676be Mon Sep 17 00:00:00 2001 From: Faidon Liambotis Date: Fri, 5 Apr 2024 16:42:29 +0300 Subject: [PATCH] util/network: cleanup, further alignment with stdlib * Remove the constants LINK_LOCAL_NETWORKS and LOOPBACK_NETWORKS, as they are currently unreferenced, due to the changes made by PR#102019. * Replace the PRIVATE_NETWORKS check with stdlib's is_private() method. Besides a reduction of code, this results in a more comprehensive check, as stdlib includes additional address spaces that were not covered before, and is ever evolving (see, for example, python/cpython#113179). * In contrast with CPython, loopbacks and link-locals are NOT included in our definition of "private", so adjust the code to keep the existing semantics * However, this *does* change the semantics for some address spaces to follow Python's interpretation of IANA, such as marking reserved documentation spaces as "local". Invert the test cases for 198.51.100.0/24 (TEST-NET-2) and 2001:db8::/32 (Documentation) accordingly. * We now need a new globally reachable IP space to use as EXTERNAL_ADDRESSES in test_auth. Use AS112's address space, as it is reserved by IANA, but marked as Globally Reachable. * Finally, inline the last remaining constant IPV6_IPV4_LOOPBACK, and leave a comment that work is underway in CPython by yours truly to reduce the need for it and have is_loopback() subsume it. Further work is required to align the semantics of e.g. what "private" means with stdlib, and replace these seldomly used util functions with stdlib properties across the tree (as is already the case in several integrations), but hopefully this provides a stepping stone towards this direction. --- homeassistant/util/network.py | 32 +++--------------------- tests/components/auth/test_indieauth.py | 2 -- tests/components/auth/test_login_flow.py | 8 +++--- tests/components/http/test_auth.py | 2 +- tests/util/test_network.py | 16 ++++++++++-- 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index 70d7dc80505ff7..bbab77a420e017 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -7,42 +7,16 @@ import yarl -# RFC6890 - IP addresses of loopback interfaces -IPV6_IPV4_LOOPBACK = ip_network("::ffff:127.0.0.0/104") - -LOOPBACK_NETWORKS = ( - ip_network("127.0.0.0/8"), - ip_network("::1/128"), - IPV6_IPV4_LOOPBACK, -) - -# RFC6890 - Address allocation for Private Internets -PRIVATE_NETWORKS = ( - ip_network("10.0.0.0/8"), - ip_network("172.16.0.0/12"), - ip_network("192.168.0.0/16"), - ip_network("fd00::/8"), - ip_network("::ffff:10.0.0.0/104"), - ip_network("::ffff:172.16.0.0/108"), - ip_network("::ffff:192.168.0.0/112"), -) - -# RFC6890 - Link local ranges -LINK_LOCAL_NETWORKS = ( - ip_network("169.254.0.0/16"), - ip_network("fe80::/10"), - ip_network("::ffff:169.254.0.0/112"), -) - def is_loopback(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a loopback address.""" - return address.is_loopback or address in IPV6_IPV4_LOOPBACK + # the ::ffff: check is a workaround for python/cpython#117566 + return address.is_loopback or address in ip_network("::ffff:127.0.0.0/104") def is_private(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a unique local non-loopback address.""" - return any(address in network for network in PRIVATE_NETWORKS) + return address.is_private and not is_loopback(address) and not address.is_link_local def is_link_local(address: IPv4Address | IPv6Address) -> bool: diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py index 2a8d6894dc63c1..e300601512e4d1 100644 --- a/tests/components/auth/test_indieauth.py +++ b/tests/components/auth/test_indieauth.py @@ -83,8 +83,6 @@ def test_client_id_hostname() -> None: assert indieauth._parse_client_id("http://192.168.0.0") assert indieauth._parse_client_id("http://192.168.255.255") - with pytest.raises(ValueError): - assert indieauth._parse_client_id("http://255.255.255.255/") with pytest.raises(ValueError): assert indieauth._parse_client_id("http://11.0.0.0/") with pytest.raises(ValueError): diff --git a/tests/components/auth/test_login_flow.py b/tests/components/auth/test_login_flow.py index af9a2cf62f1a8d..ca9a2ea9c325e8 100644 --- a/tests/components/auth/test_login_flow.py +++ b/tests/components/auth/test_login_flow.py @@ -30,8 +30,8 @@ [ ("192.168.1.10", True), ("::ffff:192.168.0.10", True), - ("1.2.3.4", False), - ("2001:db8::1", False), + ("192.175.48.1", False), + ("2620:4f:8000::1", False), ], ) @pytest.mark.parametrize( @@ -81,8 +81,8 @@ async def test_fetch_auth_providers( [{"name": "Trusted Networks", "type": "trusted_networks", "id": None}], ), ("::ffff:192.168.0.10", []), - ("1.2.3.4", []), - ("2001:db8::1", []), + ("192.175.48.1", []), + ("2620:4f:8000::1", []), ], ) async def test_fetch_auth_providers_trusted_network( diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index e31e630807e196..ad779e8d7e0844 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -54,7 +54,7 @@ ip_network("FD01:DB8::1"), ] TRUSTED_ADDRESSES = ["100.64.0.1", "192.0.2.100", "FD01:DB8::1", "2001:DB8:ABCD::1"] -EXTERNAL_ADDRESSES = ["198.51.100.1", "2001:DB8:FA1::1"] +EXTERNAL_ADDRESSES = ["192.175.48.1", "2620:4f:8000::1"] LOCALHOST_ADDRESSES = ["127.0.0.1", "::1"] UNTRUSTED_ADDRESSES = [*EXTERNAL_ADDRESSES, *LOCALHOST_ADDRESSES] PRIVATE_ADDRESSES = [ diff --git a/tests/util/test_network.py b/tests/util/test_network.py index c234a51764031f..6b87764723f117 100644 --- a/tests/util/test_network.py +++ b/tests/util/test_network.py @@ -43,14 +43,26 @@ def test_is_invalid() -> None: def test_is_local() -> None: """Test local addresses.""" + # RFC 1918 space assert network_util.is_local(ip_address("192.168.0.1")) + # loopback assert network_util.is_local(ip_address("127.0.0.1")) + assert network_util.is_local(ip_address("::ffff:127.0.0.1")) + assert network_util.is_local(ip_address("::1")) + # IPv6 ULA assert network_util.is_local(ip_address("fd12:3456:789a:1::1")) + # IPv6 link-local assert network_util.is_local(ip_address("fe80::1234:5678:abcd")) + # mapped ipv4-to-ipv6 assert network_util.is_local(ip_address("::ffff:192.168.0.1")) + # Documentation/TEST-NET2 IP space, marked as Globally Reachable: False by IANA + assert network_util.is_local(ip_address("198.51.100.1")) + assert network_util.is_local(ip_address("2001:DB8:FA1::1")) + # AS112 space, marked as Globally Reachable: True by IANA + assert not network_util.is_local(ip_address("192.175.48.1")) + assert not network_util.is_local(ip_address("2620:4f:8000::1")) + # random globally routable IP space assert not network_util.is_local(ip_address("208.5.4.2")) - assert not network_util.is_local(ip_address("198.51.100.1")) - assert not network_util.is_local(ip_address("2001:DB8:FA1::1")) assert not network_util.is_local(ip_address("::ffff:208.5.4.2"))