@@ -217,10 +217,12 @@ def service_type_name(type_):
217217 """
218218 if not (type_ .endswith ('._tcp.local.' ) or type_ .endswith ('._udp.local.' )):
219219 raise BadTypeInNameException (
220- "Type must end with '._tcp.local.' or '._udp.local.'" )
220+ "Type '%s' must end with '._tcp.local.' or '._udp.local.'" %
221+ type_ )
221222
222223 if type_ .startswith ('.' ):
223- raise BadTypeInNameException ("Type must not start with '.'" )
224+ raise BadTypeInNameException (
225+ "Type '%s' must not start with '.'" % type_ )
224226
225227 remaining = type_ [:- len ('._tcp.local.' )].split ('.' )
226228 name = remaining .pop ()
@@ -264,7 +266,7 @@ def service_type_name(type_):
264266
265267 if len (remaining ) > 1 :
266268 raise BadTypeInNameException (
267- "Unexpected characters '%s.' in %s " % (
269+ "Unexpected characters '%s.' in '%s' " % (
268270 '.' .join (remaining [1 :]), type_ ))
269271
270272 if remaining :
@@ -765,6 +767,16 @@ def __init__(self, flags, multicast=True):
765767 self .authorities = []
766768 self .additionals = []
767769
770+ def __repr__ (self ):
771+ return '<DNSOutgoing:{%s}>' % ', ' .join ([
772+ 'multicast=%s' % self .multicast ,
773+ 'flags=%s' % self .flags ,
774+ 'questions=%s' % self .questions ,
775+ 'answers=%s' % self .answers ,
776+ 'authorities=%s' % self .authorities ,
777+ 'additionals=%s' % self .additionals ,
778+ ])
779+
768780 class State (enum .Enum ):
769781 init = 0
770782 finished = 1
@@ -789,7 +801,41 @@ def add_authorative_answer(self, record):
789801 self .authorities .append (record )
790802
791803 def add_additional_answer (self , record ):
792- """Adds an additional answer"""
804+ """ Adds an additional answer
805+
806+ From: RFC 6763, DNS-Based Service Discovery, February 2013
807+
808+ 12. DNS Additional Record Generation
809+
810+ DNS has an efficiency feature whereby a DNS server may place
811+ additional records in the additional section of the DNS message.
812+ These additional records are records that the client did not
813+ explicitly request, but the server has reasonable grounds to expect
814+ that the client might request them shortly, so including them can
815+ save the client from having to issue additional queries.
816+
817+ This section recommends which additional records SHOULD be generated
818+ to improve network efficiency, for both Unicast and Multicast DNS-SD
819+ responses.
820+
821+ 12.1. PTR Records
822+
823+ When including a DNS-SD Service Instance Enumeration or Selective
824+ Instance Enumeration (subtype) PTR record in a response packet, the
825+ server/responder SHOULD include the following additional records:
826+
827+ o The SRV record(s) named in the PTR rdata.
828+ o The TXT record(s) named in the PTR rdata.
829+ o All address records (type "A" and "AAAA") named in the SRV rdata.
830+
831+ 12.2. SRV Records
832+
833+ When including an SRV record in a response packet, the
834+ server/responder SHOULD include the following additional records:
835+
836+ o All address records (type "A" and "AAAA") named in the SRV rdata.
837+
838+ """
793839 self .additionals .append (record )
794840
795841 def pack (self , format_ , value ):
@@ -995,10 +1041,18 @@ def get_by_details(self, name, type_, class_):
9951041 def entries_with_name (self , name ):
9961042 """Returns a list of entries whose key matches the name."""
9971043 try :
998- return self .cache [name ]
1044+ return self .cache [name . lower () ]
9991045 except KeyError :
10001046 return []
10011047
1048+ def current_entry_with_name_and_alias (self , name , alias ):
1049+ now = current_time_millis ()
1050+ for record in self .entries_with_name (name ):
1051+ if (record .type == _TYPE_PTR and
1052+ not record .is_expired (now ) and
1053+ record .alias == alias ):
1054+ return record
1055+
10021056 def entries (self ):
10031057 """Returns a list of all entries"""
10041058 if not self .cache :
@@ -1689,12 +1743,12 @@ def remove_all_service_listeners(self):
16891743 for listener in [k for k in self .browsers ]:
16901744 self .remove_service_listener (listener )
16911745
1692- def register_service (self , info , ttl = _DNS_TTL ):
1746+ def register_service (self , info , ttl = _DNS_TTL , allow_name_change = False ):
16931747 """Registers service information to the network with a default TTL
16941748 of 60 seconds. Zeroconf will then respond to requests for
16951749 information for that service. The name of the service may be
16961750 changed if needed to make it unique on the network."""
1697- self .check_service (info )
1751+ self .check_service (info , allow_name_change )
16981752 self .services [info .name .lower ()] = info
16991753 if info .type in self .servicetypes :
17001754 self .servicetypes [info .type ] += 1
@@ -1789,28 +1843,42 @@ def unregister_all_services(self):
17891843 i += 1
17901844 next_time += _UNREGISTER_TIME
17911845
1792- def check_service (self , info ):
1846+ def check_service (self , info , allow_name_change ):
17931847 """Checks the network for a unique service name, modifying the
17941848 ServiceInfo passed in if it is not unique."""
1849+ service_name = service_type_name (info .name )
1850+
1851+ # This asserts breaks on the current subtype based tests
1852+ # need to make subtypes a first class citizen
1853+ # assert service_name == info.type
1854+ # instead try:
1855+ assert service_name == '.' .join (info .type .split ('.' )[- 4 :])
1856+ instance_name = info .name [:- len (service_name ) - 1 ]
1857+ next_instance_number = 2
1858+
17951859 now = current_time_millis ()
17961860 next_time = now
17971861 i = 0
17981862 while i < 3 :
1799- for record in self .cache .entries_with_name (info .type ):
1800- if (record .type == _TYPE_PTR and
1801- not record .is_expired (now ) and
1802- record .alias == info .name ):
1803- if info .name .find ('.' ) < 0 :
1804- info .name = '%s.[%s:%s].%s' % (
1805- info .name , info .address , info .port , info .type )
1806-
1807- self .check_service (info )
1808- return
1863+ # check for a name conflict
1864+ while self .cache .current_entry_with_name_and_alias (
1865+ info .type , info .name ):
1866+ if not allow_name_change :
18091867 raise NonUniqueNameException
1868+
1869+ # change the name and look for a conflict
1870+ info .name = '%s-%s.%s' % (
1871+ instance_name , next_instance_number , info .type )
1872+ next_instance_number += 1
1873+ service_type_name (info .name )
1874+ next_time = now
1875+ i = 0
1876+
18101877 if now < next_time :
18111878 self .wait (next_time - now )
18121879 now = current_time_millis ()
18131880 continue
1881+
18141882 out = DNSOutgoing (_FLAGS_QR_QUERY | _FLAGS_AA )
18151883 self .debug = out
18161884 out .add_question (DNSQuestion (info .type , _TYPE_PTR , _CLASS_IN ))
@@ -1874,7 +1942,7 @@ def handle_query(self, msg, addr, port):
18741942 # Support unicast client responses
18751943 #
18761944 if port != _MDNS_PORT :
1877- out = DNSOutgoing (_FLAGS_QR_RESPONSE | _FLAGS_AA , False )
1945+ out = DNSOutgoing (_FLAGS_QR_RESPONSE | _FLAGS_AA , multicast = False )
18781946 for question in msg .questions :
18791947 out .add_question (question )
18801948
0 commit comments