util/Zeroconf.py
changeset 1784 64beb9e9c749
parent 1783 3311eea28d56
child 1826 91796f408540
equal deleted inserted replaced
1729:31e63e25b4cc 1784:64beb9e9c749
     1 """ Multicast DNS Service Discovery for Python, v0.12
     1 #  Multicast DNS Service Discovery for Python, v0.12
     2     Copyright (C) 2003, Paul Scott-Murphy
     2 #     Copyright (C) 2003, Paul Scott-Murphy
     3 
     3 #
     4     This module provides a framework for the use of DNS Service Discovery
     4 #     This module provides a framework for the use of DNS Service Discovery
     5     using IP multicast.  It has been tested against the JRendezvous
     5 #     using IP multicast.  It has been tested against the JRendezvous
     6     implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
     6 #     implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
     7     and against the mDNSResponder from Mac OS X 10.3.8.
     7 #     and against the mDNSResponder from Mac OS X 10.3.8.
     8 
     8 
     9     This library is free software; you can redistribute it and/or
     9 #     This library is free software; you can redistribute it and/or
    10     modify it under the terms of the GNU Lesser General Public
    10 #     modify it under the terms of the GNU Lesser General Public
    11     License as published by the Free Software Foundation; either
    11 #     License as published by the Free Software Foundation; either
    12     version 2.1 of the License, or (at your option) any later version.
    12 #     version 2.1 of the License, or (at your option) any later version.
    13 
    13 
    14     This library is distributed in the hope that it will be useful,
    14 #     This library is distributed in the hope that it will be useful,
    15     but WITHOUT ANY WARRANTY; without even the implied warranty of
    15 #     but WITHOUT ANY WARRANTY; without even the implied warranty of
    16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    16 #     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    17     Lesser General Public License for more details.
    17 #     Lesser General Public License for more details.
    18 
    18 
    19     You should have received a copy of the GNU Lesser General Public
    19 #     You should have received a copy of the GNU Lesser General Public
    20     License along with this library; if not, write to the Free Software
    20 #     License along with this library; if not, write to the Free Software
    21     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    21 #     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    22     
    22 #
    23 """
    23 #
    24 
    24 #
    25 """0.12 update - allow selection of binding interface
    25 # 0.12 update - allow selection of binding interface
    26          typo fix - Thanks A. M. Kuchlingi
    26 #          typo fix - Thanks A. M. Kuchlingi
    27          removed all use of word 'Rendezvous' - this is an API change"""
    27 #          removed all use of word 'Rendezvous' - this is an API change
    28 
    28 #
    29 """0.11 update - correction to comments for addListener method
    29 # 0.11 update - correction to comments for addListener method
    30                  support for new record types seen from OS X
    30 #                  support for new record types seen from OS X
    31                   - IPv6 address
    31 #                   - IPv6 address
    32                   - hostinfo
    32 #                   - hostinfo
    33                  ignore unknown DNS record types
    33 #                  ignore unknown DNS record types
    34                  fixes to name decoding
    34 #                  fixes to name decoding
    35                  works alongside other processes using port 5353 (e.g. on Mac OS X)
    35 #                  works alongside other processes using port 5353 (e.g. on Mac OS X)
    36                  tested against Mac OS X 10.3.2's mDNSResponder
    36 #                  tested against Mac OS X 10.3.2's mDNSResponder
    37                  corrections to removal of list entries for service browser"""
    37 #                  corrections to removal of list entries for service browser
    38 
    38 #
    39 """0.10 update - Jonathon Paisley contributed these corrections:
    39 # 0.10 update - Jonathon Paisley contributed these corrections:
    40                  always multicast replies, even when query is unicast
    40 #                  always multicast replies, even when query is unicast
    41                  correct a pointer encoding problem
    41 #                  correct a pointer encoding problem
    42                  can now write records in any order
    42 #                  can now write records in any order
    43                  traceback shown on failure
    43 #                  traceback shown on failure
    44                  better TXT record parsing
    44 #                  better TXT record parsing
    45                  server is now separate from name
    45 #                  server is now separate from name
    46                  can cancel a service browser
    46 #                  can cancel a service browser
    47 
    47 #
    48                  modified some unit tests to accommodate these changes"""
    48 #                  modified some unit tests to accommodate these changes
    49 
    49 #
    50 """0.09 update - remove all records on service unregistration
    50 # 0.09 update - remove all records on service unregistration
    51                  fix DOS security problem with readName"""
    51 #                  fix DOS security problem with readName
    52 
    52 #
    53 """0.08 update - changed licensing to LGPL"""
    53 # 0.08 update - changed licensing to LGPL
    54 
    54 #
    55 """0.07 update - faster shutdown on engine
    55 # 0.07 update - faster shutdown on engine
    56                  pointer encoding of outgoing names
    56 #                  pointer encoding of outgoing names
    57                  ServiceBrowser now works
    57 #                  ServiceBrowser now works
    58                  new unit tests"""
    58 #                  new unit tests
    59 
    59 #
    60 """0.06 update - small improvements with unit tests
    60 # 0.06 update - small improvements with unit tests
    61                  added defined exception types
    61 #                  added defined exception types
    62                  new style objects
    62 #                  new style objects
    63                  fixed hostname/interface problem
    63 #                  fixed hostname/interface problem
    64                  fixed socket timeout problem
    64 #                  fixed socket timeout problem
    65                  fixed addServiceListener() typo bug
    65 #                  fixed addServiceListener() typo bug
    66                  using select() for socket reads
    66 #                  using select() for socket reads
    67                  tested on Debian unstable with Python 2.2.2"""
    67 #                  tested on Debian unstable with Python 2.2.2
    68 
    68 #
    69 """0.05 update - ensure case insensitivty on domain names
    69 # 0.05 update - ensure case insensitivty on domain names
    70                  support for unicast DNS queries"""
    70 #                  support for unicast DNS queries
    71 
    71 #
    72 """0.04 update - added some unit tests
    72 # 0.04 update - added some unit tests
    73                  added __ne__ adjuncts where required
    73 #                  added __ne__ adjuncts where required
    74                  ensure names end in '.local.'
    74 #                  ensure names end in '.local.'
    75                  timeout on receiving socket for clean shutdown"""
    75 #                  timeout on receiving socket for clean shutdown
    76 
       
    77 __author__ = "Paul Scott-Murphy"
       
    78 __email__ = "paul at scott dash murphy dot com"
       
    79 __version__ = "0.12"
       
    80 
    76 
    81 import string
    77 import string
    82 import time
    78 import time
    83 import struct
    79 import struct
    84 import socket
    80 import socket
    85 import threading
    81 import threading
    86 import select
    82 import select
    87 import traceback
    83 import traceback
    88 
    84 
       
    85 
       
    86 __author__ = "Paul Scott-Murphy"
       
    87 __email__ = "paul at scott dash murphy dot com"
       
    88 __version__ = "0.12"
       
    89 
       
    90 
    89 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
    91 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
    90 
    92 
    91 # hook for threads
    93 # hook for threads
    92 
    94 
    93 globals()['_GLOBAL_DONE'] = 0
    95 globals()['_GLOBAL_DONE'] = 0
    99 _REGISTER_TIME = 225
   101 _REGISTER_TIME = 225
   100 _LISTENER_TIME = 200
   102 _LISTENER_TIME = 200
   101 _BROWSER_TIME = 500
   103 _BROWSER_TIME = 500
   102 
   104 
   103 # Some DNS constants
   105 # Some DNS constants
   104     
   106 
   105 _MDNS_ADDR = '224.0.0.251'
   107 _MDNS_ADDR = '224.0.0.251'
   106 _MDNS_PORT = 5353;
   108 _MDNS_PORT = 5353
   107 _DNS_PORT = 53;
   109 _DNS_PORT = 53
   108 _DNS_TTL = 60 * 60; # one hour default TTL
   110 _DNS_TTL = 60 * 60  # one hour default TTL
   109 
   111 
   110 _MAX_MSG_TYPICAL = 1460 # unused
   112 _MAX_MSG_TYPICAL = 1460  # unused
   111 _MAX_MSG_ABSOLUTE = 8972
   113 _MAX_MSG_ABSOLUTE = 8972
   112 
   114 
   113 _FLAGS_QR_MASK = 0x8000 # query response mask
   115 _FLAGS_QR_MASK = 0x8000  # query response mask
   114 _FLAGS_QR_QUERY = 0x0000 # query
   116 _FLAGS_QR_QUERY = 0x0000  # query
   115 _FLAGS_QR_RESPONSE = 0x8000 # response
   117 _FLAGS_QR_RESPONSE = 0x8000  # response
   116 
   118 
   117 _FLAGS_AA = 0x0400 # Authorative answer
   119 _FLAGS_AA = 0x0400  # Authorative answer
   118 _FLAGS_TC = 0x0200 # Truncated
   120 _FLAGS_TC = 0x0200  # Truncated
   119 _FLAGS_RD = 0x0100 # Recursion desired
   121 _FLAGS_RD = 0x0100  # Recursion desired
   120 _FLAGS_RA = 0x8000 # Recursion available
   122 _FLAGS_RA = 0x8000  # Recursion available
   121 
   123 
   122 _FLAGS_Z = 0x0040 # Zero
   124 _FLAGS_Z = 0x0040   # Zero
   123 _FLAGS_AD = 0x0020 # Authentic data
   125 _FLAGS_AD = 0x0020  # Authentic data
   124 _FLAGS_CD = 0x0010 # Checking disabled
   126 _FLAGS_CD = 0x0010  # Checking disabled
   125 
   127 
   126 _CLASS_IN = 1
   128 _CLASS_IN = 1
   127 _CLASS_CS = 2
   129 _CLASS_CS = 2
   128 _CLASS_CH = 3
   130 _CLASS_CH = 3
   129 _CLASS_HS = 4
   131 _CLASS_HS = 4
   148 _TYPE_MINFO = 14
   150 _TYPE_MINFO = 14
   149 _TYPE_MX = 15
   151 _TYPE_MX = 15
   150 _TYPE_TXT = 16
   152 _TYPE_TXT = 16
   151 _TYPE_AAAA = 28
   153 _TYPE_AAAA = 28
   152 _TYPE_SRV = 33
   154 _TYPE_SRV = 33
   153 _TYPE_ANY =  255
   155 _TYPE_ANY = 255
   154 
   156 
   155 # Mapping constants to names
   157 # Mapping constants to names
   156 
   158 
   157 _CLASSES = { _CLASS_IN : "in",
   159 _CLASSES = {
   158              _CLASS_CS : "cs",
   160     _CLASS_IN:   "in",
   159              _CLASS_CH : "ch",
   161     _CLASS_CS:   "cs",
   160              _CLASS_HS : "hs",
   162     _CLASS_CH:   "ch",
   161              _CLASS_NONE : "none",
   163     _CLASS_HS:   "hs",
   162              _CLASS_ANY : "any" }
   164     _CLASS_NONE: "none",
   163 
   165     _CLASS_ANY:  "any"
   164 _TYPES = { _TYPE_A : "a",
   166 }
   165            _TYPE_NS : "ns",
   167 
   166            _TYPE_MD : "md",
   168 _TYPES = {
   167            _TYPE_MF : "mf",
   169     _TYPE_A:     "a",
   168            _TYPE_CNAME : "cname",
   170     _TYPE_NS:    "ns",
   169            _TYPE_SOA : "soa",
   171     _TYPE_MD:    "md",
   170            _TYPE_MB : "mb",
   172     _TYPE_MF:    "mf",
   171            _TYPE_MG : "mg",
   173     _TYPE_CNAME: "cname",
   172            _TYPE_MR : "mr",
   174     _TYPE_SOA:   "soa",
   173            _TYPE_NULL : "null",
   175     _TYPE_MB:    "mb",
   174            _TYPE_WKS : "wks",
   176     _TYPE_MG:    "mg",
   175            _TYPE_PTR : "ptr",
   177     _TYPE_MR:    "mr",
   176            _TYPE_HINFO : "hinfo",
   178     _TYPE_NULL:  "null",
   177            _TYPE_MINFO : "minfo",
   179     _TYPE_WKS:   "wks",
   178            _TYPE_MX : "mx",
   180     _TYPE_PTR:   "ptr",
   179            _TYPE_TXT : "txt",
   181     _TYPE_HINFO: "hinfo",
   180            _TYPE_AAAA : "quada",
   182     _TYPE_MINFO: "minfo",
   181            _TYPE_SRV : "srv",
   183     _TYPE_MX:    "mx",
   182            _TYPE_ANY : "any" }
   184     _TYPE_TXT:   "txt",
       
   185     _TYPE_AAAA:  "quada",
       
   186     _TYPE_SRV:   "srv",
       
   187     _TYPE_ANY:   "any"
       
   188 }
   183 
   189 
   184 # utility functions
   190 # utility functions
       
   191 
   185 
   192 
   186 def currentTimeMillis():
   193 def currentTimeMillis():
   187     """Current system time in milliseconds"""
   194     """Current system time in milliseconds"""
   188     return time.time() * 1000
   195     return time.time() * 1000
   189 
   196 
   190 # Exceptions
   197 # Exceptions
   191 
   198 
       
   199 
   192 class NonLocalNameException(Exception):
   200 class NonLocalNameException(Exception):
   193     pass
   201     pass
   194 
   202 
       
   203 
   195 class NonUniqueNameException(Exception):
   204 class NonUniqueNameException(Exception):
   196     pass
   205     pass
   197 
   206 
       
   207 
   198 class NamePartTooLongException(Exception):
   208 class NamePartTooLongException(Exception):
   199     pass
   209     pass
   200 
   210 
       
   211 
   201 class AbstractMethodException(Exception):
   212 class AbstractMethodException(Exception):
   202     pass
   213     pass
   203 
   214 
       
   215 
   204 class BadTypeInNameException(Exception):
   216 class BadTypeInNameException(Exception):
   205     pass
   217     pass
   206 
   218 
   207 # implementation classes
   219 # implementation classes
   208 
   220 
       
   221 
   209 class DNSEntry(object):
   222 class DNSEntry(object):
   210     """A DNS entry"""
   223     """A DNS entry"""
   211     
   224 
   212     def __init__(self, name, type, clazz):
   225     def __init__(self, name, type, clazz):
   213         self.key = string.lower(name)
   226         self.key = string.lower(name)
   214         self.name = name
   227         self.name = name
   215         self.type = type
   228         self.type = type
   216         self.clazz = clazz & _CLASS_MASK
   229         self.clazz = clazz & _CLASS_MASK
   228 
   241 
   229     def getClazz(self, clazz):
   242     def getClazz(self, clazz):
   230         """Class accessor"""
   243         """Class accessor"""
   231         try:
   244         try:
   232             return _CLASSES[clazz]
   245             return _CLASSES[clazz]
   233         except:
   246         except Exception:
   234             return "?(%s)" % (clazz)
   247             return "?(%s)" % (clazz)
   235 
   248 
   236     def getType(self, type):
   249     def getType(self, type):
   237         """Type accessor"""
   250         """Type accessor"""
   238         try:
   251         try:
   239             return _TYPES[type]
   252             return _TYPES[type]
   240         except:
   253         except Exception:
   241             return "?(%s)" % (type)
   254             return "?(%s)" % (type)
   242 
   255 
   243     def toString(self, hdr, other):
   256     def toString(self, hdr, other):
   244         """String representation with additional information"""
   257         """String representation with additional information"""
   245         result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
   258         result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
   252             result += ",%s]" % (other)
   265             result += ",%s]" % (other)
   253         else:
   266         else:
   254             result += "]"
   267             result += "]"
   255         return result
   268         return result
   256 
   269 
       
   270 
   257 class DNSQuestion(DNSEntry):
   271 class DNSQuestion(DNSEntry):
   258     """A DNS question entry"""
   272     """A DNS question entry"""
   259     
   273 
   260     def __init__(self, name, type, clazz):
   274     def __init__(self, name, type, clazz):
   261         if not name.endswith(".local."):
   275         if not name.endswith(".local."):
   262             raise NonLocalNameException
   276             raise NonLocalNameException
   263         DNSEntry.__init__(self, name, type, clazz)
   277         DNSEntry.__init__(self, name, type, clazz)
   264 
   278 
   271         return DNSEntry.toString(self, "question", None)
   285         return DNSEntry.toString(self, "question", None)
   272 
   286 
   273 
   287 
   274 class DNSRecord(DNSEntry):
   288 class DNSRecord(DNSEntry):
   275     """A DNS record - like a DNS entry, but has a TTL"""
   289     """A DNS record - like a DNS entry, but has a TTL"""
   276     
   290 
   277     def __init__(self, name, type, clazz, ttl):
   291     def __init__(self, name, type, clazz, ttl):
   278         DNSEntry.__init__(self, name, type, clazz)
   292         DNSEntry.__init__(self, name, type, clazz)
   279         self.ttl = ttl
   293         self.ttl = ttl
   280         self.created = currentTimeMillis()
   294         self.created = currentTimeMillis()
   281 
   295 
   330     def toString(self, other):
   344     def toString(self, other):
   331         """String representation with addtional information"""
   345         """String representation with addtional information"""
   332         arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
   346         arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
   333         return DNSEntry.toString(self, "record", arg)
   347         return DNSEntry.toString(self, "record", arg)
   334 
   348 
       
   349 
   335 class DNSAddress(DNSRecord):
   350 class DNSAddress(DNSRecord):
   336     """A DNS address record"""
   351     """A DNS address record"""
   337     
   352 
   338     def __init__(self, name, type, clazz, ttl, address):
   353     def __init__(self, name, type, clazz, ttl, address):
   339         DNSRecord.__init__(self, name, type, clazz, ttl)
   354         DNSRecord.__init__(self, name, type, clazz, ttl)
   340         self.address = address
   355         self.address = address
   341 
   356 
   342     def write(self, out):
   357     def write(self, out):
   351 
   366 
   352     def __repr__(self):
   367     def __repr__(self):
   353         """String representation"""
   368         """String representation"""
   354         try:
   369         try:
   355             return socket.inet_ntoa(self.address)
   370             return socket.inet_ntoa(self.address)
   356         except:
   371         except Exception:
   357             return self.address
   372             return self.address
       
   373 
   358 
   374 
   359 class DNSHinfo(DNSRecord):
   375 class DNSHinfo(DNSRecord):
   360     """A DNS host information record"""
   376     """A DNS host information record"""
   361 
   377 
   362     def __init__(self, name, type, clazz, ttl, cpu, os):
   378     def __init__(self, name, type, clazz, ttl, cpu, os):
   376         return 0
   392         return 0
   377 
   393 
   378     def __repr__(self):
   394     def __repr__(self):
   379         """String representation"""
   395         """String representation"""
   380         return self.cpu + " " + self.os
   396         return self.cpu + " " + self.os
   381     
   397 
       
   398 
   382 class DNSPointer(DNSRecord):
   399 class DNSPointer(DNSRecord):
   383     """A DNS pointer record"""
   400     """A DNS pointer record"""
   384     
   401 
   385     def __init__(self, name, type, clazz, ttl, alias):
   402     def __init__(self, name, type, clazz, ttl, alias):
   386         DNSRecord.__init__(self, name, type, clazz, ttl)
   403         DNSRecord.__init__(self, name, type, clazz, ttl)
   387         self.alias = alias
   404         self.alias = alias
   388 
   405 
   389     def write(self, out):
   406     def write(self, out):
   398 
   415 
   399     def __repr__(self):
   416     def __repr__(self):
   400         """String representation"""
   417         """String representation"""
   401         return self.toString(self.alias)
   418         return self.toString(self.alias)
   402 
   419 
       
   420 
   403 class DNSText(DNSRecord):
   421 class DNSText(DNSRecord):
   404     """A DNS text record"""
   422     """A DNS text record"""
   405     
   423 
   406     def __init__(self, name, type, clazz, ttl, text):
   424     def __init__(self, name, type, clazz, ttl, text):
   407         DNSRecord.__init__(self, name, type, clazz, ttl)
   425         DNSRecord.__init__(self, name, type, clazz, ttl)
   408         self.text = text
   426         self.text = text
   409 
   427 
   410     def write(self, out):
   428     def write(self, out):
   422         if len(self.text) > 10:
   440         if len(self.text) > 10:
   423             return self.toString(self.text[:7] + "...")
   441             return self.toString(self.text[:7] + "...")
   424         else:
   442         else:
   425             return self.toString(self.text)
   443             return self.toString(self.text)
   426 
   444 
       
   445 
   427 class DNSService(DNSRecord):
   446 class DNSService(DNSRecord):
   428     """A DNS service record"""
   447     """A DNS service record"""
   429     
   448 
   430     def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
   449     def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
   431         DNSRecord.__init__(self, name, type, clazz, ttl)
   450         DNSRecord.__init__(self, name, type, clazz, ttl)
   432         self.priority = priority
   451         self.priority = priority
   433         self.weight = weight
   452         self.weight = weight
   434         self.port = port
   453         self.port = port
   449 
   468 
   450     def __repr__(self):
   469     def __repr__(self):
   451         """String representation"""
   470         """String representation"""
   452         return self.toString("%s:%s" % (self.server, self.port))
   471         return self.toString("%s:%s" % (self.server, self.port))
   453 
   472 
       
   473 
   454 class DNSIncoming(object):
   474 class DNSIncoming(object):
   455     """Object representation of an incoming DNS packet"""
   475     """Object representation of an incoming DNS packet"""
   456     
   476 
   457     def __init__(self, data):
   477     def __init__(self, data):
   458         """Constructor from string holding bytes of packet"""
   478         """Constructor from string holding bytes of packet"""
   459         self.offset = 0
   479         self.offset = 0
   460         self.data = data
   480         self.data = data
   461         self.questions = []
   481         self.questions = []
   462         self.answers = []
   482         self.answers = []
   463         self.numQuestions = 0
   483         self.numQuestions = 0
   464         self.numAnswers = 0
   484         self.numAnswers = 0
   465         self.numAuthorities = 0
   485         self.numAuthorities = 0
   466         self.numAdditionals = 0
   486         self.numAdditionals = 0
   467         
   487 
   468         self.readHeader()
   488         self.readHeader()
   469         self.readQuestions()
   489         self.readQuestions()
   470         self.readOthers()
   490         self.readOthers()
   471 
   491 
   472     def readHeader(self):
   492     def readHeader(self):
   489         length = struct.calcsize(format)
   509         length = struct.calcsize(format)
   490         for i in range(0, self.numQuestions):
   510         for i in range(0, self.numQuestions):
   491             name = self.readName()
   511             name = self.readName()
   492             info = struct.unpack(format, self.data[self.offset:self.offset+length])
   512             info = struct.unpack(format, self.data[self.offset:self.offset+length])
   493             self.offset += length
   513             self.offset += length
   494             
   514 
   495             question = DNSQuestion(name, info[0], info[1])
   515             question = DNSQuestion(name, info[0], info[1])
   496             self.questions.append(question)
   516             self.questions.append(question)
   497 
   517 
   498     def readInt(self):
   518     def readInt(self):
   499         """Reads an integer from the packet"""
   519         """Reads an integer from the packet"""
   510         return self.readString(length)
   530         return self.readString(length)
   511 
   531 
   512     def readString(self, len):
   532     def readString(self, len):
   513         """Reads a string of a given length from the packet"""
   533         """Reads a string of a given length from the packet"""
   514         format = '!' + str(len) + 's'
   534         format = '!' + str(len) + 's'
   515         length =  struct.calcsize(format)
   535         length = struct.calcsize(format)
   516         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   536         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   517         self.offset += length
   537         self.offset += length
   518         return info[0]
   538         return info[0]
   519 
   539 
   520     def readUnsignedShort(self):
   540     def readUnsignedShort(self):
   553                 # this may mean the rest of the name is
   573                 # this may mean the rest of the name is
   554                 # unable to be parsed, and may show errors
   574                 # unable to be parsed, and may show errors
   555                 # so this is left for debugging.  New types
   575                 # so this is left for debugging.  New types
   556                 # encountered need to be parsed properly.
   576                 # encountered need to be parsed properly.
   557                 #
   577                 #
   558                 #print "UNKNOWN TYPE = " + str(info[0])
   578                 # print "UNKNOWN TYPE = " + str(info[0])
   559                 #raise BadTypeInNameException
   579                 # raise BadTypeInNameException
   560                 pass
   580                 pass
   561 
   581 
   562             if rec is not None:
   582             if rec is not None:
   563                 self.answers.append(rec)
   583                 self.answers.append(rec)
   564                 
   584 
   565     def isQuery(self):
   585     def isQuery(self):
   566         """Returns true if this is a query"""
   586         """Returns true if this is a query"""
   567         return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
   587         return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
   568 
   588 
   569     def isResponse(self):
   589     def isResponse(self):
   572 
   592 
   573     def readUTF(self, offset, len):
   593     def readUTF(self, offset, len):
   574         """Reads a UTF-8 string of a given length from the packet"""
   594         """Reads a UTF-8 string of a given length from the packet"""
   575         result = self.data[offset:offset+len].decode('utf-8')
   595         result = self.data[offset:offset+len].decode('utf-8')
   576         return result
   596         return result
   577         
   597 
   578     def readName(self):
   598     def readName(self):
   579         """Reads a domain name from the packet"""
   599         """Reads a domain name from the packet"""
   580         result = ''
   600         result = ''
   581         off = self.offset
   601         off = self.offset
   582         next = -1
   602         next = -1
   605             self.offset = next
   625             self.offset = next
   606         else:
   626         else:
   607             self.offset = off
   627             self.offset = off
   608 
   628 
   609         return result
   629         return result
   610     
   630 
   611         
   631 
   612 class DNSOutgoing(object):
   632 class DNSOutgoing(object):
   613     """Object representation of an outgoing packet"""
   633     """Object representation of an outgoing packet"""
   614     
   634 
   615     def __init__(self, flags, multicast = 1):
   635     def __init__(self, flags, multicast=1):
   616         self.finished = 0
   636         self.finished = 0
   617         self.id = 0
   637         self.id = 0
   618         self.multicast = multicast
   638         self.multicast = multicast
   619         self.flags = flags
   639         self.flags = flags
   620         self.names = {}
   640         self.names = {}
   621         self.data = []
   641         self.data = []
   622         self.size = 12
   642         self.size = 12
   623         
   643 
   624         self.questions = []
   644         self.questions = []
   625         self.answers = []
   645         self.answers = []
   626         self.authorities = []
   646         self.authorities = []
   627         self.additionals = []
   647         self.additionals = []
   628 
   648 
   658     def insertShort(self, index, value):
   678     def insertShort(self, index, value):
   659         """Inserts an unsigned short in a certain position in the packet"""
   679         """Inserts an unsigned short in a certain position in the packet"""
   660         format = '!H'
   680         format = '!H'
   661         self.data.insert(index, struct.pack(format, value))
   681         self.data.insert(index, struct.pack(format, value))
   662         self.size += 2
   682         self.size += 2
   663         
   683 
   664     def writeShort(self, value):
   684     def writeShort(self, value):
   665         """Writes an unsigned short to the packet"""
   685         """Writes an unsigned short to the packet"""
   666         format = '!H'
   686         format = '!H'
   667         self.data.append(struct.pack(format, value))
   687         self.data.append(struct.pack(format, value))
   668         self.size += 2
   688         self.size += 2
   737         # Adjust size for the short we will write before this record
   757         # Adjust size for the short we will write before this record
   738         #
   758         #
   739         self.size += 2
   759         self.size += 2
   740         record.write(self)
   760         record.write(self)
   741         self.size -= 2
   761         self.size -= 2
   742         
   762 
   743         length = len(''.join(self.data[index:]))
   763         length = len(''.join(self.data[index:]))
   744         self.insertShort(index, length) # Here is the short we adjusted for
   764         self.insertShort(index, length)  # Here is the short we adjusted for
   745 
   765 
   746     def packet(self):
   766     def packet(self):
   747         """Returns a string containing the packet's bytes
   767         """Returns a string containing the packet's bytes
   748 
   768 
   749         No further parts should be added to the packet once this
   769         No further parts should be added to the packet once this
   756                 self.writeRecord(answer, time)
   776                 self.writeRecord(answer, time)
   757             for authority in self.authorities:
   777             for authority in self.authorities:
   758                 self.writeRecord(authority, 0)
   778                 self.writeRecord(authority, 0)
   759             for additional in self.additionals:
   779             for additional in self.additionals:
   760                 self.writeRecord(additional, 0)
   780                 self.writeRecord(additional, 0)
   761         
   781 
   762             self.insertShort(0, len(self.additionals))
   782             self.insertShort(0, len(self.additionals))
   763             self.insertShort(0, len(self.authorities))
   783             self.insertShort(0, len(self.authorities))
   764             self.insertShort(0, len(self.answers))
   784             self.insertShort(0, len(self.answers))
   765             self.insertShort(0, len(self.questions))
   785             self.insertShort(0, len(self.questions))
   766             self.insertShort(0, self.flags)
   786             self.insertShort(0, self.flags)
   771         return ''.join(self.data)
   791         return ''.join(self.data)
   772 
   792 
   773 
   793 
   774 class DNSCache(object):
   794 class DNSCache(object):
   775     """A cache of DNS entries"""
   795     """A cache of DNS entries"""
   776     
   796 
   777     def __init__(self):
   797     def __init__(self):
   778         self.cache = {}
   798         self.cache = {}
   779 
   799 
   780     def add(self, entry):
   800     def add(self, entry):
   781         """Adds an entry"""
   801         """Adds an entry"""
   782         try:
   802         try:
   783             list = self.cache[entry.key]
   803             list = self.cache[entry.key]
   784         except:
   804         except Exception:
   785             list = self.cache[entry.key] = []
   805             list = self.cache[entry.key] = []
   786         list.append(entry)
   806         list.append(entry)
   787 
   807 
   788     def remove(self, entry):
   808     def remove(self, entry):
   789         """Removes an entry"""
   809         """Removes an entry"""
   790         try:
   810         try:
   791             list = self.cache[entry.key]
   811             list = self.cache[entry.key]
   792             list.remove(entry)
   812             list.remove(entry)
   793         except:
   813         except Exception:
   794             pass
   814             pass
   795 
   815 
   796     def get(self, entry):
   816     def get(self, entry):
   797         """Gets an entry by key.  Will return None if there is no
   817         """Gets an entry by key.  Will return None if there is no
   798         matching entry."""
   818         matching entry."""
   799         try:
   819         try:
   800             list = self.cache[entry.key]
   820             list = self.cache[entry.key]
   801             return list[list.index(entry)]
   821             return list[list.index(entry)]
   802         except:
   822         except Exception:
   803             return None
   823             return None
   804 
   824 
   805     def getByDetails(self, name, type, clazz):
   825     def getByDetails(self, name, type, clazz):
   806         """Gets an entry by details.  Will return None if there is
   826         """Gets an entry by details.  Will return None if there is
   807         no matching entry."""
   827         no matching entry."""
   810 
   830 
   811     def entriesWithName(self, name):
   831     def entriesWithName(self, name):
   812         """Returns a list of entries whose key matches the name."""
   832         """Returns a list of entries whose key matches the name."""
   813         try:
   833         try:
   814             return self.cache[name]
   834             return self.cache[name]
   815         except:
   835         except Exception:
   816             return []
   836             return []
   817 
   837 
   818     def entries(self):
   838     def entries(self):
   819         """Returns a list of all entries"""
   839         """Returns a list of all entries"""
   820         def add(x, y): return x+y
   840         def add(x, y): return x+y
   821         try:
   841         try:
   822             return reduce(add, self.cache.values())
   842             return reduce(add, self.cache.values())
   823         except:
   843         except Exception:
   824             return []
   844             return []
   825 
   845 
   826 
   846 
   827 class Engine(threading.Thread):
   847 class Engine(threading.Thread):
   828     """An engine wraps read access to sockets, allowing objects that
   848     """An engine wraps read access to sockets, allowing objects that
   837     """
   857     """
   838 
   858 
   839     def __init__(self, zeroconf):
   859     def __init__(self, zeroconf):
   840         threading.Thread.__init__(self)
   860         threading.Thread.__init__(self)
   841         self.zeroconf = zeroconf
   861         self.zeroconf = zeroconf
   842         self.readers = {} # maps socket to reader
   862         self.readers = {}  # maps socket to reader
   843         self.timeout = 5
   863         self.timeout = 5
   844         self.condition = threading.Condition()
   864         self.condition = threading.Condition()
   845         self.start()
   865         self.start()
   846 
   866 
   847     def run(self):
   867     def run(self):
   858                 try:
   878                 try:
   859                     rr, wr, er = select.select(rs, [], [], self.timeout)
   879                     rr, wr, er = select.select(rs, [], [], self.timeout)
   860                     for socket in rr:
   880                     for socket in rr:
   861                         try:
   881                         try:
   862                             self.readers[socket].handle_read()
   882                             self.readers[socket].handle_read()
   863                         except:
   883                         except Exception:
   864                             # Ignore errors that occur on shutdown
   884                             # Ignore errors that occur on shutdown
   865                             pass
   885                             pass
   866                 except:
   886                 except Exception:
   867                     pass
   887                     pass
   868 
   888 
   869     def getReaders(self):
   889     def getReaders(self):
   870         result = []
   890         result = []
   871         self.condition.acquire()
   891         self.condition.acquire()
   872         result = self.readers.keys()
   892         result = self.readers.keys()
   873         self.condition.release()
   893         self.condition.release()
   874         return result
   894         return result
   875     
   895 
   876     def addReader(self, reader, socket):
   896     def addReader(self, reader, socket):
   877         self.condition.acquire()
   897         self.condition.acquire()
   878         self.readers[socket] = reader
   898         self.readers[socket] = reader
   879         self.condition.notify()
   899         self.condition.notify()
   880         self.condition.release()
   900         self.condition.release()
   888     def notify(self):
   908     def notify(self):
   889         self.condition.acquire()
   909         self.condition.acquire()
   890         self.condition.notify()
   910         self.condition.notify()
   891         self.condition.release()
   911         self.condition.release()
   892 
   912 
       
   913 
   893 class Listener(object):
   914 class Listener(object):
   894     """A Listener is used by this module to listen on the multicast
   915     """A Listener is used by this module to listen on the multicast
   895     group to which DNS messages are sent, allowing the implementation
   916     group to which DNS messages are sent, allowing the implementation
   896     to cache information as it arrives.
   917     to cache information as it arrives.
   897 
   918 
   898     It requires registration with an Engine object in order to have
   919     It requires registration with an Engine object in order to have
   899     the read() method called when a socket is availble for reading."""
   920     the read() method called when a socket is availble for reading."""
   900     
   921 
   901     def __init__(self, zeroconf):
   922     def __init__(self, zeroconf):
   902         self.zeroconf = zeroconf
   923         self.zeroconf = zeroconf
   903         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
   924         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
   904 
   925 
   905     def handle_read(self):
   926     def handle_read(self):
   922 
   943 
   923 
   944 
   924 class Reaper(threading.Thread):
   945 class Reaper(threading.Thread):
   925     """A Reaper is used by this module to remove cache entries that
   946     """A Reaper is used by this module to remove cache entries that
   926     have expired."""
   947     have expired."""
   927     
   948 
   928     def __init__(self, zeroconf):
   949     def __init__(self, zeroconf):
   929         threading.Thread.__init__(self)
   950         threading.Thread.__init__(self)
   930         self.zeroconf = zeroconf
   951         self.zeroconf = zeroconf
   931         self.start()
   952         self.start()
   932 
   953 
   946     """Used to browse for a service of a specific type.
   967     """Used to browse for a service of a specific type.
   947 
   968 
   948     The listener object will have its addService() and
   969     The listener object will have its addService() and
   949     removeService() methods called when this browser
   970     removeService() methods called when this browser
   950     discovers changes in the services availability."""
   971     discovers changes in the services availability."""
   951     
   972 
   952     def __init__(self, zeroconf, type, listener):
   973     def __init__(self, zeroconf, type, listener):
   953         """Creates a browser for a specific type"""
   974         """Creates a browser for a specific type"""
   954         threading.Thread.__init__(self)
   975         threading.Thread.__init__(self)
   955         self.zeroconf = zeroconf
   976         self.zeroconf = zeroconf
   956         self.type = type
   977         self.type = type
   957         self.listener = listener
   978         self.listener = listener
   958         self.services = {}
   979         self.services = {}
   959         self.nextTime = currentTimeMillis()
   980         self.nextTime = currentTimeMillis()
   960         self.delay = _BROWSER_TIME
   981         self.delay = _BROWSER_TIME
   961         self.list = []
   982         self.list = []
   962         
   983 
   963         self.done = 0
   984         self.done = 0
   964 
   985 
   965         self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
   986         self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
   966         self.start()
   987         self.start()
   967 
   988 
   974             try:
   995             try:
   975                 oldrecord = self.services[record.alias.lower()]
   996                 oldrecord = self.services[record.alias.lower()]
   976                 if not expired:
   997                 if not expired:
   977                     oldrecord.resetTTL(record)
   998                     oldrecord.resetTTL(record)
   978                 else:
   999                 else:
       
  1000                     def callback(x):
       
  1001                         return self.listener.removeService(x, self.type, record.alias)
   979                     del(self.services[record.alias.lower()])
  1002                     del(self.services[record.alias.lower()])
   980                     callback = lambda x: self.listener.removeService(x, self.type, record.alias)
       
   981                     self.list.append(callback)
  1003                     self.list.append(callback)
   982                     return
  1004                     return
   983             except:
  1005             except Exception:
   984                 if not expired:
  1006                 if not expired:
       
  1007                     def callback(x):
       
  1008                         return self.listener.addService(x, self.type, record.alias)
   985                     self.services[record.alias.lower()] = record
  1009                     self.services[record.alias.lower()] = record
   986                     callback = lambda x: self.listener.addService(x, self.type, record.alias)
       
   987                     self.list.append(callback)
  1010                     self.list.append(callback)
   988 
  1011 
   989             expires = record.getExpirationTime(75)
  1012             expires = record.getExpirationTime(75)
   990             if expires < self.nextTime:
  1013             if expires < self.nextTime:
   991                 self.nextTime = expires
  1014                 self.nextTime = expires
  1017             if len(self.list) > 0:
  1040             if len(self.list) > 0:
  1018                 event = self.list.pop(0)
  1041                 event = self.list.pop(0)
  1019 
  1042 
  1020             if event is not None:
  1043             if event is not None:
  1021                 event(self.zeroconf)
  1044                 event(self.zeroconf)
  1022                 
  1045 
  1023 
  1046 
  1024 class ServiceInfo(object):
  1047 class ServiceInfo(object):
  1025     """Service information"""
  1048     """Service information"""
  1026     
  1049 
  1027     def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
  1050     def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
  1028         """Create a service description.
  1051         """Create a service description.
  1029 
  1052 
  1030         type: fully qualified service type name
  1053         type: fully qualified service type name
  1031         name: fully qualified service name
  1054         name: fully qualified service name
  1087             while index < end:
  1110             while index < end:
  1088                 length = ord(text[index])
  1111                 length = ord(text[index])
  1089                 index += 1
  1112                 index += 1
  1090                 strs.append(text[index:index+length])
  1113                 strs.append(text[index:index+length])
  1091                 index += length
  1114                 index += length
  1092             
  1115 
  1093             for s in strs:
  1116             for s in strs:
  1094                 eindex = s.find('=')
  1117                 eindex = s.find('=')
  1095                 if eindex == -1:
  1118                 if eindex == -1:
  1096                     # No equals sign at all
  1119                     # No equals sign at all
  1097                     key = s
  1120                     key = s
  1103                         value = 1
  1126                         value = 1
  1104                     elif value == 'false' or not value:
  1127                     elif value == 'false' or not value:
  1105                         value = 0
  1128                         value = 0
  1106 
  1129 
  1107                 # Only update non-existent properties
  1130                 # Only update non-existent properties
  1108                 if key and result.get(key) == None:
  1131                 if key and result.get(key) is None:
  1109                     result[key] = value
  1132                     result[key] = value
  1110 
  1133 
  1111             self.properties = result
  1134             self.properties = result
  1112         except:
  1135         except Exception:
  1113             traceback.print_exc()
  1136             traceback.print_exc()
  1114             self.properties = None
  1137             self.properties = None
  1115             
  1138 
  1116     def getType(self):
  1139     def getType(self):
  1117         """Type accessor"""
  1140         """Type accessor"""
  1118         return self.type
  1141         return self.type
  1119 
  1142 
  1120     def getName(self):
  1143     def getName(self):
  1198                 zeroconf.wait(min(next, last) - now)
  1221                 zeroconf.wait(min(next, last) - now)
  1199                 now = currentTimeMillis()
  1222                 now = currentTimeMillis()
  1200             result = 1
  1223             result = 1
  1201         finally:
  1224         finally:
  1202             zeroconf.removeListener(self)
  1225             zeroconf.removeListener(self)
  1203             
  1226 
  1204         return result
  1227         return result
  1205 
  1228 
  1206     def __eq__(self, other):
  1229     def __eq__(self, other):
  1207         """Tests equality of service name"""
  1230         """Tests equality of service name"""
  1208         if isinstance(other, ServiceInfo):
  1231         if isinstance(other, ServiceInfo):
  1223                 result += self.text
  1246                 result += self.text
  1224             else:
  1247             else:
  1225                 result += self.text[:17] + "..."
  1248                 result += self.text[:17] + "..."
  1226         result += "]"
  1249         result += "]"
  1227         return result
  1250         return result
  1228                 
  1251 
  1229 
  1252 
  1230 class Zeroconf(object):
  1253 class Zeroconf(object):
  1231     """Implementation of Zeroconf Multicast DNS Service Discovery
  1254     """Implementation of Zeroconf Multicast DNS Service Discovery
  1232 
  1255 
  1233     Supports registration, unregistration, queries and browsing.
  1256     Supports registration, unregistration, queries and browsing.
  1240         self.group = ('', _MDNS_PORT)
  1263         self.group = ('', _MDNS_PORT)
  1241         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1264         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1242         try:
  1265         try:
  1243             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1266             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1244             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1267             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1245         except:
  1268         except Exception:
  1246             # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
  1269             # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
  1247             # multicast UDP sockets (p 731, "TCP/IP Illustrated,
  1270             # multicast UDP sockets (p 731, "TCP/IP Illustrated,
  1248             # Volume 2"), but some BSD-derived systems require
  1271             # Volume 2"), but some BSD-derived systems require
  1249             # SO_REUSEPORT to be specified explicity.  Also, not all
  1272             # SO_REUSEPORT to be specified explicity.  Also, not all
  1250             # versions of Python have SO_REUSEPORT available.  So
  1273             # versions of Python have SO_REUSEPORT available.  So
  1255             pass
  1278             pass
  1256         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
  1279         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
  1257         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
  1280         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
  1258         try:
  1281         try:
  1259             self.socket.bind(self.group)
  1282             self.socket.bind(self.group)
  1260         except:
  1283         except Exception:
  1261             # Some versions of linux raise an exception even though
  1284             # Some versions of linux raise an exception even though
  1262             # the SO_REUSE* options have been set, so ignore it
  1285             # the SO_REUSE* options have been set, so ignore it
  1263             #
  1286             #
  1264             pass
  1287             pass
  1265 
  1288 
  1272         self.services = {}
  1295         self.services = {}
  1273 
  1296 
  1274         self.cache = DNSCache()
  1297         self.cache = DNSCache()
  1275 
  1298 
  1276         self.condition = threading.Condition()
  1299         self.condition = threading.Condition()
  1277         
  1300 
  1278         self.engine = Engine(self)
  1301         self.engine = Engine(self)
  1279         self.listener = Listener(self)
  1302         self.listener = Listener(self)
  1280         self.reaper = Reaper(self)
  1303         self.reaper = Reaper(self)
  1281 
  1304 
  1282     def isLoopback(self):
  1305     def isLoopback(self):
  1352 
  1375 
  1353     def unregisterService(self, info):
  1376     def unregisterService(self, info):
  1354         """Unregister a service."""
  1377         """Unregister a service."""
  1355         try:
  1378         try:
  1356             del(self.services[info.name.lower()])
  1379             del(self.services[info.name.lower()])
  1357         except:
  1380         except Exception:
  1358             pass
  1381             pass
  1359         now = currentTimeMillis()
  1382         now = currentTimeMillis()
  1360         nextTime = now
  1383         nextTime = now
  1361         i = 0
  1384         i = 0
  1362         while i < 3:
  1385         while i < 3:
  1437     def removeListener(self, listener):
  1460     def removeListener(self, listener):
  1438         """Removes a listener."""
  1461         """Removes a listener."""
  1439         try:
  1462         try:
  1440             self.listeners.remove(listener)
  1463             self.listeners.remove(listener)
  1441             self.notifyAll()
  1464             self.notifyAll()
  1442         except:
  1465         except Exception:
  1443             pass
  1466             pass
  1444 
  1467 
  1445     def updateRecord(self, now, rec):
  1468     def updateRecord(self, now, rec):
  1446         """Used to notify listeners of new information that has updated
  1469         """Used to notify listeners of new information that has updated
  1447         a record."""
  1470         a record."""
  1463                     if entry is not None:
  1486                     if entry is not None:
  1464                         entry.resetTTL(record)
  1487                         entry.resetTTL(record)
  1465                         record = entry
  1488                         record = entry
  1466             else:
  1489             else:
  1467                 self.cache.add(record)
  1490                 self.cache.add(record)
  1468                 
  1491 
  1469             self.updateRecord(now, record)
  1492             self.updateRecord(now, record)
  1470 
  1493 
  1471     def handleQuery(self, msg, addr, port):
  1494     def handleQuery(self, msg, addr, port):
  1472         """Deal with incoming query packets.  Provides a response if
  1495         """Deal with incoming query packets.  Provides a response if
  1473         possible."""
  1496         possible."""
  1477         #
  1500         #
  1478         if port != _MDNS_PORT:
  1501         if port != _MDNS_PORT:
  1479             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
  1502             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
  1480             for question in msg.questions:
  1503             for question in msg.questions:
  1481                 out.addQuestion(question)
  1504                 out.addQuestion(question)
  1482         
  1505 
  1483         for question in msg.questions:
  1506         for question in msg.questions:
  1484             if question.type == _TYPE_PTR:
  1507             if question.type == _TYPE_PTR:
  1485                 for service in self.services.values():
  1508                 for service in self.services.values():
  1486                     if question.name == service.type:
  1509                     if question.name == service.type:
  1487                         if out is None:
  1510                         if out is None:
  1489                         out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
  1512                         out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
  1490             else:
  1513             else:
  1491                 try:
  1514                 try:
  1492                     if out is None:
  1515                     if out is None:
  1493                         out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1516                         out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1494                     
  1517 
  1495                     # Answer A record queries for any service addresses we know
  1518                     # Answer A record queries for any service addresses we know
  1496                     if question.type == _TYPE_A or question.type == _TYPE_ANY:
  1519                     if question.type == _TYPE_A or question.type == _TYPE_ANY:
  1497                         for service in self.services.values():
  1520                         for service in self.services.values():
  1498                             if service.server == question.name.lower():
  1521                             if service.server == question.name.lower():
  1499                                 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1522                                 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1500                     
  1523 
  1501                     service = self.services.get(question.name.lower(), None)
  1524                     service = self.services.get(question.name.lower(), None)
  1502                     if not service: continue
  1525                     if not service:
  1503                     
  1526                         continue
       
  1527 
  1504                     if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
  1528                     if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
  1505                         out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
  1529                         out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
  1506                     if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
  1530                     if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
  1507                         out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
  1531                         out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
  1508                     if question.type == _TYPE_SRV:
  1532                     if question.type == _TYPE_SRV:
  1509                         out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1533                         out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1510                 except:
  1534                 except Exception:
  1511                     traceback.print_exc()
  1535                     traceback.print_exc()
  1512                 
  1536 
  1513         if out is not None and out.answers:
  1537         if out is not None and out.answers:
  1514             out.id = msg.id
  1538             out.id = msg.id
  1515             self.send(out, addr, port)
  1539             self.send(out, addr, port)
  1516 
  1540 
  1517     def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
  1541     def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
  1518         """Sends an outgoing packet."""
  1542         """Sends an outgoing packet."""
  1519         # This is a quick test to see if we can parse the packets we generate
  1543         # This is a quick test to see if we can parse the packets we generate
  1520         #temp = DNSIncoming(out.packet())
  1544         # temp = DNSIncoming(out.packet())
  1521         try:
  1545         try:
  1522             bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
  1546             bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
  1523         except:
  1547         except Exception:
  1524             # Ignore this, it may be a temporary loss of network connection
  1548             # Ignore this, it may be a temporary loss of network connection
  1525             pass
  1549             pass
  1526 
  1550 
  1527     def close(self):
  1551     def close(self):
  1528         """Ends the background threads, and prevent this instance from
  1552         """Ends the background threads, and prevent this instance from
  1532             self.notifyAll()
  1556             self.notifyAll()
  1533             self.engine.notify()
  1557             self.engine.notify()
  1534             self.unregisterAllServices()
  1558             self.unregisterAllServices()
  1535             self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
  1559             self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
  1536             self.socket.close()
  1560             self.socket.close()
  1537             
  1561 
  1538 # Test a few module features, including service registration, service
  1562 # Test a few module features, including service registration, service
  1539 # query (for Zoe), and service unregistration.
  1563 # query (for Zoe), and service unregistration.
  1540 
  1564 
  1541 if __name__ == '__main__':    
  1565 
       
  1566 if __name__ == '__main__':
  1542     print "Multicast DNS Service Discovery for Python, version", __version__
  1567     print "Multicast DNS Service Discovery for Python, version", __version__
  1543     r = Zeroconf()
  1568     r = Zeroconf()
  1544     print "1. Testing registration of a service..."
  1569     print "1. Testing registration of a service..."
  1545     desc = {'version':'0.10','a':'test value', 'b':'another value'}
  1570     desc = {'version': '0.10', 'a': 'test value', 'b': 'another value'}
  1546     info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
  1571     info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
  1547     print "   Registering service..."
  1572     print "   Registering service..."
  1548     r.registerService(info)
  1573     r.registerService(info)
  1549     print "   Registration done."
  1574     print "   Registration done."
  1550     print "2. Testing query of service information..."
  1575     print "2. Testing query of service information..."