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