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() |