Skip to content

Commit 91a5489

Browse files
committed
fix(net): fallback multicast join without adapters
ifaddr may return no adapters in sandboxed environments, which prevents mDNS multicast membership and leads to empty scan results. Detect a default IPv4 via a dummy UDP connect and use it for IP_MULTICAST_IF/ADD_MEMBERSHIP while keeping the socket bind on an empty string to avoid conflicts. Reuse the same probe fallback in get_private_addresses so callers still obtain a usable IPv4 when adapter enumeration is empty.
1 parent 1aec1c0 commit 91a5489

File tree

1 file changed

+31
-0
lines changed

1 file changed

+31
-0
lines changed

pyatv/support/net.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ def mcast_socket(address: Optional[str], port: int = 0) -> socket.socket:
4747
with suppress(OSError):
4848
membership = socket.inet_aton("224.0.0.251") + socket.inet_aton(address)
4949
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, membership)
50+
else:
51+
# Fallback for sandboxed platforms where ifaddr finds no adapters:
52+
# detect default IPv4 via a dummy UDP connect and use it for multicast join.
53+
try:
54+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
55+
probe.connect(("8.8.8.8", 80))
56+
detected = probe.getsockname()[0]
57+
sock.setsockopt(
58+
socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(detected)
59+
)
60+
membership = socket.inet_aton("224.0.0.251") + socket.inet_aton(
61+
detected
62+
)
63+
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, membership)
64+
except OSError:
65+
with suppress(OSError):
66+
membership = socket.inet_aton("224.0.0.251") + socket.inet_aton(
67+
"0.0.0.0"
68+
)
69+
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, membership)
5070

5171
_LOGGER.debug("Binding on %s:%d", address or "*", port)
5272
sock.bind((address or "", port))
@@ -74,6 +94,17 @@ def get_private_addresses(include_loopback=True) -> List[IPv4Address]:
7494
if ipaddr.is_private:
7595
addresses.append(ipaddr)
7696

97+
# Fallback when no adapters are reported (e.g. sandboxed platforms).
98+
if not addresses:
99+
try:
100+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
101+
probe.connect(("8.8.8.8", 80))
102+
detected = IPv4Address(probe.getsockname()[0])
103+
if include_loopback or not detected.is_loopback:
104+
addresses.append(detected)
105+
except OSError:
106+
pass
107+
77108
return addresses
78109

79110

0 commit comments

Comments
 (0)