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