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