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