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