Zeroconf.py
changeset 223 1c0d3aaacf3d
parent 203 cb9901076a21
child 361 331d698e1118
child 373 a7f26d9f214f
equal deleted inserted replaced
222:d0f7d36bf241 223:1c0d3aaacf3d
    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 
    76 
    77 __author__ = "Paul Scott-Murphy"
    77 __author__ = "Paul Scott-Murphy"
    78 __email__ = "paul at scott dash murphy dot com"
    78 __email__ = "paul at scott dash murphy dot com"
    79 __version__ = "0.12"
    79 __version__ = "0.12"
    80 
    80 
    99 _REGISTER_TIME = 225
    99 _REGISTER_TIME = 225
   100 _LISTENER_TIME = 200
   100 _LISTENER_TIME = 200
   101 _BROWSER_TIME = 500
   101 _BROWSER_TIME = 500
   102 
   102 
   103 # Some DNS constants
   103 # Some DNS constants
   104 	
   104     
   105 _MDNS_ADDR = '224.0.0.251'
   105 _MDNS_ADDR = '224.0.0.251'
   106 _MDNS_PORT = 5353;
   106 _MDNS_PORT = 5353;
   107 _DNS_PORT = 53;
   107 _DNS_PORT = 53;
   108 _DNS_TTL = 60 * 60; # one hour default TTL
   108 _DNS_TTL = 60 * 60; # one hour default TTL
   109 
   109 
   153 _TYPE_ANY =  255
   153 _TYPE_ANY =  255
   154 
   154 
   155 # Mapping constants to names
   155 # Mapping constants to names
   156 
   156 
   157 _CLASSES = { _CLASS_IN : "in",
   157 _CLASSES = { _CLASS_IN : "in",
   158 			 _CLASS_CS : "cs",
   158              _CLASS_CS : "cs",
   159 			 _CLASS_CH : "ch",
   159              _CLASS_CH : "ch",
   160 			 _CLASS_HS : "hs",
   160              _CLASS_HS : "hs",
   161 			 _CLASS_NONE : "none",
   161              _CLASS_NONE : "none",
   162 			 _CLASS_ANY : "any" }
   162              _CLASS_ANY : "any" }
   163 
   163 
   164 _TYPES = { _TYPE_A : "a",
   164 _TYPES = { _TYPE_A : "a",
   165 		   _TYPE_NS : "ns",
   165            _TYPE_NS : "ns",
   166 		   _TYPE_MD : "md",
   166            _TYPE_MD : "md",
   167 		   _TYPE_MF : "mf",
   167            _TYPE_MF : "mf",
   168 		   _TYPE_CNAME : "cname",
   168            _TYPE_CNAME : "cname",
   169 		   _TYPE_SOA : "soa",
   169            _TYPE_SOA : "soa",
   170 		   _TYPE_MB : "mb",
   170            _TYPE_MB : "mb",
   171 		   _TYPE_MG : "mg",
   171            _TYPE_MG : "mg",
   172 		   _TYPE_MR : "mr",
   172            _TYPE_MR : "mr",
   173 		   _TYPE_NULL : "null",
   173            _TYPE_NULL : "null",
   174 		   _TYPE_WKS : "wks",
   174            _TYPE_WKS : "wks",
   175 		   _TYPE_PTR : "ptr",
   175            _TYPE_PTR : "ptr",
   176 		   _TYPE_HINFO : "hinfo",
   176            _TYPE_HINFO : "hinfo",
   177 		   _TYPE_MINFO : "minfo",
   177            _TYPE_MINFO : "minfo",
   178 		   _TYPE_MX : "mx",
   178            _TYPE_MX : "mx",
   179 		   _TYPE_TXT : "txt",
   179            _TYPE_TXT : "txt",
   180 		   _TYPE_AAAA : "quada",
   180            _TYPE_AAAA : "quada",
   181 		   _TYPE_SRV : "srv",
   181            _TYPE_SRV : "srv",
   182 		   _TYPE_ANY : "any" }
   182            _TYPE_ANY : "any" }
   183 
   183 
   184 # utility functions
   184 # utility functions
   185 
   185 
   186 def currentTimeMillis():
   186 def currentTimeMillis():
   187 	"""Current system time in milliseconds"""
   187     """Current system time in milliseconds"""
   188 	return time.time() * 1000
   188     return time.time() * 1000
   189 
   189 
   190 # Exceptions
   190 # Exceptions
   191 
   191 
   192 class NonLocalNameException(Exception):
   192 class NonLocalNameException(Exception):
   193 	pass
   193     pass
   194 
   194 
   195 class NonUniqueNameException(Exception):
   195 class NonUniqueNameException(Exception):
   196 	pass
   196     pass
   197 
   197 
   198 class NamePartTooLongException(Exception):
   198 class NamePartTooLongException(Exception):
   199 	pass
   199     pass
   200 
   200 
   201 class AbstractMethodException(Exception):
   201 class AbstractMethodException(Exception):
   202 	pass
   202     pass
   203 
   203 
   204 class BadTypeInNameException(Exception):
   204 class BadTypeInNameException(Exception):
   205 	pass
   205     pass
   206 
   206 
   207 # implementation classes
   207 # implementation classes
   208 
   208 
   209 class DNSEntry(object):
   209 class DNSEntry(object):
   210 	"""A DNS entry"""
   210     """A DNS entry"""
   211 	
   211     
   212 	def __init__(self, name, type, clazz):
   212     def __init__(self, name, type, clazz):
   213 		self.key = string.lower(name)
   213         self.key = string.lower(name)
   214 		self.name = name
   214         self.name = name
   215 		self.type = type
   215         self.type = type
   216 		self.clazz = clazz & _CLASS_MASK
   216         self.clazz = clazz & _CLASS_MASK
   217 		self.unique = (clazz & _CLASS_UNIQUE) != 0
   217         self.unique = (clazz & _CLASS_UNIQUE) != 0
   218 
   218 
   219 	def __eq__(self, other):
   219     def __eq__(self, other):
   220 		"""Equality test on name, type, and class"""
   220         """Equality test on name, type, and class"""
   221 		if isinstance(other, DNSEntry):
   221         if isinstance(other, DNSEntry):
   222 			return self.name == other.name and self.type == other.type and self.clazz == other.clazz
   222             return self.name == other.name and self.type == other.type and self.clazz == other.clazz
   223 		return 0
   223         return 0
   224 
   224 
   225 	def __ne__(self, other):
   225     def __ne__(self, other):
   226 		"""Non-equality test"""
   226         """Non-equality test"""
   227 		return not self.__eq__(other)
   227         return not self.__eq__(other)
   228 
   228 
   229 	def getClazz(self, clazz):
   229     def getClazz(self, clazz):
   230 		"""Class accessor"""
   230         """Class accessor"""
   231 		try:
   231         try:
   232 			return _CLASSES[clazz]
   232             return _CLASSES[clazz]
   233 		except:
   233         except:
   234 			return "?(%s)" % (clazz)
   234             return "?(%s)" % (clazz)
   235 
   235 
   236 	def getType(self, type):
   236     def getType(self, type):
   237 		"""Type accessor"""
   237         """Type accessor"""
   238 		try:
   238         try:
   239 			return _TYPES[type]
   239             return _TYPES[type]
   240 		except:
   240         except:
   241 			return "?(%s)" % (type)
   241             return "?(%s)" % (type)
   242 
   242 
   243 	def toString(self, hdr, other):
   243     def toString(self, hdr, other):
   244 		"""String representation with additional information"""
   244         """String representation with additional information"""
   245 		result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
   245         result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
   246 		if self.unique:
   246         if self.unique:
   247 			result += "-unique,"
   247             result += "-unique,"
   248 		else:
   248         else:
   249 			result += ","
   249             result += ","
   250 		result += self.name
   250         result += self.name
   251 		if other is not None:
   251         if other is not None:
   252 			result += ",%s]" % (other)
   252             result += ",%s]" % (other)
   253 		else:
   253         else:
   254 			result += "]"
   254             result += "]"
   255 		return result
   255         return result
   256 
   256 
   257 class DNSQuestion(DNSEntry):
   257 class DNSQuestion(DNSEntry):
   258 	"""A DNS question entry"""
   258     """A DNS question entry"""
   259 	
   259     
   260 	def __init__(self, name, type, clazz):
   260     def __init__(self, name, type, clazz):
   261 		if not name.endswith(".local."):
   261         if not name.endswith(".local."):
   262 			raise NonLocalNameException
   262             raise NonLocalNameException
   263 		DNSEntry.__init__(self, name, type, clazz)
   263         DNSEntry.__init__(self, name, type, clazz)
   264 
   264 
   265 	def answeredBy(self, rec):
   265     def answeredBy(self, rec):
   266 		"""Returns true if the question is answered by the record"""
   266         """Returns true if the question is answered by the record"""
   267 		return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
   267         return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
   268 
   268 
   269 	def __repr__(self):
   269     def __repr__(self):
   270 		"""String representation"""
   270         """String representation"""
   271 		return DNSEntry.toString(self, "question", None)
   271         return DNSEntry.toString(self, "question", None)
   272 
   272 
   273 
   273 
   274 class DNSRecord(DNSEntry):
   274 class DNSRecord(DNSEntry):
   275 	"""A DNS record - like a DNS entry, but has a TTL"""
   275     """A DNS record - like a DNS entry, but has a TTL"""
   276 	
   276     
   277 	def __init__(self, name, type, clazz, ttl):
   277     def __init__(self, name, type, clazz, ttl):
   278 		DNSEntry.__init__(self, name, type, clazz)
   278         DNSEntry.__init__(self, name, type, clazz)
   279 		self.ttl = ttl
   279         self.ttl = ttl
   280 		self.created = currentTimeMillis()
   280         self.created = currentTimeMillis()
   281 
   281 
   282 	def __eq__(self, other):
   282     def __eq__(self, other):
   283 		"""Tests equality as per DNSRecord"""
   283         """Tests equality as per DNSRecord"""
   284 		if isinstance(other, DNSRecord):
   284         if isinstance(other, DNSRecord):
   285 			return DNSEntry.__eq__(self, other)
   285             return DNSEntry.__eq__(self, other)
   286 		return 0
   286         return 0
   287 
   287 
   288 	def suppressedBy(self, msg):
   288     def suppressedBy(self, msg):
   289 		"""Returns true if any answer in a message can suffice for the
   289         """Returns true if any answer in a message can suffice for the
   290 		information held in this record."""
   290         information held in this record."""
   291 		for record in msg.answers:
   291         for record in msg.answers:
   292 			if self.suppressedByAnswer(record):
   292             if self.suppressedByAnswer(record):
   293 				return 1
   293                 return 1
   294 		return 0
   294         return 0
   295 
   295 
   296 	def suppressedByAnswer(self, other):
   296     def suppressedByAnswer(self, other):
   297 		"""Returns true if another record has same name, type and class,
   297         """Returns true if another record has same name, type and class,
   298 		and if its TTL is at least half of this record's."""
   298         and if its TTL is at least half of this record's."""
   299 		if self == other and other.ttl > (self.ttl / 2):
   299         if self == other and other.ttl > (self.ttl / 2):
   300 			return 1
   300             return 1
   301 		return 0
   301         return 0
   302 
   302 
   303 	def getExpirationTime(self, percent):
   303     def getExpirationTime(self, percent):
   304 		"""Returns the time at which this record will have expired
   304         """Returns the time at which this record will have expired
   305 		by a certain percentage."""
   305         by a certain percentage."""
   306 		return self.created + (percent * self.ttl * 10)
   306         return self.created + (percent * self.ttl * 10)
   307 
   307 
   308 	def getRemainingTTL(self, now):
   308     def getRemainingTTL(self, now):
   309 		"""Returns the remaining TTL in seconds."""
   309         """Returns the remaining TTL in seconds."""
   310 		return max(0, (self.getExpirationTime(100) - now) / 1000)
   310         return max(0, (self.getExpirationTime(100) - now) / 1000)
   311 
   311 
   312 	def isExpired(self, now):
   312     def isExpired(self, now):
   313 		"""Returns true if this record has expired."""
   313         """Returns true if this record has expired."""
   314 		return self.getExpirationTime(100) <= now
   314         return self.getExpirationTime(100) <= now
   315 
   315 
   316 	def isStale(self, now):
   316     def isStale(self, now):
   317 		"""Returns true if this record is at least half way expired."""
   317         """Returns true if this record is at least half way expired."""
   318 		return self.getExpirationTime(50) <= now
   318         return self.getExpirationTime(50) <= now
   319 
   319 
   320 	def resetTTL(self, other):
   320     def resetTTL(self, other):
   321 		"""Sets this record's TTL and created time to that of
   321         """Sets this record's TTL and created time to that of
   322 		another record."""
   322         another record."""
   323 		self.created = other.created
   323         self.created = other.created
   324 		self.ttl = other.ttl
   324         self.ttl = other.ttl
   325 
   325 
   326 	def write(self, out):
   326     def write(self, out):
   327 		"""Abstract method"""
   327         """Abstract method"""
   328 		raise AbstractMethodException
   328         raise AbstractMethodException
   329 
   329 
   330 	def toString(self, other):
   330     def toString(self, other):
   331 		"""String representation with addtional information"""
   331         """String representation with addtional information"""
   332 		arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
   332         arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
   333 		return DNSEntry.toString(self, "record", arg)
   333         return DNSEntry.toString(self, "record", arg)
   334 
   334 
   335 class DNSAddress(DNSRecord):
   335 class DNSAddress(DNSRecord):
   336 	"""A DNS address record"""
   336     """A DNS address record"""
   337 	
   337     
   338 	def __init__(self, name, type, clazz, ttl, address):
   338     def __init__(self, name, type, clazz, ttl, address):
   339 		DNSRecord.__init__(self, name, type, clazz, ttl)
   339         DNSRecord.__init__(self, name, type, clazz, ttl)
   340 		self.address = address
   340         self.address = address
   341 
   341 
   342 	def write(self, out):
   342     def write(self, out):
   343 		"""Used in constructing an outgoing packet"""
   343         """Used in constructing an outgoing packet"""
   344 		out.writeString(self.address, len(self.address))
   344         out.writeString(self.address, len(self.address))
   345 
   345 
   346 	def __eq__(self, other):
   346     def __eq__(self, other):
   347 		"""Tests equality on address"""
   347         """Tests equality on address"""
   348 		if isinstance(other, DNSAddress):
   348         if isinstance(other, DNSAddress):
   349 			return self.address == other.address
   349             return self.address == other.address
   350 		return 0
   350         return 0
   351 
   351 
   352 	def __repr__(self):
   352     def __repr__(self):
   353 		"""String representation"""
   353         """String representation"""
   354 		try:
   354         try:
   355 			return socket.inet_ntoa(self.address)
   355             return socket.inet_ntoa(self.address)
   356 		except:
   356         except:
   357 			return self.address
   357             return self.address
   358 
   358 
   359 class DNSHinfo(DNSRecord):
   359 class DNSHinfo(DNSRecord):
   360 	"""A DNS host information record"""
   360     """A DNS host information record"""
   361 
   361 
   362 	def __init__(self, name, type, clazz, ttl, cpu, os):
   362     def __init__(self, name, type, clazz, ttl, cpu, os):
   363 		DNSRecord.__init__(self, name, type, clazz, ttl)
   363         DNSRecord.__init__(self, name, type, clazz, ttl)
   364 		self.cpu = cpu
   364         self.cpu = cpu
   365 		self.os = os
   365         self.os = os
   366 
   366 
   367 	def write(self, out):
   367     def write(self, out):
   368 		"""Used in constructing an outgoing packet"""
   368         """Used in constructing an outgoing packet"""
   369 		out.writeString(self.cpu, len(self.cpu))
   369         out.writeString(self.cpu, len(self.cpu))
   370 		out.writeString(self.os, len(self.os))
   370         out.writeString(self.os, len(self.os))
   371 
   371 
   372 	def __eq__(self, other):
   372     def __eq__(self, other):
   373 		"""Tests equality on cpu and os"""
   373         """Tests equality on cpu and os"""
   374 		if isinstance(other, DNSHinfo):
   374         if isinstance(other, DNSHinfo):
   375 			return self.cpu == other.cpu and self.os == other.os
   375             return self.cpu == other.cpu and self.os == other.os
   376 		return 0
   376         return 0
   377 
   377 
   378 	def __repr__(self):
   378     def __repr__(self):
   379 		"""String representation"""
   379         """String representation"""
   380 		return self.cpu + " " + self.os
   380         return self.cpu + " " + self.os
   381 	
   381     
   382 class DNSPointer(DNSRecord):
   382 class DNSPointer(DNSRecord):
   383 	"""A DNS pointer record"""
   383     """A DNS pointer record"""
   384 	
   384     
   385 	def __init__(self, name, type, clazz, ttl, alias):
   385     def __init__(self, name, type, clazz, ttl, alias):
   386 		DNSRecord.__init__(self, name, type, clazz, ttl)
   386         DNSRecord.__init__(self, name, type, clazz, ttl)
   387 		self.alias = alias
   387         self.alias = alias
   388 
   388 
   389 	def write(self, out):
   389     def write(self, out):
   390 		"""Used in constructing an outgoing packet"""
   390         """Used in constructing an outgoing packet"""
   391 		out.writeName(self.alias)
   391         out.writeName(self.alias)
   392 
   392 
   393 	def __eq__(self, other):
   393     def __eq__(self, other):
   394 		"""Tests equality on alias"""
   394         """Tests equality on alias"""
   395 		if isinstance(other, DNSPointer):
   395         if isinstance(other, DNSPointer):
   396 			return self.alias == other.alias
   396             return self.alias == other.alias
   397 		return 0
   397         return 0
   398 
   398 
   399 	def __repr__(self):
   399     def __repr__(self):
   400 		"""String representation"""
   400         """String representation"""
   401 		return self.toString(self.alias)
   401         return self.toString(self.alias)
   402 
   402 
   403 class DNSText(DNSRecord):
   403 class DNSText(DNSRecord):
   404 	"""A DNS text record"""
   404     """A DNS text record"""
   405 	
   405     
   406 	def __init__(self, name, type, clazz, ttl, text):
   406     def __init__(self, name, type, clazz, ttl, text):
   407 		DNSRecord.__init__(self, name, type, clazz, ttl)
   407         DNSRecord.__init__(self, name, type, clazz, ttl)
   408 		self.text = text
   408         self.text = text
   409 
   409 
   410 	def write(self, out):
   410     def write(self, out):
   411 		"""Used in constructing an outgoing packet"""
   411         """Used in constructing an outgoing packet"""
   412 		out.writeString(self.text, len(self.text))
   412         out.writeString(self.text, len(self.text))
   413 
   413 
   414 	def __eq__(self, other):
   414     def __eq__(self, other):
   415 		"""Tests equality on text"""
   415         """Tests equality on text"""
   416 		if isinstance(other, DNSText):
   416         if isinstance(other, DNSText):
   417 			return self.text == other.text
   417             return self.text == other.text
   418 		return 0
   418         return 0
   419 
   419 
   420 	def __repr__(self):
   420     def __repr__(self):
   421 		"""String representation"""
   421         """String representation"""
   422 		if len(self.text) > 10:
   422         if len(self.text) > 10:
   423 			return self.toString(self.text[:7] + "...")
   423             return self.toString(self.text[:7] + "...")
   424 		else:
   424         else:
   425 			return self.toString(self.text)
   425             return self.toString(self.text)
   426 
   426 
   427 class DNSService(DNSRecord):
   427 class DNSService(DNSRecord):
   428 	"""A DNS service record"""
   428     """A DNS service record"""
   429 	
   429     
   430 	def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
   430     def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
   431 		DNSRecord.__init__(self, name, type, clazz, ttl)
   431         DNSRecord.__init__(self, name, type, clazz, ttl)
   432 		self.priority = priority
   432         self.priority = priority
   433 		self.weight = weight
   433         self.weight = weight
   434 		self.port = port
   434         self.port = port
   435 		self.server = server
   435         self.server = server
   436 
   436 
   437 	def write(self, out):
   437     def write(self, out):
   438 		"""Used in constructing an outgoing packet"""
   438         """Used in constructing an outgoing packet"""
   439 		out.writeShort(self.priority)
   439         out.writeShort(self.priority)
   440 		out.writeShort(self.weight)
   440         out.writeShort(self.weight)
   441 		out.writeShort(self.port)
   441         out.writeShort(self.port)
   442 		out.writeName(self.server)
   442         out.writeName(self.server)
   443 
   443 
   444 	def __eq__(self, other):
   444     def __eq__(self, other):
   445 		"""Tests equality on priority, weight, port and server"""
   445         """Tests equality on priority, weight, port and server"""
   446 		if isinstance(other, DNSService):
   446         if isinstance(other, DNSService):
   447 			return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
   447             return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
   448 		return 0
   448         return 0
   449 
   449 
   450 	def __repr__(self):
   450     def __repr__(self):
   451 		"""String representation"""
   451         """String representation"""
   452 		return self.toString("%s:%s" % (self.server, self.port))
   452         return self.toString("%s:%s" % (self.server, self.port))
   453 
   453 
   454 class DNSIncoming(object):
   454 class DNSIncoming(object):
   455 	"""Object representation of an incoming DNS packet"""
   455     """Object representation of an incoming DNS packet"""
   456 	
   456     
   457 	def __init__(self, data):
   457     def __init__(self, data):
   458 		"""Constructor from string holding bytes of packet"""
   458         """Constructor from string holding bytes of packet"""
   459 		self.offset = 0
   459         self.offset = 0
   460 		self.data = data
   460         self.data = data
   461 		self.questions = []
   461         self.questions = []
   462 		self.answers = []
   462         self.answers = []
   463 		self.numQuestions = 0
   463         self.numQuestions = 0
   464 		self.numAnswers = 0
   464         self.numAnswers = 0
   465 		self.numAuthorities = 0
   465         self.numAuthorities = 0
   466 		self.numAdditionals = 0
   466         self.numAdditionals = 0
   467 		
   467         
   468 		self.readHeader()
   468         self.readHeader()
   469 		self.readQuestions()
   469         self.readQuestions()
   470 		self.readOthers()
   470         self.readOthers()
   471 
   471 
   472 	def readHeader(self):
   472     def readHeader(self):
   473 		"""Reads header portion of packet"""
   473         """Reads header portion of packet"""
   474 		format = '!HHHHHH'
   474         format = '!HHHHHH'
   475 		length = struct.calcsize(format)
   475         length = struct.calcsize(format)
   476 		info = struct.unpack(format, self.data[self.offset:self.offset+length])
   476         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   477 		self.offset += length
   477         self.offset += length
   478 
   478 
   479 		self.id = info[0]
   479         self.id = info[0]
   480 		self.flags = info[1]
   480         self.flags = info[1]
   481 		self.numQuestions = info[2]
   481         self.numQuestions = info[2]
   482 		self.numAnswers = info[3]
   482         self.numAnswers = info[3]
   483 		self.numAuthorities = info[4]
   483         self.numAuthorities = info[4]
   484 		self.numAdditionals = info[5]
   484         self.numAdditionals = info[5]
   485 
   485 
   486 	def readQuestions(self):
   486     def readQuestions(self):
   487 		"""Reads questions section of packet"""
   487         """Reads questions section of packet"""
   488 		format = '!HH'
   488         format = '!HH'
   489 		length = struct.calcsize(format)
   489         length = struct.calcsize(format)
   490 		for i in range(0, self.numQuestions):
   490         for i in range(0, self.numQuestions):
   491 			name = self.readName()
   491             name = self.readName()
   492 			info = struct.unpack(format, self.data[self.offset:self.offset+length])
   492             info = struct.unpack(format, self.data[self.offset:self.offset+length])
   493 			self.offset += length
   493             self.offset += length
   494 			
   494             
   495 			question = DNSQuestion(name, info[0], info[1])
   495             question = DNSQuestion(name, info[0], info[1])
   496 			self.questions.append(question)
   496             self.questions.append(question)
   497 
   497 
   498 	def readInt(self):
   498     def readInt(self):
   499 		"""Reads an integer from the packet"""
   499         """Reads an integer from the packet"""
   500 		format = '!I'
   500         format = '!I'
   501 		length = struct.calcsize(format)
   501         length = struct.calcsize(format)
   502 		info = struct.unpack(format, self.data[self.offset:self.offset+length])
   502         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   503 		self.offset += length
   503         self.offset += length
   504 		return info[0]
   504         return info[0]
   505 
   505 
   506 	def readCharacterString(self):
   506     def readCharacterString(self):
   507 		"""Reads a character string from the packet"""
   507         """Reads a character string from the packet"""
   508 		length = ord(self.data[self.offset])
   508         length = ord(self.data[self.offset])
   509 		self.offset += 1
   509         self.offset += 1
   510 		return self.readString(length)
   510         return self.readString(length)
   511 
   511 
   512 	def readString(self, len):
   512     def readString(self, len):
   513 		"""Reads a string of a given length from the packet"""
   513         """Reads a string of a given length from the packet"""
   514 		format = '!' + str(len) + 's'
   514         format = '!' + str(len) + 's'
   515 		length =  struct.calcsize(format)
   515         length =  struct.calcsize(format)
   516 		info = struct.unpack(format, self.data[self.offset:self.offset+length])
   516         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   517 		self.offset += length
   517         self.offset += length
   518 		return info[0]
   518         return info[0]
   519 
   519 
   520 	def readUnsignedShort(self):
   520     def readUnsignedShort(self):
   521 		"""Reads an unsigned short from the packet"""
   521         """Reads an unsigned short from the packet"""
   522 		format = '!H'
   522         format = '!H'
   523 		length = struct.calcsize(format)
   523         length = struct.calcsize(format)
   524 		info = struct.unpack(format, self.data[self.offset:self.offset+length])
   524         info = struct.unpack(format, self.data[self.offset:self.offset+length])
   525 		self.offset += length
   525         self.offset += length
   526 		return info[0]
   526         return info[0]
   527 
   527 
   528 	def readOthers(self):
   528     def readOthers(self):
   529 		"""Reads the answers, authorities and additionals section of the packet"""
   529         """Reads the answers, authorities and additionals section of the packet"""
   530 		format = '!HHiH'
   530         format = '!HHiH'
   531 		length = struct.calcsize(format)
   531         length = struct.calcsize(format)
   532 		n = self.numAnswers + self.numAuthorities + self.numAdditionals
   532         n = self.numAnswers + self.numAuthorities + self.numAdditionals
   533 		for i in range(0, n):
   533         for i in range(0, n):
   534 			domain = self.readName()
   534             domain = self.readName()
   535 			info = struct.unpack(format, self.data[self.offset:self.offset+length])
   535             info = struct.unpack(format, self.data[self.offset:self.offset+length])
   536 			self.offset += length
   536             self.offset += length
   537 
   537 
   538 			rec = None
   538             rec = None
   539 			if info[0] == _TYPE_A:
   539             if info[0] == _TYPE_A:
   540 				rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
   540                 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
   541 			elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
   541             elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
   542 				rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
   542                 rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
   543 			elif info[0] == _TYPE_TXT:
   543             elif info[0] == _TYPE_TXT:
   544 				rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
   544                 rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
   545 			elif info[0] == _TYPE_SRV:
   545             elif info[0] == _TYPE_SRV:
   546 				rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
   546                 rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
   547 			elif info[0] == _TYPE_HINFO:
   547             elif info[0] == _TYPE_HINFO:
   548 				rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
   548                 rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
   549 			elif info[0] == _TYPE_AAAA:
   549             elif info[0] == _TYPE_AAAA:
   550 				rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
   550                 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
   551 			else:
   551             else:
   552 				# Try to ignore types we don't know about
   552                 # Try to ignore types we don't know about
   553 				# this may mean the rest of the name is
   553                 # this may mean the rest of the name is
   554 				# unable to be parsed, and may show errors
   554                 # unable to be parsed, and may show errors
   555 				# so this is left for debugging.  New types
   555                 # so this is left for debugging.  New types
   556 				# encountered need to be parsed properly.
   556                 # encountered need to be parsed properly.
   557 				#
   557                 #
   558 				#print "UNKNOWN TYPE = " + str(info[0])
   558                 #print "UNKNOWN TYPE = " + str(info[0])
   559 				#raise BadTypeInNameException
   559                 #raise BadTypeInNameException
   560 				pass
   560                 pass
   561 
   561 
   562 			if rec is not None:
   562             if rec is not None:
   563 				self.answers.append(rec)
   563                 self.answers.append(rec)
   564 				
   564                 
   565 	def isQuery(self):
   565     def isQuery(self):
   566 		"""Returns true if this is a query"""
   566         """Returns true if this is a query"""
   567 		return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
   567         return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
   568 
   568 
   569 	def isResponse(self):
   569     def isResponse(self):
   570 		"""Returns true if this is a response"""
   570         """Returns true if this is a response"""
   571 		return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
   571         return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
   572 
   572 
   573 	def readUTF(self, offset, len):
   573     def readUTF(self, offset, len):
   574 		"""Reads a UTF-8 string of a given length from the packet"""
   574         """Reads a UTF-8 string of a given length from the packet"""
   575 		result = self.data[offset:offset+len].decode('utf-8')
   575         result = self.data[offset:offset+len].decode('utf-8')
   576 		return result
   576         return result
   577 		
   577         
   578 	def readName(self):
   578     def readName(self):
   579 		"""Reads a domain name from the packet"""
   579         """Reads a domain name from the packet"""
   580 		result = ''
   580         result = ''
   581 		off = self.offset
   581         off = self.offset
   582 		next = -1
   582         next = -1
   583 		first = off
   583         first = off
   584 
   584 
   585 		while 1:
   585         while 1:
   586 			len = ord(self.data[off])
   586             len = ord(self.data[off])
   587 			off += 1
   587             off += 1
   588 			if len == 0:
   588             if len == 0:
   589 				break
   589                 break
   590 			t = len & 0xC0
   590             t = len & 0xC0
   591 			if t == 0x00:
   591             if t == 0x00:
   592 				result = ''.join((result, self.readUTF(off, len) + '.'))
   592                 result = ''.join((result, self.readUTF(off, len) + '.'))
   593 				off += len
   593                 off += len
   594 			elif t == 0xC0:
   594             elif t == 0xC0:
   595 				if next < 0:
   595                 if next < 0:
   596 					next = off + 1
   596                     next = off + 1
   597 				off = ((len & 0x3F) << 8) | ord(self.data[off])
   597                 off = ((len & 0x3F) << 8) | ord(self.data[off])
   598 				if off >= first:
   598                 if off >= first:
   599 					raise "Bad domain name (circular) at " + str(off)
   599                     raise "Bad domain name (circular) at " + str(off)
   600 				first = off
   600                 first = off
   601 			else:
   601             else:
   602 				raise "Bad domain name at " + str(off)
   602                 raise "Bad domain name at " + str(off)
   603 
   603 
   604 		if next >= 0:
   604         if next >= 0:
   605 			self.offset = next
   605             self.offset = next
   606 		else:
   606         else:
   607 			self.offset = off
   607             self.offset = off
   608 
   608 
   609 		return result
   609         return result
   610 	
   610     
   611 		
   611         
   612 class DNSOutgoing(object):
   612 class DNSOutgoing(object):
   613 	"""Object representation of an outgoing packet"""
   613     """Object representation of an outgoing packet"""
   614 	
   614     
   615 	def __init__(self, flags, multicast = 1):
   615     def __init__(self, flags, multicast = 1):
   616 		self.finished = 0
   616         self.finished = 0
   617 		self.id = 0
   617         self.id = 0
   618 		self.multicast = multicast
   618         self.multicast = multicast
   619 		self.flags = flags
   619         self.flags = flags
   620 		self.names = {}
   620         self.names = {}
   621 		self.data = []
   621         self.data = []
   622 		self.size = 12
   622         self.size = 12
   623 		
   623         
   624 		self.questions = []
   624         self.questions = []
   625 		self.answers = []
   625         self.answers = []
   626 		self.authorities = []
   626         self.authorities = []
   627 		self.additionals = []
   627         self.additionals = []
   628 
   628 
   629 	def addQuestion(self, record):
   629     def addQuestion(self, record):
   630 		"""Adds a question"""
   630         """Adds a question"""
   631 		self.questions.append(record)
   631         self.questions.append(record)
   632 
   632 
   633 	def addAnswer(self, inp, record):
   633     def addAnswer(self, inp, record):
   634 		"""Adds an answer"""
   634         """Adds an answer"""
   635 		if not record.suppressedBy(inp):
   635         if not record.suppressedBy(inp):
   636 			self.addAnswerAtTime(record, 0)
   636             self.addAnswerAtTime(record, 0)
   637 
   637 
   638 	def addAnswerAtTime(self, record, now):
   638     def addAnswerAtTime(self, record, now):
   639 		"""Adds an answer if if does not expire by a certain time"""
   639         """Adds an answer if if does not expire by a certain time"""
   640 		if record is not None:
   640         if record is not None:
   641 			if now == 0 or not record.isExpired(now):
   641             if now == 0 or not record.isExpired(now):
   642 				self.answers.append((record, now))
   642                 self.answers.append((record, now))
   643 
   643 
   644 	def addAuthorativeAnswer(self, record):
   644     def addAuthorativeAnswer(self, record):
   645 		"""Adds an authoritative answer"""
   645         """Adds an authoritative answer"""
   646 		self.authorities.append(record)
   646         self.authorities.append(record)
   647 
   647 
   648 	def addAdditionalAnswer(self, record):
   648     def addAdditionalAnswer(self, record):
   649 		"""Adds an additional answer"""
   649         """Adds an additional answer"""
   650 		self.additionals.append(record)
   650         self.additionals.append(record)
   651 
   651 
   652 	def writeByte(self, value):
   652     def writeByte(self, value):
   653 		"""Writes a single byte to the packet"""
   653         """Writes a single byte to the packet"""
   654 		format = '!c'
   654         format = '!c'
   655 		self.data.append(struct.pack(format, chr(value)))
   655         self.data.append(struct.pack(format, chr(value)))
   656 		self.size += 1
   656         self.size += 1
   657 
   657 
   658 	def insertShort(self, index, value):
   658     def insertShort(self, index, value):
   659 		"""Inserts an unsigned short in a certain position in the packet"""
   659         """Inserts an unsigned short in a certain position in the packet"""
   660 		format = '!H'
   660         format = '!H'
   661 		self.data.insert(index, struct.pack(format, value))
   661         self.data.insert(index, struct.pack(format, value))
   662 		self.size += 2
   662         self.size += 2
   663 		
   663         
   664 	def writeShort(self, value):
   664     def writeShort(self, value):
   665 		"""Writes an unsigned short to the packet"""
   665         """Writes an unsigned short to the packet"""
   666 		format = '!H'
   666         format = '!H'
   667 		self.data.append(struct.pack(format, value))
   667         self.data.append(struct.pack(format, value))
   668 		self.size += 2
   668         self.size += 2
   669 
   669 
   670 	def writeInt(self, value):
   670     def writeInt(self, value):
   671 		"""Writes an unsigned integer to the packet"""
   671         """Writes an unsigned integer to the packet"""
   672 		format = '!I'
   672         format = '!I'
   673 		self.data.append(struct.pack(format, int(value)))
   673         self.data.append(struct.pack(format, int(value)))
   674 		self.size += 4
   674         self.size += 4
   675 
   675 
   676 	def writeString(self, value, length):
   676     def writeString(self, value, length):
   677 		"""Writes a string to the packet"""
   677         """Writes a string to the packet"""
   678 		format = '!' + str(length) + 's'
   678         format = '!' + str(length) + 's'
   679 		self.data.append(struct.pack(format, value))
   679         self.data.append(struct.pack(format, value))
   680 		self.size += length
   680         self.size += length
   681 
   681 
   682 	def writeUTF(self, s):
   682     def writeUTF(self, s):
   683 		"""Writes a UTF-8 string of a given length to the packet"""
   683         """Writes a UTF-8 string of a given length to the packet"""
   684 		utfstr = s.encode('utf-8')
   684         utfstr = s.encode('utf-8')
   685 		length = len(utfstr)
   685         length = len(utfstr)
   686 		if length > 64:
   686         if length > 64:
   687 			raise NamePartTooLongException
   687             raise NamePartTooLongException
   688 		self.writeByte(length)
   688         self.writeByte(length)
   689 		self.writeString(utfstr, length)
   689         self.writeString(utfstr, length)
   690 
   690 
   691 	def writeName(self, name):
   691     def writeName(self, name):
   692 		"""Writes a domain name to the packet"""
   692         """Writes a domain name to the packet"""
   693 
   693 
   694 		try:
   694         try:
   695 			# Find existing instance of this name in packet
   695             # Find existing instance of this name in packet
   696 			#
   696             #
   697 			index = self.names[name]
   697             index = self.names[name]
   698 		except KeyError:
   698         except KeyError:
   699 			# No record of this name already, so write it
   699             # No record of this name already, so write it
   700 			# out as normal, recording the location of the name
   700             # out as normal, recording the location of the name
   701 			# for future pointers to it.
   701             # for future pointers to it.
   702 			#
   702             #
   703 			self.names[name] = self.size
   703             self.names[name] = self.size
   704 			parts = name.split('.')
   704             parts = name.split('.')
   705 			if parts[-1] == '':
   705             if parts[-1] == '':
   706 				parts = parts[:-1]
   706                 parts = parts[:-1]
   707 			for part in parts:
   707             for part in parts:
   708 				self.writeUTF(part)
   708                 self.writeUTF(part)
   709 			self.writeByte(0)
   709             self.writeByte(0)
   710 			return
   710             return
   711 
   711 
   712 		# An index was found, so write a pointer to it
   712         # An index was found, so write a pointer to it
   713 		#
   713         #
   714 		self.writeByte((index >> 8) | 0xC0)
   714         self.writeByte((index >> 8) | 0xC0)
   715 		self.writeByte(index)
   715         self.writeByte(index)
   716 
   716 
   717 	def writeQuestion(self, question):
   717     def writeQuestion(self, question):
   718 		"""Writes a question to the packet"""
   718         """Writes a question to the packet"""
   719 		self.writeName(question.name)
   719         self.writeName(question.name)
   720 		self.writeShort(question.type)
   720         self.writeShort(question.type)
   721 		self.writeShort(question.clazz)
   721         self.writeShort(question.clazz)
   722 
   722 
   723 	def writeRecord(self, record, now):
   723     def writeRecord(self, record, now):
   724 		"""Writes a record (answer, authoritative answer, additional) to
   724         """Writes a record (answer, authoritative answer, additional) to
   725 		the packet"""
   725         the packet"""
   726 		self.writeName(record.name)
   726         self.writeName(record.name)
   727 		self.writeShort(record.type)
   727         self.writeShort(record.type)
   728 		if record.unique and self.multicast:
   728         if record.unique and self.multicast:
   729 			self.writeShort(record.clazz | _CLASS_UNIQUE)
   729             self.writeShort(record.clazz | _CLASS_UNIQUE)
   730 		else:
   730         else:
   731 			self.writeShort(record.clazz)
   731             self.writeShort(record.clazz)
   732 		if now == 0:
   732         if now == 0:
   733 			self.writeInt(record.ttl)
   733             self.writeInt(record.ttl)
   734 		else:
   734         else:
   735 			self.writeInt(record.getRemainingTTL(now))
   735             self.writeInt(record.getRemainingTTL(now))
   736 		index = len(self.data)
   736         index = len(self.data)
   737 		# Adjust size for the short we will write before this record
   737         # Adjust size for the short we will write before this record
   738 		#
   738         #
   739 		self.size += 2
   739         self.size += 2
   740 		record.write(self)
   740         record.write(self)
   741 		self.size -= 2
   741         self.size -= 2
   742 		
   742         
   743 		length = len(''.join(self.data[index:]))
   743         length = len(''.join(self.data[index:]))
   744 		self.insertShort(index, length) # Here is the short we adjusted for
   744         self.insertShort(index, length) # Here is the short we adjusted for
   745 
   745 
   746 	def packet(self):
   746     def packet(self):
   747 		"""Returns a string containing the packet's bytes
   747         """Returns a string containing the packet's bytes
   748 
   748 
   749 		No further parts should be added to the packet once this
   749         No further parts should be added to the packet once this
   750 		is done."""
   750         is done."""
   751 		if not self.finished:
   751         if not self.finished:
   752 			self.finished = 1
   752             self.finished = 1
   753 			for question in self.questions:
   753             for question in self.questions:
   754 				self.writeQuestion(question)
   754                 self.writeQuestion(question)
   755 			for answer, time in self.answers:
   755             for answer, time in self.answers:
   756 				self.writeRecord(answer, time)
   756                 self.writeRecord(answer, time)
   757 			for authority in self.authorities:
   757             for authority in self.authorities:
   758 				self.writeRecord(authority, 0)
   758                 self.writeRecord(authority, 0)
   759 			for additional in self.additionals:
   759             for additional in self.additionals:
   760 				self.writeRecord(additional, 0)
   760                 self.writeRecord(additional, 0)
   761 		
   761         
   762 			self.insertShort(0, len(self.additionals))
   762             self.insertShort(0, len(self.additionals))
   763 			self.insertShort(0, len(self.authorities))
   763             self.insertShort(0, len(self.authorities))
   764 			self.insertShort(0, len(self.answers))
   764             self.insertShort(0, len(self.answers))
   765 			self.insertShort(0, len(self.questions))
   765             self.insertShort(0, len(self.questions))
   766 			self.insertShort(0, self.flags)
   766             self.insertShort(0, self.flags)
   767 			if self.multicast:
   767             if self.multicast:
   768 				self.insertShort(0, 0)
   768                 self.insertShort(0, 0)
   769 			else:
   769             else:
   770 				self.insertShort(0, self.id)
   770                 self.insertShort(0, self.id)
   771 		return ''.join(self.data)
   771         return ''.join(self.data)
   772 
   772 
   773 
   773 
   774 class DNSCache(object):
   774 class DNSCache(object):
   775 	"""A cache of DNS entries"""
   775     """A cache of DNS entries"""
   776 	
   776     
   777 	def __init__(self):
   777     def __init__(self):
   778 		self.cache = {}
   778         self.cache = {}
   779 
   779 
   780 	def add(self, entry):
   780     def add(self, entry):
   781 		"""Adds an entry"""
   781         """Adds an entry"""
   782 		try:
   782         try:
   783 			list = self.cache[entry.key]
   783             list = self.cache[entry.key]
   784 		except:
   784         except:
   785 			list = self.cache[entry.key] = []
   785             list = self.cache[entry.key] = []
   786 		list.append(entry)
   786         list.append(entry)
   787 
   787 
   788 	def remove(self, entry):
   788     def remove(self, entry):
   789 		"""Removes an entry"""
   789         """Removes an entry"""
   790 		try:
   790         try:
   791 			list = self.cache[entry.key]
   791             list = self.cache[entry.key]
   792 			list.remove(entry)
   792             list.remove(entry)
   793 		except:
   793         except:
   794 			pass
   794             pass
   795 
   795 
   796 	def get(self, entry):
   796     def get(self, entry):
   797 		"""Gets an entry by key.  Will return None if there is no
   797         """Gets an entry by key.  Will return None if there is no
   798 		matching entry."""
   798         matching entry."""
   799 		try:
   799         try:
   800 			list = self.cache[entry.key]
   800             list = self.cache[entry.key]
   801 			return list[list.index(entry)]
   801             return list[list.index(entry)]
   802 		except:
   802         except:
   803 			return None
   803             return None
   804 
   804 
   805 	def getByDetails(self, name, type, clazz):
   805     def getByDetails(self, name, type, clazz):
   806 		"""Gets an entry by details.  Will return None if there is
   806         """Gets an entry by details.  Will return None if there is
   807 		no matching entry."""
   807         no matching entry."""
   808 		entry = DNSEntry(name, type, clazz)
   808         entry = DNSEntry(name, type, clazz)
   809 		return self.get(entry)
   809         return self.get(entry)
   810 
   810 
   811 	def entriesWithName(self, name):
   811     def entriesWithName(self, name):
   812 		"""Returns a list of entries whose key matches the name."""
   812         """Returns a list of entries whose key matches the name."""
   813 		try:
   813         try:
   814 			return self.cache[name]
   814             return self.cache[name]
   815 		except:
   815         except:
   816 			return []
   816             return []
   817 
   817 
   818 	def entries(self):
   818     def entries(self):
   819 		"""Returns a list of all entries"""
   819         """Returns a list of all entries"""
   820 		def add(x, y): return x+y
   820         def add(x, y): return x+y
   821 		try:
   821         try:
   822 			return reduce(add, self.cache.values())
   822             return reduce(add, self.cache.values())
   823 		except:
   823         except:
   824 			return []
   824             return []
   825 
   825 
   826 
   826 
   827 class Engine(threading.Thread):
   827 class Engine(threading.Thread):
   828 	"""An engine wraps read access to sockets, allowing objects that
   828     """An engine wraps read access to sockets, allowing objects that
   829 	need to receive data from sockets to be called back when the
   829     need to receive data from sockets to be called back when the
   830 	sockets are ready.
   830     sockets are ready.
   831 
   831 
   832 	A reader needs a handle_read() method, which is called when the socket
   832     A reader needs a handle_read() method, which is called when the socket
   833 	it is interested in is ready for reading.
   833     it is interested in is ready for reading.
   834 
   834 
   835 	Writers are not implemented here, because we only send short
   835     Writers are not implemented here, because we only send short
   836 	packets.
   836     packets.
   837 	"""
   837     """
   838 
   838 
   839 	def __init__(self, zeroconf):
   839     def __init__(self, zeroconf):
   840 		threading.Thread.__init__(self)
   840         threading.Thread.__init__(self)
   841 		self.zeroconf = zeroconf
   841         self.zeroconf = zeroconf
   842 		self.readers = {} # maps socket to reader
   842         self.readers = {} # maps socket to reader
   843 		self.timeout = 5
   843         self.timeout = 5
   844 		self.condition = threading.Condition()
   844         self.condition = threading.Condition()
   845 		self.start()
   845         self.start()
   846 
   846 
   847 	def run(self):
   847     def run(self):
   848 		while not globals()['_GLOBAL_DONE']:
   848         while not globals()['_GLOBAL_DONE']:
   849 			rs = self.getReaders()
   849             rs = self.getReaders()
   850 			if len(rs) == 0:
   850             if len(rs) == 0:
   851 				# No sockets to manage, but we wait for the timeout
   851                 # No sockets to manage, but we wait for the timeout
   852 				# or addition of a socket
   852                 # or addition of a socket
   853 				#
   853                 #
   854 				self.condition.acquire()
   854                 self.condition.acquire()
   855 				self.condition.wait(self.timeout)
   855                 self.condition.wait(self.timeout)
   856 				self.condition.release()
   856                 self.condition.release()
   857 			else:
   857             else:
   858 				try:
   858                 try:
   859 					rr, wr, er = select.select(rs, [], [], self.timeout)
   859                     rr, wr, er = select.select(rs, [], [], self.timeout)
   860 					for socket in rr:
   860                     for socket in rr:
   861 						try:
   861                         try:
   862 							self.readers[socket].handle_read()
   862                             self.readers[socket].handle_read()
   863 						except:
   863                         except:
   864 							traceback.print_exc()
   864                             traceback.print_exc()
   865 				except:
   865                 except:
   866 					pass
   866                     pass
   867 
   867 
   868 	def getReaders(self):
   868     def getReaders(self):
   869 		result = []
   869         result = []
   870 		self.condition.acquire()
   870         self.condition.acquire()
   871 		result = self.readers.keys()
   871         result = self.readers.keys()
   872 		self.condition.release()
   872         self.condition.release()
   873 		return result
   873         return result
   874 	
   874     
   875 	def addReader(self, reader, socket):
   875     def addReader(self, reader, socket):
   876 		self.condition.acquire()
   876         self.condition.acquire()
   877 		self.readers[socket] = reader
   877         self.readers[socket] = reader
   878 		self.condition.notify()
   878         self.condition.notify()
   879 		self.condition.release()
   879         self.condition.release()
   880 
   880 
   881 	def delReader(self, socket):
   881     def delReader(self, socket):
   882 		self.condition.acquire()
   882         self.condition.acquire()
   883 		del(self.readers[socket])
   883         del(self.readers[socket])
   884 		self.condition.notify()
   884         self.condition.notify()
   885 		self.condition.release()
   885         self.condition.release()
   886 
   886 
   887 	def notify(self):
   887     def notify(self):
   888 		self.condition.acquire()
   888         self.condition.acquire()
   889 		self.condition.notify()
   889         self.condition.notify()
   890 		self.condition.release()
   890         self.condition.release()
   891 
   891 
   892 class Listener(object):
   892 class Listener(object):
   893 	"""A Listener is used by this module to listen on the multicast
   893     """A Listener is used by this module to listen on the multicast
   894 	group to which DNS messages are sent, allowing the implementation
   894     group to which DNS messages are sent, allowing the implementation
   895 	to cache information as it arrives.
   895     to cache information as it arrives.
   896 
   896 
   897 	It requires registration with an Engine object in order to have
   897     It requires registration with an Engine object in order to have
   898 	the read() method called when a socket is availble for reading."""
   898     the read() method called when a socket is availble for reading."""
   899 	
   899     
   900 	def __init__(self, zeroconf):
   900     def __init__(self, zeroconf):
   901 		self.zeroconf = zeroconf
   901         self.zeroconf = zeroconf
   902 		self.zeroconf.engine.addReader(self, self.zeroconf.socket)
   902         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
   903 
   903 
   904 	def handle_read(self):
   904     def handle_read(self):
   905 		data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
   905         data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
   906 		self.data = data
   906         self.data = data
   907 		msg = DNSIncoming(data)
   907         msg = DNSIncoming(data)
   908 		if msg.isQuery():
   908         if msg.isQuery():
   909 			# Always multicast responses
   909             # Always multicast responses
   910 			#
   910             #
   911 			if port == _MDNS_PORT:
   911             if port == _MDNS_PORT:
   912 				self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
   912                 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
   913 			# If it's not a multicast query, reply via unicast
   913             # If it's not a multicast query, reply via unicast
   914 			# and multicast
   914             # and multicast
   915 			#
   915             #
   916 			elif port == _DNS_PORT:
   916             elif port == _DNS_PORT:
   917 				self.zeroconf.handleQuery(msg, addr, port)
   917                 self.zeroconf.handleQuery(msg, addr, port)
   918 				self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
   918                 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
   919 		else:
   919         else:
   920 			self.zeroconf.handleResponse(msg)
   920             self.zeroconf.handleResponse(msg)
   921 
   921 
   922 
   922 
   923 class Reaper(threading.Thread):
   923 class Reaper(threading.Thread):
   924 	"""A Reaper is used by this module to remove cache entries that
   924     """A Reaper is used by this module to remove cache entries that
   925 	have expired."""
   925     have expired."""
   926 	
   926     
   927 	def __init__(self, zeroconf):
   927     def __init__(self, zeroconf):
   928 		threading.Thread.__init__(self)
   928         threading.Thread.__init__(self)
   929 		self.zeroconf = zeroconf
   929         self.zeroconf = zeroconf
   930 		self.start()
   930         self.start()
   931 
   931 
   932 	def run(self):
   932     def run(self):
   933 		while 1:
   933         while 1:
   934 			self.zeroconf.wait(10 * 1000)
   934             self.zeroconf.wait(10 * 1000)
   935 			if globals()['_GLOBAL_DONE']:
   935             if globals()['_GLOBAL_DONE']:
   936 				return
   936                 return
   937 			now = currentTimeMillis()
   937             now = currentTimeMillis()
   938 			for record in self.zeroconf.cache.entries():
   938             for record in self.zeroconf.cache.entries():
   939 				if record.isExpired(now):
   939                 if record.isExpired(now):
   940 					self.zeroconf.updateRecord(now, record)
   940                     self.zeroconf.updateRecord(now, record)
   941 					self.zeroconf.cache.remove(record)
   941                     self.zeroconf.cache.remove(record)
   942 
   942 
   943 
   943 
   944 class ServiceBrowser(threading.Thread):
   944 class ServiceBrowser(threading.Thread):
   945 	"""Used to browse for a service of a specific type.
   945     """Used to browse for a service of a specific type.
   946 
   946 
   947 	The listener object will have its addService() and
   947     The listener object will have its addService() and
   948 	removeService() methods called when this browser
   948     removeService() methods called when this browser
   949 	discovers changes in the services availability."""
   949     discovers changes in the services availability."""
   950 	
   950     
   951 	def __init__(self, zeroconf, type, listener):
   951     def __init__(self, zeroconf, type, listener):
   952 		"""Creates a browser for a specific type"""
   952         """Creates a browser for a specific type"""
   953 		threading.Thread.__init__(self)
   953         threading.Thread.__init__(self)
   954 		self.zeroconf = zeroconf
   954         self.zeroconf = zeroconf
   955 		self.type = type
   955         self.type = type
   956 		self.listener = listener
   956         self.listener = listener
   957 		self.services = {}
   957         self.services = {}
   958 		self.nextTime = currentTimeMillis()
   958         self.nextTime = currentTimeMillis()
   959 		self.delay = _BROWSER_TIME
   959         self.delay = _BROWSER_TIME
   960 		self.list = []
   960         self.list = []
   961 		
   961         
   962 		self.done = 0
   962         self.done = 0
   963 
   963 
   964 		self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
   964         self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
   965 		self.start()
   965         self.start()
   966 
   966 
   967 	def updateRecord(self, zeroconf, now, record):
   967     def updateRecord(self, zeroconf, now, record):
   968 		"""Callback invoked by Zeroconf when new information arrives.
   968         """Callback invoked by Zeroconf when new information arrives.
   969 
   969 
   970 		Updates information required by browser in the Zeroconf cache."""
   970         Updates information required by browser in the Zeroconf cache."""
   971 		if record.type == _TYPE_PTR and record.name == self.type:
   971         if record.type == _TYPE_PTR and record.name == self.type:
   972 			expired = record.isExpired(now)
   972             expired = record.isExpired(now)
   973 			try:
   973             try:
   974 				oldrecord = self.services[record.alias.lower()]
   974                 oldrecord = self.services[record.alias.lower()]
   975 				if not expired:
   975                 if not expired:
   976 					oldrecord.resetTTL(record)
   976                     oldrecord.resetTTL(record)
   977 				else:
   977                 else:
   978 					del(self.services[record.alias.lower()])
   978                     del(self.services[record.alias.lower()])
   979 					callback = lambda x: self.listener.removeService(x, self.type, record.alias)
   979                     callback = lambda x: self.listener.removeService(x, self.type, record.alias)
   980 					self.list.append(callback)
   980                     self.list.append(callback)
   981 					return
   981                     return
   982 			except:
   982             except:
   983 				if not expired:
   983                 if not expired:
   984 					self.services[record.alias.lower()] = record
   984                     self.services[record.alias.lower()] = record
   985 					callback = lambda x: self.listener.addService(x, self.type, record.alias)
   985                     callback = lambda x: self.listener.addService(x, self.type, record.alias)
   986 					self.list.append(callback)
   986                     self.list.append(callback)
   987 
   987 
   988 			expires = record.getExpirationTime(75)
   988             expires = record.getExpirationTime(75)
   989 			if expires < self.nextTime:
   989             if expires < self.nextTime:
   990 				self.nextTime = expires
   990                 self.nextTime = expires
   991 
   991 
   992 	def cancel(self):
   992     def cancel(self):
   993 		self.done = 1
   993         self.done = 1
   994 		self.zeroconf.notifyAll()
   994         self.zeroconf.notifyAll()
   995 
   995 
   996 	def run(self):
   996     def run(self):
   997 		while 1:
   997         while 1:
   998 			event = None
   998             event = None
   999 			now = currentTimeMillis()
   999             now = currentTimeMillis()
  1000 			if len(self.list) == 0 and self.nextTime > now:
  1000             if len(self.list) == 0 and self.nextTime > now:
  1001 				self.zeroconf.wait(self.nextTime - now)
  1001                 self.zeroconf.wait(self.nextTime - now)
  1002 			if globals()['_GLOBAL_DONE'] or self.done:
  1002             if globals()['_GLOBAL_DONE'] or self.done:
  1003 				return
  1003                 return
  1004 			now = currentTimeMillis()
  1004             now = currentTimeMillis()
  1005 
  1005 
  1006 			if self.nextTime <= now:
  1006             if self.nextTime <= now:
  1007 				out = DNSOutgoing(_FLAGS_QR_QUERY)
  1007                 out = DNSOutgoing(_FLAGS_QR_QUERY)
  1008 				out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
  1008                 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
  1009 				for record in self.services.values():
  1009                 for record in self.services.values():
  1010 					if not record.isExpired(now):
  1010                     if not record.isExpired(now):
  1011 						out.addAnswerAtTime(record, now)
  1011                         out.addAnswerAtTime(record, now)
  1012 				self.zeroconf.send(out)
  1012                 self.zeroconf.send(out)
  1013 				self.nextTime = now + self.delay
  1013                 self.nextTime = now + self.delay
  1014 				self.delay = min(20 * 1000, self.delay * 2)
  1014                 self.delay = min(20 * 1000, self.delay * 2)
  1015 
  1015 
  1016 			if len(self.list) > 0:
  1016             if len(self.list) > 0:
  1017 				event = self.list.pop(0)
  1017                 event = self.list.pop(0)
  1018 
  1018 
  1019 			if event is not None:
  1019             if event is not None:
  1020 				event(self.zeroconf)
  1020                 event(self.zeroconf)
  1021 				
  1021                 
  1022 
  1022 
  1023 class ServiceInfo(object):
  1023 class ServiceInfo(object):
  1024 	"""Service information"""
  1024     """Service information"""
  1025 	
  1025     
  1026 	def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
  1026     def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
  1027 		"""Create a service description.
  1027         """Create a service description.
  1028 
  1028 
  1029 		type: fully qualified service type name
  1029         type: fully qualified service type name
  1030 		name: fully qualified service name
  1030         name: fully qualified service name
  1031 		address: IP address as unsigned short, network byte order
  1031         address: IP address as unsigned short, network byte order
  1032 		port: port that the service runs on
  1032         port: port that the service runs on
  1033 		weight: weight of the service
  1033         weight: weight of the service
  1034 		priority: priority of the service
  1034         priority: priority of the service
  1035 		properties: dictionary of properties (or a string holding the bytes for the text field)
  1035         properties: dictionary of properties (or a string holding the bytes for the text field)
  1036 		server: fully qualified name for service host (defaults to name)"""
  1036         server: fully qualified name for service host (defaults to name)"""
  1037 
  1037 
  1038 		if not name.endswith(type):
  1038         if not name.endswith(type):
  1039 			raise BadTypeInNameException
  1039             raise BadTypeInNameException
  1040 		self.type = type
  1040         self.type = type
  1041 		self.name = name
  1041         self.name = name
  1042 		self.address = address
  1042         self.address = address
  1043 		self.port = port
  1043         self.port = port
  1044 		self.weight = weight
  1044         self.weight = weight
  1045 		self.priority = priority
  1045         self.priority = priority
  1046 		if server:
  1046         if server:
  1047 			self.server = server
  1047             self.server = server
  1048 		else:
  1048         else:
  1049 			self.server = name
  1049             self.server = name
  1050 		self.setProperties(properties)
  1050         self.setProperties(properties)
  1051 
  1051 
  1052 	def setProperties(self, properties):
  1052     def setProperties(self, properties):
  1053 		"""Sets properties and text of this info from a dictionary"""
  1053         """Sets properties and text of this info from a dictionary"""
  1054 		if isinstance(properties, dict):
  1054         if isinstance(properties, dict):
  1055 			self.properties = properties
  1055             self.properties = properties
  1056 			list = []
  1056             list = []
  1057 			result = ''
  1057             result = ''
  1058 			for key in properties:
  1058             for key in properties:
  1059 				value = properties[key]
  1059                 value = properties[key]
  1060 				if value is None:
  1060                 if value is None:
  1061 					suffix = ''.encode('utf-8')
  1061                     suffix = ''.encode('utf-8')
  1062 				elif isinstance(value, str):
  1062                 elif isinstance(value, str):
  1063 					suffix = value.encode('utf-8')
  1063                     suffix = value.encode('utf-8')
  1064 				elif isinstance(value, int):
  1064                 elif isinstance(value, int):
  1065 					if value:
  1065                     if value:
  1066 						suffix = 'true'
  1066                         suffix = 'true'
  1067 					else:
  1067                     else:
  1068 						suffix = 'false'
  1068                         suffix = 'false'
  1069 				else:
  1069                 else:
  1070 					suffix = ''.encode('utf-8')
  1070                     suffix = ''.encode('utf-8')
  1071 				list.append('='.join((key, suffix)))
  1071                 list.append('='.join((key, suffix)))
  1072 			for item in list:
  1072             for item in list:
  1073 				result = ''.join((result, struct.pack('!c', chr(len(item))), item))
  1073                 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
  1074 			self.text = result
  1074             self.text = result
  1075 		else:
  1075         else:
  1076 			self.text = properties
  1076             self.text = properties
  1077 
  1077 
  1078 	def setText(self, text):
  1078     def setText(self, text):
  1079 		"""Sets properties and text given a text field"""
  1079         """Sets properties and text given a text field"""
  1080 		self.text = text
  1080         self.text = text
  1081 		try:
  1081         try:
  1082 			result = {}
  1082             result = {}
  1083 			end = len(text)
  1083             end = len(text)
  1084 			index = 0
  1084             index = 0
  1085 			strs = []
  1085             strs = []
  1086 			while index < end:
  1086             while index < end:
  1087 				length = ord(text[index])
  1087                 length = ord(text[index])
  1088 				index += 1
  1088                 index += 1
  1089 				strs.append(text[index:index+length])
  1089                 strs.append(text[index:index+length])
  1090 				index += length
  1090                 index += length
  1091 			
  1091             
  1092 			for s in strs:
  1092             for s in strs:
  1093 				eindex = s.find('=')
  1093                 eindex = s.find('=')
  1094 				if eindex == -1:
  1094                 if eindex == -1:
  1095 					# No equals sign at all
  1095                     # No equals sign at all
  1096 					key = s
  1096                     key = s
  1097 					value = 0
  1097                     value = 0
  1098 				else:
  1098                 else:
  1099 					key = s[:eindex]
  1099                     key = s[:eindex]
  1100 					value = s[eindex+1:]
  1100                     value = s[eindex+1:]
  1101 					if value == 'true':
  1101                     if value == 'true':
  1102 						value = 1
  1102                         value = 1
  1103 					elif value == 'false' or not value:
  1103                     elif value == 'false' or not value:
  1104 						value = 0
  1104                         value = 0
  1105 
  1105 
  1106 				# Only update non-existent properties
  1106                 # Only update non-existent properties
  1107 				if key and result.get(key) == None:
  1107                 if key and result.get(key) == None:
  1108 					result[key] = value
  1108                     result[key] = value
  1109 
  1109 
  1110 			self.properties = result
  1110             self.properties = result
  1111 		except:
  1111         except:
  1112 			traceback.print_exc()
  1112             traceback.print_exc()
  1113 			self.properties = None
  1113             self.properties = None
  1114 			
  1114             
  1115 	def getType(self):
  1115     def getType(self):
  1116 		"""Type accessor"""
  1116         """Type accessor"""
  1117 		return self.type
  1117         return self.type
  1118 
  1118 
  1119 	def getName(self):
  1119     def getName(self):
  1120 		"""Name accessor"""
  1120         """Name accessor"""
  1121 		if self.type is not None and self.name.endswith("." + self.type):
  1121         if self.type is not None and self.name.endswith("." + self.type):
  1122 			return self.name[:len(self.name) - len(self.type) - 1]
  1122             return self.name[:len(self.name) - len(self.type) - 1]
  1123 		return self.name
  1123         return self.name
  1124 
  1124 
  1125 	def getAddress(self):
  1125     def getAddress(self):
  1126 		"""Address accessor"""
  1126         """Address accessor"""
  1127 		return self.address
  1127         return self.address
  1128 
  1128 
  1129 	def getPort(self):
  1129     def getPort(self):
  1130 		"""Port accessor"""
  1130         """Port accessor"""
  1131 		return self.port
  1131         return self.port
  1132 
  1132 
  1133 	def getPriority(self):
  1133     def getPriority(self):
  1134 		"""Pirority accessor"""
  1134         """Pirority accessor"""
  1135 		return self.priority
  1135         return self.priority
  1136 
  1136 
  1137 	def getWeight(self):
  1137     def getWeight(self):
  1138 		"""Weight accessor"""
  1138         """Weight accessor"""
  1139 		return self.weight
  1139         return self.weight
  1140 
  1140 
  1141 	def getProperties(self):
  1141     def getProperties(self):
  1142 		"""Properties accessor"""
  1142         """Properties accessor"""
  1143 		return self.properties
  1143         return self.properties
  1144 
  1144 
  1145 	def getText(self):
  1145     def getText(self):
  1146 		"""Text accessor"""
  1146         """Text accessor"""
  1147 		return self.text
  1147         return self.text
  1148 
  1148 
  1149 	def getServer(self):
  1149     def getServer(self):
  1150 		"""Server accessor"""
  1150         """Server accessor"""
  1151 		return self.server
  1151         return self.server
  1152 
  1152 
  1153 	def updateRecord(self, zeroconf, now, record):
  1153     def updateRecord(self, zeroconf, now, record):
  1154 		"""Updates service information from a DNS record"""
  1154         """Updates service information from a DNS record"""
  1155 		if record is not None and not record.isExpired(now):
  1155         if record is not None and not record.isExpired(now):
  1156 			if record.type == _TYPE_A:
  1156             if record.type == _TYPE_A:
  1157 				if record.name == self.name:
  1157                 if record.name == self.name:
  1158 					self.address = record.address
  1158                     self.address = record.address
  1159 			elif record.type == _TYPE_SRV:
  1159             elif record.type == _TYPE_SRV:
  1160 				if record.name == self.name:
  1160                 if record.name == self.name:
  1161 					self.server = record.server
  1161                     self.server = record.server
  1162 					self.port = record.port
  1162                     self.port = record.port
  1163 					self.weight = record.weight
  1163                     self.weight = record.weight
  1164 					self.priority = record.priority
  1164                     self.priority = record.priority
  1165 					self.address = None
  1165                     self.address = None
  1166 					self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
  1166                     self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
  1167 			elif record.type == _TYPE_TXT:
  1167             elif record.type == _TYPE_TXT:
  1168 				if record.name == self.name:
  1168                 if record.name == self.name:
  1169 					self.setText(record.text)
  1169                     self.setText(record.text)
  1170 
  1170 
  1171 	def request(self, zeroconf, timeout):
  1171     def request(self, zeroconf, timeout):
  1172 		"""Returns true if the service could be discovered on the
  1172         """Returns true if the service could be discovered on the
  1173 		network, and updates this object with details discovered.
  1173         network, and updates this object with details discovered.
  1174 		"""
  1174         """
  1175 		now = currentTimeMillis()
  1175         now = currentTimeMillis()
  1176 		delay = _LISTENER_TIME
  1176         delay = _LISTENER_TIME
  1177 		next = now + delay
  1177         next = now + delay
  1178 		last = now + timeout
  1178         last = now + timeout
  1179 		result = 0
  1179         result = 0
  1180 		try:
  1180         try:
  1181 			zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
  1181             zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
  1182 			while self.server is None or self.address is None or self.text is None:
  1182             while self.server is None or self.address is None or self.text is None:
  1183 				if last <= now:
  1183                 if last <= now:
  1184 					return 0
  1184                     return 0
  1185 				if next <= now:
  1185                 if next <= now:
  1186 					out = DNSOutgoing(_FLAGS_QR_QUERY)
  1186                     out = DNSOutgoing(_FLAGS_QR_QUERY)
  1187 					out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
  1187                     out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
  1188 					out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
  1188                     out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
  1189 					out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
  1189                     out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
  1190 					out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
  1190                     out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
  1191 					if self.server is not None:
  1191                     if self.server is not None:
  1192 						out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
  1192                         out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
  1193 						out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
  1193                         out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
  1194 					zeroconf.send(out)
  1194                     zeroconf.send(out)
  1195 					next = now + delay
  1195                     next = now + delay
  1196 					delay = delay * 2
  1196                     delay = delay * 2
  1197 
  1197 
  1198 				zeroconf.wait(min(next, last) - now)
  1198                 zeroconf.wait(min(next, last) - now)
  1199 				now = currentTimeMillis()
  1199                 now = currentTimeMillis()
  1200 			result = 1
  1200             result = 1
  1201 		finally:
  1201         finally:
  1202 			zeroconf.removeListener(self)
  1202             zeroconf.removeListener(self)
  1203 			
  1203             
  1204 		return result
  1204         return result
  1205 
  1205 
  1206 	def __eq__(self, other):
  1206     def __eq__(self, other):
  1207 		"""Tests equality of service name"""
  1207         """Tests equality of service name"""
  1208 		if isinstance(other, ServiceInfo):
  1208         if isinstance(other, ServiceInfo):
  1209 			return other.name == self.name
  1209             return other.name == self.name
  1210 		return 0
  1210         return 0
  1211 
  1211 
  1212 	def __ne__(self, other):
  1212     def __ne__(self, other):
  1213 		"""Non-equality test"""
  1213         """Non-equality test"""
  1214 		return not self.__eq__(other)
  1214         return not self.__eq__(other)
  1215 
  1215 
  1216 	def __repr__(self):
  1216     def __repr__(self):
  1217 		"""String representation"""
  1217         """String representation"""
  1218 		result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
  1218         result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
  1219 		if self.text is None:
  1219         if self.text is None:
  1220 			result += "None"
  1220             result += "None"
  1221 		else:
  1221         else:
  1222 			if len(self.text) < 20:
  1222             if len(self.text) < 20:
  1223 				result += self.text
  1223                 result += self.text
  1224 			else:
  1224             else:
  1225 				result += self.text[:17] + "..."
  1225                 result += self.text[:17] + "..."
  1226 		result += "]"
  1226         result += "]"
  1227 		return result
  1227         return result
  1228 				
  1228                 
  1229 
  1229 
  1230 class Zeroconf(object):
  1230 class Zeroconf(object):
  1231 	"""Implementation of Zeroconf Multicast DNS Service Discovery
  1231     """Implementation of Zeroconf Multicast DNS Service Discovery
  1232 
  1232 
  1233 	Supports registration, unregistration, queries and browsing.
  1233     Supports registration, unregistration, queries and browsing.
  1234 	"""
  1234     """
  1235 	def __init__(self, bindaddress=None):
  1235     def __init__(self, bindaddress=None):
  1236 		"""Creates an instance of the Zeroconf class, establishing
  1236         """Creates an instance of the Zeroconf class, establishing
  1237 		multicast communications, listening and reaping threads."""
  1237         multicast communications, listening and reaping threads."""
  1238 		globals()['_GLOBAL_DONE'] = 0
  1238         globals()['_GLOBAL_DONE'] = 0
  1239 		if bindaddress is None:
  1239         self.intf = bindaddress
  1240 			self.intf = socket.gethostbyname(socket.gethostname())
  1240         self.group = ('', _MDNS_PORT)
  1241 		else:
  1241         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1242 			self.intf = bindaddress
  1242         try:
  1243 		self.group = ('', _MDNS_PORT)
  1243             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1244 		self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1244             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1245 		try:
  1245         except:
  1246 			self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1246             # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
  1247 			self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1247             # multicast UDP sockets (p 731, "TCP/IP Illustrated,
  1248 		except:
  1248             # Volume 2"), but some BSD-derived systems require
  1249 			# SO_REUSEADDR should be equivalent to SO_REUSEPORT for
  1249             # SO_REUSEPORT to be specified explicity.  Also, not all
  1250 			# multicast UDP sockets (p 731, "TCP/IP Illustrated,
  1250             # versions of Python have SO_REUSEPORT available.  So
  1251 			# Volume 2"), but some BSD-derived systems require
  1251             # if you're on a BSD-based system, and haven't upgraded
  1252 			# SO_REUSEPORT to be specified explicity.  Also, not all
  1252             # to Python 2.3 yet, you may find this library doesn't
  1253 			# versions of Python have SO_REUSEPORT available.  So
  1253             # work as expected.
  1254 			# if you're on a BSD-based system, and haven't upgraded
  1254             #
  1255 			# to Python 2.3 yet, you may find this library doesn't
  1255             pass
  1256 			# work as expected.
  1256         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
  1257 			#
  1257         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
  1258 			pass
  1258         try:
  1259 		self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
  1259             self.socket.bind(self.group)
  1260 		self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
  1260         except:
  1261 		try:
  1261             # Some versions of linux raise an exception even though
  1262 			self.socket.bind(self.group)
  1262             # the SO_REUSE* options have been set, so ignore it
  1263 		except:
  1263             #
  1264 			# Some versions of linux raise an exception even though
  1264             pass
  1265 			# the SO_REUSE* options have been set, so ignore it
  1265 
  1266 			#
  1266         if self.intf is not None:
  1267 			pass
  1267             self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
  1268 		self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
  1268         self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
  1269 		self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
  1269 
  1270 
  1270         self.listeners = []
  1271 		self.listeners = []
  1271         self.browsers = []
  1272 		self.browsers = []
  1272         self.services = {}
  1273 		self.services = {}
  1273 
  1274 
  1274         self.cache = DNSCache()
  1275 		self.cache = DNSCache()
  1275 
  1276 
  1276         self.condition = threading.Condition()
  1277 		self.condition = threading.Condition()
  1277         
  1278 		
  1278         self.engine = Engine(self)
  1279 		self.engine = Engine(self)
  1279         self.listener = Listener(self)
  1280 		self.listener = Listener(self)
  1280         self.reaper = Reaper(self)
  1281 		self.reaper = Reaper(self)
  1281 
  1282 
  1282     def isLoopback(self):
  1283 	def isLoopback(self):
  1283         if self.intf is not None:
  1284 		return self.intf.startswith("127.0.0.1")
  1284             return self.intf.startswith("127.0.0.1")
  1285 
  1285         return False
  1286 	def isLinklocal(self):
  1286 
  1287 		return self.intf.startswith("169.254.")
  1287     def isLinklocal(self):
  1288 
  1288         if self.intf is not None:
  1289 	def wait(self, timeout):
  1289             return self.intf.startswith("169.254.")
  1290 		"""Calling thread waits for a given number of milliseconds or
  1290         return False
  1291 		until notified."""
  1291 
  1292 		self.condition.acquire()
  1292     def wait(self, timeout):
  1293 		self.condition.wait(timeout/1000)
  1293         """Calling thread waits for a given number of milliseconds or
  1294 		self.condition.release()
  1294         until notified."""
  1295 
  1295         self.condition.acquire()
  1296 	def notifyAll(self):
  1296         self.condition.wait(timeout/1000)
  1297 		"""Notifies all waiting threads"""
  1297         self.condition.release()
  1298 		self.condition.acquire()
  1298 
  1299 		self.condition.notifyAll()
  1299     def notifyAll(self):
  1300 		self.condition.release()
  1300         """Notifies all waiting threads"""
  1301 
  1301         self.condition.acquire()
  1302 	def getServiceInfo(self, type, name, timeout=3000):
  1302         self.condition.notifyAll()
  1303 		"""Returns network's service information for a particular
  1303         self.condition.release()
  1304 		name and type, or None if no service matches by the timeout,
  1304 
  1305 		which defaults to 3 seconds."""
  1305     def getServiceInfo(self, type, name, timeout=3000):
  1306 		info = ServiceInfo(type, name)
  1306         """Returns network's service information for a particular
  1307 		if info.request(self, timeout):
  1307         name and type, or None if no service matches by the timeout,
  1308 			return info
  1308         which defaults to 3 seconds."""
  1309 		return None
  1309         info = ServiceInfo(type, name)
  1310 
  1310         if info.request(self, timeout):
  1311 	def addServiceListener(self, type, listener):
  1311             return info
  1312 		"""Adds a listener for a particular service type.  This object
  1312         return None
  1313 		will then have its updateRecord method called when information
  1313 
  1314 		arrives for that type."""
  1314     def addServiceListener(self, type, listener):
  1315 		self.removeServiceListener(listener)
  1315         """Adds a listener for a particular service type.  This object
  1316 		self.browsers.append(ServiceBrowser(self, type, listener))
  1316         will then have its updateRecord method called when information
  1317 
  1317         arrives for that type."""
  1318 	def removeServiceListener(self, listener):
  1318         self.removeServiceListener(listener)
  1319 		"""Removes a listener from the set that is currently listening."""
  1319         self.browsers.append(ServiceBrowser(self, type, listener))
  1320 		for browser in self.browsers:
  1320 
  1321 			if browser.listener == listener:
  1321     def removeServiceListener(self, listener):
  1322 				browser.cancel()
  1322         """Removes a listener from the set that is currently listening."""
  1323 				del(browser)
  1323         for browser in self.browsers:
  1324 
  1324             if browser.listener == listener:
  1325 	def registerService(self, info, ttl=_DNS_TTL):
  1325                 browser.cancel()
  1326 		"""Registers service information to the network with a default TTL
  1326                 del(browser)
  1327 		of 60 seconds.  Zeroconf will then respond to requests for
  1327 
  1328 		information for that service.  The name of the service may be
  1328     def registerService(self, info, ttl=_DNS_TTL):
  1329 		changed if needed to make it unique on the network."""
  1329         """Registers service information to the network with a default TTL
  1330 		self.checkService(info)
  1330         of 60 seconds.  Zeroconf will then respond to requests for
  1331 		self.services[info.name.lower()] = info
  1331         information for that service.  The name of the service may be
  1332 		now = currentTimeMillis()
  1332         changed if needed to make it unique on the network."""
  1333 		nextTime = now
  1333         self.checkService(info)
  1334 		i = 0
  1334         self.services[info.name.lower()] = info
  1335 		while i < 3:
  1335         now = currentTimeMillis()
  1336 			if now < nextTime:
  1336         nextTime = now
  1337 				self.wait(nextTime - now)
  1337         i = 0
  1338 				now = currentTimeMillis()
  1338         while i < 3:
  1339 				continue
  1339             if now < nextTime:
  1340 			out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1340                 self.wait(nextTime - now)
  1341 			out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
  1341                 now = currentTimeMillis()
  1342 			out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
  1342                 continue
  1343 			out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
  1343             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1344 			if info.address:
  1344             out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
  1345 				out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
  1345             out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
  1346 			self.send(out)
  1346             out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
  1347 			i += 1
  1347             if info.address:
  1348 			nextTime += _REGISTER_TIME
  1348                 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
  1349 
  1349             self.send(out)
  1350 	def unregisterService(self, info):
  1350             i += 1
  1351 		"""Unregister a service."""
  1351             nextTime += _REGISTER_TIME
  1352 		try:
  1352 
  1353 			del(self.services[info.name.lower()])
  1353     def unregisterService(self, info):
  1354 		except:
  1354         """Unregister a service."""
  1355 			pass
  1355         try:
  1356 		now = currentTimeMillis()
  1356             del(self.services[info.name.lower()])
  1357 		nextTime = now
  1357         except:
  1358 		i = 0
  1358             pass
  1359 		while i < 3:
  1359         now = currentTimeMillis()
  1360 			if now < nextTime:
  1360         nextTime = now
  1361 				self.wait(nextTime - now)
  1361         i = 0
  1362 				now = currentTimeMillis()
  1362         while i < 3:
  1363 				continue
  1363             if now < nextTime:
  1364 			out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1364                 self.wait(nextTime - now)
  1365 			out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
  1365                 now = currentTimeMillis()
  1366 			out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
  1366                 continue
  1367 			out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
  1367             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1368 			if info.address:
  1368             out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
  1369 				out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
  1369             out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
  1370 			self.send(out)
  1370             out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
  1371 			i += 1
  1371             if info.address:
  1372 			nextTime += _UNREGISTER_TIME
  1372                 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
  1373 
  1373             self.send(out)
  1374 	def unregisterAllServices(self):
  1374             i += 1
  1375 		"""Unregister all registered services."""
  1375             nextTime += _UNREGISTER_TIME
  1376 		if len(self.services) > 0:
  1376 
  1377 			now = currentTimeMillis()
  1377     def unregisterAllServices(self):
  1378 			nextTime = now
  1378         """Unregister all registered services."""
  1379 			i = 0
  1379         if len(self.services) > 0:
  1380 			while i < 3:
  1380             now = currentTimeMillis()
  1381 				if now < nextTime:
  1381             nextTime = now
  1382 					self.wait(nextTime - now)
  1382             i = 0
  1383 					now = currentTimeMillis()
  1383             while i < 3:
  1384 					continue
  1384                 if now < nextTime:
  1385 				out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1385                     self.wait(nextTime - now)
  1386 				for info in self.services.values():
  1386                     now = currentTimeMillis()
  1387 					out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
  1387                     continue
  1388 					out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
  1388                 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1389 					out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
  1389                 for info in self.services.values():
  1390 					if info.address:
  1390                     out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
  1391 						out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
  1391                     out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
  1392 				self.send(out)
  1392                     out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
  1393 				i += 1
  1393                     if info.address:
  1394 				nextTime += _UNREGISTER_TIME
  1394                         out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
  1395 
  1395                 self.send(out)
  1396 	def checkService(self, info):
  1396                 i += 1
  1397 		"""Checks the network for a unique service name, modifying the
  1397                 nextTime += _UNREGISTER_TIME
  1398 		ServiceInfo passed in if it is not unique."""
  1398 
  1399 		now = currentTimeMillis()
  1399     def checkService(self, info):
  1400 		nextTime = now
  1400         """Checks the network for a unique service name, modifying the
  1401 		i = 0
  1401         ServiceInfo passed in if it is not unique."""
  1402 		while i < 3:
  1402         now = currentTimeMillis()
  1403 			for record in self.cache.entriesWithName(info.type):
  1403         nextTime = now
  1404 				if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
  1404         i = 0
  1405 					if (info.name.find('.') < 0):
  1405         while i < 3:
  1406 						info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
  1406             for record in self.cache.entriesWithName(info.type):
  1407 						self.checkService(info)
  1407                 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
  1408 						return
  1408                     if (info.name.find('.') < 0):
  1409 					raise NonUniqueNameException
  1409                         info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
  1410 			if now < nextTime:
  1410                         self.checkService(info)
  1411 				self.wait(nextTime - now)
  1411                         return
  1412 				now = currentTimeMillis()
  1412                     raise NonUniqueNameException
  1413 				continue
  1413             if now < nextTime:
  1414 			out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
  1414                 self.wait(nextTime - now)
  1415 			self.debug = out
  1415                 now = currentTimeMillis()
  1416 			out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
  1416                 continue
  1417 			out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
  1417             out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
  1418 			self.send(out)
  1418             self.debug = out
  1419 			i += 1
  1419             out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
  1420 			nextTime += _CHECK_TIME
  1420             out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
  1421 
  1421             self.send(out)
  1422 	def addListener(self, listener, question):
  1422             i += 1
  1423 		"""Adds a listener for a given question.  The listener will have
  1423             nextTime += _CHECK_TIME
  1424 		its updateRecord method called when information is available to
  1424 
  1425 		answer the question."""
  1425     def addListener(self, listener, question):
  1426 		now = currentTimeMillis()
  1426         """Adds a listener for a given question.  The listener will have
  1427 		self.listeners.append(listener)
  1427         its updateRecord method called when information is available to
  1428 		if question is not None:
  1428         answer the question."""
  1429 			for record in self.cache.entriesWithName(question.name):
  1429         now = currentTimeMillis()
  1430 				if question.answeredBy(record) and not record.isExpired(now):
  1430         self.listeners.append(listener)
  1431 					listener.updateRecord(self, now, record)
  1431         if question is not None:
  1432 		self.notifyAll()
  1432             for record in self.cache.entriesWithName(question.name):
  1433 
  1433                 if question.answeredBy(record) and not record.isExpired(now):
  1434 	def removeListener(self, listener):
  1434                     listener.updateRecord(self, now, record)
  1435 		"""Removes a listener."""
  1435         self.notifyAll()
  1436 		try:
  1436 
  1437 			self.listeners.remove(listener)
  1437     def removeListener(self, listener):
  1438 			self.notifyAll()
  1438         """Removes a listener."""
  1439 		except:
  1439         try:
  1440 			pass
  1440             self.listeners.remove(listener)
  1441 
  1441             self.notifyAll()
  1442 	def updateRecord(self, now, rec):
  1442         except:
  1443 		"""Used to notify listeners of new information that has updated
  1443             pass
  1444 		a record."""
  1444 
  1445 		for listener in self.listeners:
  1445     def updateRecord(self, now, rec):
  1446 			listener.updateRecord(self, now, rec)
  1446         """Used to notify listeners of new information that has updated
  1447 		self.notifyAll()
  1447         a record."""
  1448 
  1448         for listener in self.listeners:
  1449 	def handleResponse(self, msg):
  1449             listener.updateRecord(self, now, rec)
  1450 		"""Deal with incoming response packets.  All answers
  1450         self.notifyAll()
  1451 		are held in the cache, and listeners are notified."""
  1451 
  1452 		now = currentTimeMillis()
  1452     def handleResponse(self, msg):
  1453 		for record in msg.answers:
  1453         """Deal with incoming response packets.  All answers
  1454 			expired = record.isExpired(now)
  1454         are held in the cache, and listeners are notified."""
  1455 			if record in self.cache.entries():
  1455         now = currentTimeMillis()
  1456 				if expired:
  1456         for record in msg.answers:
  1457 					self.cache.remove(record)
  1457             expired = record.isExpired(now)
  1458 				else:
  1458             if record in self.cache.entries():
  1459 					entry = self.cache.get(record)
  1459                 if expired:
  1460 					if entry is not None:
  1460                     self.cache.remove(record)
  1461 						entry.resetTTL(record)
  1461                 else:
  1462 						record = entry
  1462                     entry = self.cache.get(record)
  1463 			else:
  1463                     if entry is not None:
  1464 				self.cache.add(record)
  1464                         entry.resetTTL(record)
  1465 				
  1465                         record = entry
  1466 			self.updateRecord(now, record)
  1466             else:
  1467 
  1467                 self.cache.add(record)
  1468 	def handleQuery(self, msg, addr, port):
  1468                 
  1469 		"""Deal with incoming query packets.  Provides a response if
  1469             self.updateRecord(now, record)
  1470 		possible."""
  1470 
  1471 		out = None
  1471     def handleQuery(self, msg, addr, port):
  1472 
  1472         """Deal with incoming query packets.  Provides a response if
  1473 		# Support unicast client responses
  1473         possible."""
  1474 		#
  1474         out = None
  1475 		if port != _MDNS_PORT:
  1475 
  1476 			out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
  1476         # Support unicast client responses
  1477 			for question in msg.questions:
  1477         #
  1478 				out.addQuestion(question)
  1478         if port != _MDNS_PORT:
  1479 		
  1479             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
  1480 		for question in msg.questions:
  1480             for question in msg.questions:
  1481 			if question.type == _TYPE_PTR:
  1481                 out.addQuestion(question)
  1482 				for service in self.services.values():
  1482         
  1483 					if question.name == service.type:
  1483         for question in msg.questions:
  1484 						if out is None:
  1484             if question.type == _TYPE_PTR:
  1485 							out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1485                 for service in self.services.values():
  1486 						out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
  1486                     if question.name == service.type:
  1487 			else:
  1487                         if out is None:
  1488 				try:
  1488                             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1489 					if out is None:
  1489                         out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
  1490 						out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1490             else:
  1491 					
  1491                 try:
  1492 					# Answer A record queries for any service addresses we know
  1492                     if out is None:
  1493 					if question.type == _TYPE_A or question.type == _TYPE_ANY:
  1493                         out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1494 						for service in self.services.values():
  1494                     
  1495 							if service.server == question.name.lower():
  1495                     # Answer A record queries for any service addresses we know
  1496 								out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1496                     if question.type == _TYPE_A or question.type == _TYPE_ANY:
  1497 					
  1497                         for service in self.services.values():
  1498 					service = self.services.get(question.name.lower(), None)
  1498                             if service.server == question.name.lower():
  1499 					if not service: continue
  1499                                 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1500 					
  1500                     
  1501 					if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
  1501                     service = self.services.get(question.name.lower(), None)
  1502 						out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
  1502                     if not service: continue
  1503 					if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
  1503                     
  1504 						out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
  1504                     if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
  1505 					if question.type == _TYPE_SRV:
  1505                         out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
  1506 						out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1506                     if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
  1507 				except:
  1507                         out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
  1508 					traceback.print_exc()
  1508                     if question.type == _TYPE_SRV:
  1509 				
  1509                         out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
  1510 		if out is not None and out.answers:
  1510                 except:
  1511 			out.id = msg.id
  1511                     traceback.print_exc()
  1512 			self.send(out, addr, port)
  1512                 
  1513 
  1513         if out is not None and out.answers:
  1514 	def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
  1514             out.id = msg.id
  1515 		"""Sends an outgoing packet."""
  1515             self.send(out, addr, port)
  1516 		# This is a quick test to see if we can parse the packets we generate
  1516 
  1517 		#temp = DNSIncoming(out.packet())
  1517     def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
  1518 		try:
  1518         """Sends an outgoing packet."""
  1519 			bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
  1519         # This is a quick test to see if we can parse the packets we generate
  1520 		except:
  1520         #temp = DNSIncoming(out.packet())
  1521 			# Ignore this, it may be a temporary loss of network connection
  1521         try:
  1522 			pass
  1522             bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
  1523 
  1523         except:
  1524 	def close(self):
  1524             # Ignore this, it may be a temporary loss of network connection
  1525 		"""Ends the background threads, and prevent this instance from
  1525             pass
  1526 		servicing further queries."""
  1526 
  1527 		if globals()['_GLOBAL_DONE'] == 0:
  1527     def close(self):
  1528 			globals()['_GLOBAL_DONE'] = 1
  1528         """Ends the background threads, and prevent this instance from
  1529 			self.notifyAll()
  1529         servicing further queries."""
  1530 			self.engine.notify()
  1530         if globals()['_GLOBAL_DONE'] == 0:
  1531 			self.unregisterAllServices()
  1531             globals()['_GLOBAL_DONE'] = 1
  1532 			self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
  1532             self.notifyAll()
  1533 			self.socket.close()
  1533             self.engine.notify()
  1534 			
  1534             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'))
       
  1536             self.socket.close()
       
  1537             
  1535 # Test a few module features, including service registration, service
  1538 # Test a few module features, including service registration, service
  1536 # query (for Zoe), and service unregistration.
  1539 # query (for Zoe), and service unregistration.
  1537 
  1540 
  1538 if __name__ == '__main__':	
  1541 if __name__ == '__main__':    
  1539 	print "Multicast DNS Service Discovery for Python, version", __version__
  1542     print "Multicast DNS Service Discovery for Python, version", __version__
  1540 	r = Zeroconf()
  1543     r = Zeroconf()
  1541 	print "1. Testing registration of a service..."
  1544     print "1. Testing registration of a service..."
  1542 	desc = {'version':'0.10','a':'test value', 'b':'another value'}
  1545     desc = {'version':'0.10','a':'test value', 'b':'another value'}
  1543 	info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
  1546     info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
  1544 	print "   Registering service..."
  1547     print "   Registering service..."
  1545 	r.registerService(info)
  1548     r.registerService(info)
  1546 	print "   Registration done."
  1549     print "   Registration done."
  1547 	print "2. Testing query of service information..."
  1550     print "2. Testing query of service information..."
  1548 	print "   Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
  1551     print "   Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
  1549 	print "   Query done."
  1552     print "   Query done."
  1550 	print "3. Testing query of own service..."
  1553     print "3. Testing query of own service..."
  1551 	print "   Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
  1554     print "   Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
  1552 	print "   Query done."
  1555     print "   Query done."
  1553 	print "4. Testing unregister of service information..."
  1556     print "4. Testing unregister of service information..."
  1554 	r.unregisterService(info)
  1557     r.unregisterService(info)
  1555 	print "   Unregister done."
  1558     print "   Unregister done."
  1556 	r.close()
  1559     r.close()