2727__version__ = '0.16.0'
2828__license__ = 'LGPL'
2929
30+ import enum
3031import logging
3132import select
3233import socket
3536import time
3637from functools import reduce
3738
39+ import netifaces
3840from six import indexbytes , int2byte , text_type
3941from 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+
11931240class 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