Skip to content

Rebase PR #188 (Fixes for OS X) with resolved conflicts #270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 7, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 61 additions & 27 deletions zeroconf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class InterfaceChoice(enum.Enum):
All = 2


InterfacesType = Union[List[Union[str, int]], InterfaceChoice]
InterfacesType = Union[List[Union[str, int, Tuple[Tuple[str, int, int], int]]], InterfaceChoice]


@enum.unique
Expand Down Expand Up @@ -2016,23 +2016,39 @@ def get_all_addresses() -> List[str]:
return list(set(addr.ip for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv4))


def get_all_addresses_v6() -> List[int]:
def get_all_addresses_v6() -> List[Tuple[Tuple[str, int, int], int]]:
# IPv6 multicast uses positive indexes for interfaces
return [adapter.index for adapter in ifaddr.get_adapters()]
# TODO: What about multi-address interfaces?
return list(
set((addr.ip, iface.index) for iface in ifaddr.get_adapters() for addr in iface.ips if addr.is_IPv6)
)


def ip_to_index(adapters: List[Any], ip: str) -> int:
def ip6_to_address_and_index(adapters: List[Any], ip: str) -> Tuple[Tuple[str, int, int], int]:
ipaddr = ipaddress.ip_address(ip)
for adapter in adapters:
for adapter_ip in adapter.ips:
# IPv6 addresses are represented as tuples
if isinstance(adapter_ip.ip, tuple) and ipaddress.ip_address(adapter_ip.ip[0]) == ipaddr:
return cast(int, adapter.index)
return (cast(Tuple[str, int, int], adapter_ip.ip), cast(int, adapter.index))

raise RuntimeError('No adapter found for IP address %s' % ip)


def ip6_addresses_to_indexes(interfaces: List[Union[str, int]]) -> List[int]:
def interface_index_to_ip6_address(adapters: List[Any], index: int) -> Tuple[str, int, int]:
for adapter in adapters:
if adapter.index == index:
for adapter_ip in adapter.ips:
# IPv6 addresses are represented as tuples
if isinstance(adapter_ip.ip, tuple):
return cast(Tuple[str, int, int], adapter_ip.ip)

raise RuntimeError('No adapter found for index %s' % index)


def ip6_addresses_to_indexes(
interfaces: List[Union[str, int, Tuple[Tuple[str, int, int], int]]]
) -> List[Tuple[Tuple[str, int, int], int]]:
"""Convert IPv6 interface addresses to interface indexes.

IPv4 addresses are ignored.
Expand All @@ -2045,27 +2061,27 @@ def ip6_addresses_to_indexes(interfaces: List[Union[str, int]]) -> List[int]:

for iface in interfaces:
if isinstance(iface, int):
result.append(iface)
result.append((interface_index_to_ip6_address(adapters, iface), iface))
elif isinstance(iface, str) and ipaddress.ip_address(iface).version == 6:
result.append(ip_to_index(adapters, iface))
result.append(ip6_to_address_and_index(adapters, iface))

return result


def normalize_interface_choice(
choice: InterfacesType, ip_version: IPVersion = IPVersion.V4Only
) -> List[Union[str, int]]:
) -> List[Union[str, Tuple[Tuple[str, int, int], int]]]:
"""Convert the interfaces choice into internal representation.

