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