Skip to content

Commit 8359488

Browse files
authored
fix: avoid loading adapter list twice (python-zeroconf#1564)
1 parent 389a8a2 commit 8359488

2 files changed

Lines changed: 69 additions & 11 deletions

File tree

src/zeroconf/_utils/net.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
import socket
2929
import struct
3030
import sys
31-
from collections.abc import Sequence
31+
import warnings
32+
from collections.abc import Iterable, Sequence
3233
from typing import Any, Union, cast
3334

3435
import ifaddr
@@ -73,19 +74,39 @@ def _encode_address(address: str) -> bytes:
7374
return socket.inet_pton(address_family, address)
7475

7576

76-
def get_all_addresses() -> list[str]:
77-
return list({addr.ip for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv4}) # type: ignore[misc]
77+
def get_all_addresses_ipv4(adapters: Iterable[ifaddr.Adapter]) -> list[str]:
78+
return list({addr.ip for iface in adapters for addr in iface.ips if addr.is_IPv4}) # type: ignore[misc]
7879

7980

80-
def get_all_addresses_v6() -> list[tuple[tuple[str, int, int], int]]:
81+
def get_all_addresses_ipv6(adapters: Iterable[ifaddr.Adapter]) -> list[tuple[tuple[str, int, int], int]]:
8182
# IPv6 multicast uses positive indexes for interfaces
8283
# TODO: What about multi-address interfaces?
8384
return list(
84-
{(addr.ip, iface.index) for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv6} # type: ignore[misc]
85+
{(addr.ip, iface.index) for iface in adapters for addr in iface.ips if addr.is_IPv6} # type: ignore[misc]
86+
)
87+
88+
89+
def get_all_addresses() -> list[str]:
90+
warnings.warn(
91+
"get_all_addresses is deprecated, and will be removed in a future version. Use ifaddr"
92+
"directly instead to get a list of adapters.",
93+
DeprecationWarning,
94+
stacklevel=2,
95+
)
96+
return get_all_addresses_ipv4(ifaddr.get_adapters())
97+
98+
99+
def get_all_addresses_v6() -> list[tuple[tuple[str, int, int], int]]:
100+
warnings.warn(
101+
"get_all_addresses_v6 is deprecated, and will be removed in a future version. Use ifaddr"
102+
"directly instead to get a list of adapters.",
103+
DeprecationWarning,
104+
stacklevel=2,
85105
)
106+
return get_all_addresses_ipv6(ifaddr.get_adapters())
86107

87108

88-
def ip6_to_address_and_index(adapters: list[ifaddr.Adapter], ip: str) -> tuple[tuple[str, int, int], int]:
109+
def ip6_to_address_and_index(adapters: Iterable[ifaddr.Adapter], ip: str) -> tuple[tuple[str, int, int], int]:
89110
if "%" in ip:
90111
ip = ip[: ip.index("%")] # Strip scope_id.
91112
ipaddr = ipaddress.ip_address(ip)
@@ -102,7 +123,7 @@ def ip6_to_address_and_index(adapters: list[ifaddr.Adapter], ip: str) -> tuple[t
102123
raise RuntimeError(f"No adapter found for IP address {ip}")
103124

104125

105-
def interface_index_to_ip6_address(adapters: list[ifaddr.Adapter], index: int) -> tuple[str, int, int]:
126+
def interface_index_to_ip6_address(adapters: Iterable[ifaddr.Adapter], index: int) -> tuple[str, int, int]:
106127
for adapter in adapters:
107128
if adapter.index == index:
108129
for adapter_ip in adapter.ips:
@@ -152,10 +173,11 @@ def normalize_interface_choice(
152173
if ip_version != IPVersion.V6Only:
153174
result.append("0.0.0.0")
154175
elif choice is InterfaceChoice.All:
176+
adapters = ifaddr.get_adapters()
155177
if ip_version != IPVersion.V4Only:
156-
result.extend(get_all_addresses_v6())
178+
result.extend(get_all_addresses_ipv6(adapters))
157179
if ip_version != IPVersion.V6Only:
158-
result.extend(get_all_addresses())
180+
result.extend(get_all_addresses_ipv4(adapters))
159181
if not result:
160182
raise RuntimeError(
161183
f"No interfaces to listen on, check that any interfaces have IP version {ip_version}"

tests/utils/test_net.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import socket
77
import sys
88
import unittest
9+
import warnings
910
from unittest.mock import MagicMock, Mock, patch
1011

1112
import ifaddr
1213
import pytest
1314

1415
import zeroconf as r
16+
from zeroconf import get_all_addresses, get_all_addresses_v6
1517
from zeroconf._utils import net as netutils
1618

1719

@@ -35,6 +37,40 @@ def _generate_mock_adapters():
3537
return [mock_eth0, mock_lo0, mock_eth1, mock_vtun0]
3638

3739

40+
def test_get_all_addresses() -> None:
41+
"""Test public get_all_addresses API."""
42+
with (
43+
patch(
44+
"zeroconf._utils.net.ifaddr.get_adapters",
45+
return_value=_generate_mock_adapters(),
46+
),
47+
warnings.catch_warnings(record=True) as warned,
48+
):
49+
addresses = get_all_addresses()
50+
assert isinstance(addresses, list)
51+
assert len(addresses) == 3
52+
assert len(warned) == 1
53+
first_warning = warned[0]
54+
assert "get_all_addresses is deprecated" in str(first_warning.message)
55+
56+
57+
def test_get_all_addresses_v6() -> None:
58+
"""Test public get_all_addresses_v6 API."""
59+
with (
60+
patch(
61+
"zeroconf._utils.net.ifaddr.get_adapters",
62+
return_value=_generate_mock_adapters(),
63+
),
64+
warnings.catch_warnings(record=True) as warned,
65+
):
66+
addresses = get_all_addresses_v6()
67+
assert isinstance(addresses, list)
68+
assert len(addresses) == 1
69+
assert len(warned) == 1
70+
first_warning = warned[0]
71+
assert "get_all_addresses_v6 is deprecated" in str(first_warning.message)
72+
73+
3874
def test_ip6_to_address_and_index():
3975
"""Test we can extract from mocked adapters."""
4076
adapters = _generate_mock_adapters()
@@ -84,8 +120,8 @@ def test_ip6_addresses_to_indexes():
84120
def test_normalize_interface_choice_errors():
85121
"""Test we generate exception on invalid input."""
86122
with (
87-
patch("zeroconf._utils.net.get_all_addresses", return_value=[]),
88-
patch("zeroconf._utils.net.get_all_addresses_v6", return_value=[]),
123+
patch("zeroconf._utils.net.get_all_addresses_ipv4", return_value=[]),
124+
patch("zeroconf._utils.net.get_all_addresses_ipv6", return_value=[]),
89125
pytest.raises(RuntimeError),
90126
):
91127
netutils.normalize_interface_choice(r.InterfaceChoice.All)

0 commit comments

Comments
 (0)