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