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