Skip to content

Commit 442a599

Browse files
committed
Add preliminary support for mulitple net interfaces
1 parent a0ee3d6 commit 442a599

3 files changed

Lines changed: 86 additions & 35 deletions

File tree

requirements-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
autopep8
22
coveralls
33
coverage
4+
enum34
45
flake8
56
flake8-blind-except
67
flake8-import-order>=0.4.0
78
mock
9+
netifaces
810
nose
911
pep8-naming
1012
six

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
'mDNS',
5252
],
5353
install_requires=[
54+
'enum34',
55+
'netifaces',
5456
'six',
5557
],
5658
)

zeroconf.py

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
__version__ = '0.16.0'
2828
__license__ = 'LGPL'
2929

30+
import enum
3031
import logging
3132
import select
3233
import socket
@@ -35,6 +36,7 @@
3536
import time
3637
from functools import reduce
3738

39+
import netifaces
3840
from six import indexbytes, int2byte, text_type
3941
from six.moves import xrange
4042

@@ -1190,44 +1192,85 @@ def __repr__(self):
11901192
return result
11911193

11921194

1195+
@enum.unique
1196+
class InterfaceChoice(enum.Enum):
1197+
Default = 1
1198+
All = 2
1199+
1200+
1201+
def get_all_addresses(address_family):
1202+
return [
1203+
addr['addr']
1204+
for iface in netifaces.interfaces()
1205+
for addr in netifaces.ifaddresses(iface).get(address_family, [])
1206+
]
1207+
1208+
1209+
def normalize_interface_choice(choice, address_family):
1210+
if choice is InterfaceChoice.Default:
1211+
choice = ['0.0.0.0']
1212+
elif choice is InterfaceChoice.All:
1213+
choice = get_all_addresses(address_family)
1214+
return choice
1215+
1216+
1217+
def new_socket():
1218+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1219+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1220+
1221+
# SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1222+
# multicast UDP sockets (p 731, "TCP/IP Illustrated,
1223+
# Volume 2"), but some BSD-derived systems require
1224+
# SO_REUSEPORT to be specified explicity. Also, not all
1225+
# versions of Python have SO_REUSEPORT available.
1226+
try:
1227+
reuseport = socket.SO_REUSEPORT
1228+
except AttributeError:
1229+
pass
1230+
else:
1231+
s.setsockopt(socket.SOL_SOCKET, reuseport, 1)
1232+
1233+
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
1234+
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
1235+
1236+
s.bind(('', _MDNS_PORT))
1237+
return s
1238+
1239+
11931240
class Zeroconf(object):
11941241

11951242
"""Implementation of Zeroconf Multicast DNS Service Discovery
11961243
11971244
Supports registration, unregistration, queries and browsing.
11981245
"""
11991246

1200-
def __init__(self):
1247+
def __init__(
1248+
self,
1249+
interfaces=InterfaceChoice.Default,
1250+
):
12011251
"""Creates an instance of the Zeroconf class, establishing
1202-
multicast communications, listening and reaping threads."""
1252+
multicast communications, listening and reaping threads.
1253+
1254+
:type interfaces: :class:`InterfaceChoice` or sequence of ip addresses
1255+
"""
12031256
global _GLOBAL_DONE
12041257
_GLOBAL_DONE = False
1205-
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1206-
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1207-
1208-
# SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1209-
# multicast UDP sockets (p 731, "TCP/IP Illustrated,
1210-
# Volume 2"), but some BSD-derived systems require
1211-
# SO_REUSEPORT to be specified explicity. Also, not all
1212-
# versions of Python have SO_REUSEPORT available.
1213-
try:
1214-
reuseport = socket.SO_REUSEPORT
1215-
except AttributeError:
1216-
pass
1217-
else:
1218-
self._socket.setsockopt(socket.SOL_SOCKET, reuseport, 1)
12191258

1220-
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
1221-
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
1222-
try:
1223-
self._socket.bind(('', _MDNS_PORT))
1224-
except Exception as e: # TODO stop catching all Exceptions
1225-
# Some versions of linux raise an exception even though
1226-
# the SO_REUSE* options have been set, so ignore it
1227-
#
1228-
log.exception('Unknown error, possibly benign: %r', e)
1229-
self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
1230-
socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1259+
self._listen_socket = new_socket()
1260+
interfaces = normalize_interface_choice(interfaces, socket.AF_INET)
1261+
1262+
self._respond_sockets = []
1263+
1264+
for i in interfaces:
1265+
self._listen_socket.setsockopt(
1266+
socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
1267+
socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i))
1268+
1269+
respond_socket = new_socket()
1270+
respond_socket.setsockopt(
1271+
socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(i))
1272+
1273+
self._respond_sockets.append(respond_socket)
12311274

12321275
self.listeners = []
12331276
self.browsers = []
@@ -1240,7 +1283,7 @@ def __init__(self):
12401283

12411284
self.engine = Engine(self)
12421285
self.listener = Listener(self)
1243-
self.engine.add_reader(self.listener, self._socket)
1286+
self.engine.add_reader(self.listener, self._listen_socket)
12441287
self.reaper = Reaper(self)
12451288

12461289
def wait(self, timeout):
@@ -1516,10 +1559,12 @@ def handle_query(self, msg, addr, port):
15161559
def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
15171560
"""Sends an outgoing packet."""
15181561
packet = out.packet()
1519-
bytes_sent = self._socket.sendto(packet, 0, (addr, port))
1520-
if bytes_sent != len(packet):
1521-
raise Error(
1522-
'Should not happen, sent %d out of %d bytes' % (bytes_sent, len(packet)))
1562+
for s in self._respond_sockets:
1563+
bytes_sent = s.sendto(packet, 0, (addr, port))
1564+
if bytes_sent != len(packet):
1565+
raise Error(
1566+
'Should not happen, sent %d out of %d bytes' % (
1567+
bytes_sent, len(packet)))
15231568

15241569
def close(self):
15251570
"""Ends the background threads, and prevent this instance from
@@ -1530,7 +1575,8 @@ def close(self):
15301575
self.notify_all()
15311576
self.engine.notify()
15321577
self.unregister_all_services()
1533-
self._socket.close()
1578+
for s in [self._listen_socket] + self._respond_sockets:
1579+
s.close()
15341580

15351581
# Test a few module features, including service registration, service
15361582
# query (for Zoe), and service unregistration.
@@ -1553,8 +1599,9 @@ def close(self):
15531599
r.get_service_info("_http._tcp.local.", "ZOE._http._tcp.local.")))
15541600
print(" Query done.")
15551601
print("3. Testing query of own service...")
1556-
print(" Getting self: %s" % (
1557-
r.get_service_info("_http._tcp.local.", "My Service Name._http._tcp.local.")),)
1602+
info = r.get_service_info("_http._tcp.local.", "My Service Name._http._tcp.local.")
1603+
assert info
1604+
print(" Getting self: %s" % (info,))
15581605
print(" Query done.")
15591606
print("4. Testing unregister of service information...")
15601607
r.unregister_service(info)

0 commit comments

Comments
 (0)