:param choice: `InterfaceChoice` or list of interface addresses or indexes (IPv6 only).
:param ip_address: IP version to use (ignored if `choice` is a list).
:returns: List of IP addresses (for IPv4) and indexes (for IPv6).
"""
result = [] # type: List[Union[str, int]]
result = [] # type: List[Union[str, Tuple[Tuple[str, int, int], int]]]
if choice is InterfaceChoice.Default:
if ip_version != IPVersion.V4Only:
# IPv6 multicast uses interface 0 to mean the default
result.append(0)
result.append((('', 0, 0), 0))
if ip_version != IPVersion.V6Only:
result.append('0.0.0.0')
elif choice is InterfaceChoice.All:
Expand All @@ -2088,8 +2104,18 @@ def normalize_interface_choice(


def new_socket(
port: int = _MDNS_PORT, ip_version: IPVersion = IPVersion.V4Only, apple_p2p: bool = False
bind_addr: Union[Tuple[str], Tuple[str, int, int]],
port: int = _MDNS_PORT,
ip_version: IPVersion = IPVersion.V4Only,
apple_p2p: bool = False,
) -> socket.socket:
log.debug(
'Creating new socket with port %s, ip_version %s, apple_p2p %s and bind_addr %r',
port,
ip_version,
apple_p2p,
bind_addr,
)
if ip_version == IPVersion.V4Only:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
else:
Expand Down Expand Up @@ -2141,22 +2167,25 @@ def new_socket(
# https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/socket.h
s.setsockopt(socket.SOL_SOCKET, 0x1104, 1)

s.bind(('', port))
s.bind((bind_addr[0], port, *bind_addr[1:]))
log.debug('Created socket %s', s)
return s


def add_multicast_member(
listen_socket: socket.socket, interface: Union[str, int], apple_p2p: bool = False
listen_socket: socket.socket,
interface: Union[str, Tuple[Tuple[str, int, int], int]],
apple_p2p: bool = False,
) -> Optional[socket.socket]:
# This is based on assumptions in normalize_interface_choice
is_v6 = isinstance(interface, int)
is_v6 = isinstance(interface, tuple)
err_einval = {errno.EINVAL}
if sys.platform == 'win32':
err_einval |= {errno.WSAEINVAL}
log.debug('Adding %r (socket %d) to multicast group', interface, listen_socket.fileno())
try:
if is_v6:
iface_bin = struct.pack('@I', cast(int, interface))
iface_bin = struct.pack('@I', cast(int, interface[1]))
_value = _MDNS_ADDR6_BYTES + iface_bin
listen_socket.setsockopt(_IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, _value)
else:
Expand Down Expand Up @@ -2185,7 +2214,9 @@ def add_multicast_member(
raise

respond_socket = new_socket(
ip_version=(IPVersion.V6Only if is_v6 else IPVersion.V4Only), apple_p2p=apple_p2p
ip_version=(IPVersion.V6Only if is_v6 else IPVersion.V4Only),
apple_p2p=apple_p2p,
bind_addr=cast(Tuple[Tuple[str, int, int], int], interface)[0] if is_v6 else (cast(str, interface),),
)
log.debug('Configuring socket %s with multicast interface %s', respond_socket, interface)
if is_v6:
Expand All @@ -2206,17 +2237,22 @@ def create_sockets(
if unicast:
listen_socket = None
else:
listen_socket = new_socket(ip_version=ip_version, apple_p2p=apple_p2p)
listen_socket = new_socket(ip_version=ip_version, apple_p2p=apple_p2p, bind_addr=('',))

interfaces = normalize_interface_choice(interfaces, ip_version)
normalized_interfaces = normalize_interface_choice(interfaces, ip_version)

respond_sockets = []

for i in interfaces:
for i in normalized_interfaces:
if not unicast:
respond_socket = add_multicast_member(cast(socket.socket, listen_socket), i, apple_p2p=apple_p2p)
else:
respond_socket = new_socket(port=0, ip_version=ip_version, apple_p2p=apple_p2p)
respond_socket = new_socket(
port=0,
ip_version=ip_version,
apple_p2p=apple_p2p,
bind_addr=i[0] if isinstance(i, tuple) else (i,),
)

if respond_socket is not None:
respond_sockets.append(respond_socket)
Expand Down Expand Up @@ -2307,9 +2343,8 @@ def __init__(
self.listener = Listener(self)
if not unicast:
self.engine.add_reader(self.listener, cast(socket.socket, self._listen_socket))
else:
for s in self._respond_sockets:
self.engine.add_reader(self.listener, s)
for s in self._respond_sockets:
self.engine.add_reader(self.listener, s)
self.reaper = Reaper(self)

self.debug = None # type: Optional[DNSOutgoing]
Expand Down Expand Up @@ -2817,9 +2852,8 @@ def close(self) -> None:
if not self.unicast:
self.engine.del_reader(cast(socket.socket, self._listen_socket))
cast(socket.socket, self._listen_socket).close()
else:
for s in self._respond_sockets:
self.engine.del_reader(s)
for s in self._respond_sockets:
self.engine.del_reader(s)
self.engine.join()

# shutdown the rest
Expand Down