##// END OF EJS Templates
zeroconf: use DNS length field to safely skip unknown record types
Brendan Cully -
r10387:9b87c5f4 default
parent child Browse files
Show More
@@ -1,1574 +1,1574 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 break
563 self.offset += info[3]
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 return self.data[offset:offset+len].decode('utf-8')
578 return self.data[offset:offset+len].decode('utf-8')
579
579
580 def readName(self):
580 def readName(self):
581 """Reads a domain name from the packet"""
581 """Reads a domain name from the packet"""
582 result = ''
582 result = ''
583 off = self.offset
583 off = self.offset
584 next = -1
584 next = -1
585 first = off
585 first = off
586
586
587 while 1:
587 while 1:
588 len = ord(self.data[off])
588 len = ord(self.data[off])
589 off += 1
589 off += 1
590 if len == 0:
590 if len == 0:
591 break
591 break
592 t = len & 0xC0
592 t = len & 0xC0
593 if t == 0x00:
593 if t == 0x00:
594 result = ''.join((result, self.readUTF(off, len) + '.'))
594 result = ''.join((result, self.readUTF(off, len) + '.'))
595 off += len
595 off += len
596 elif t == 0xC0:
596 elif t == 0xC0:
597 if next < 0:
597 if next < 0:
598 next = off + 1
598 next = off + 1
599 off = ((len & 0x3F) << 8) | ord(self.data[off])
599 off = ((len & 0x3F) << 8) | ord(self.data[off])
600 if off >= first:
600 if off >= first:
601 raise "Bad domain name (circular) at " + str(off)
601 raise "Bad domain name (circular) at " + str(off)
602 first = off
602 first = off
603 else:
603 else:
604 raise "Bad domain name at " + str(off)
604 raise "Bad domain name at " + str(off)
605
605
606 if next >= 0:
606 if next >= 0:
607 self.offset = next
607 self.offset = next
608 else:
608 else:
609 self.offset = off
609 self.offset = off
610
610
611 return result
611 return result
612
612
613
613
614 class DNSOutgoing(object):
614 class DNSOutgoing(object):
615 """Object representation of an outgoing packet"""
615 """Object representation of an outgoing packet"""
616
616
617 def __init__(self, flags, multicast = 1):
617 def __init__(self, flags, multicast = 1):
618 self.finished = 0
618 self.finished = 0
619 self.id = 0
619 self.id = 0
620 self.multicast = multicast
620 self.multicast = multicast
621 self.flags = flags
621 self.flags = flags
622 self.names = {}
622 self.names = {}
623 self.data = []
623 self.data = []
624 self.size = 12
624 self.size = 12
625
625
626 self.questions = []
626 self.questions = []
627 self.answers = []
627 self.answers = []
628 self.authorities = []
628 self.authorities = []
629 self.additionals = []
629 self.additionals = []
630
630
631 def addQuestion(self, record):
631 def addQuestion(self, record):
632 """Adds a question"""
632 """Adds a question"""
633 self.questions.append(record)
633 self.questions.append(record)
634
634
635 def addAnswer(self, inp, record):
635 def addAnswer(self, inp, record):
636 """Adds an answer"""
636 """Adds an answer"""
637 if not record.suppressedBy(inp):
637 if not record.suppressedBy(inp):
638 self.addAnswerAtTime(record, 0)
638 self.addAnswerAtTime(record, 0)
639
639
640 def addAnswerAtTime(self, record, now):
640 def addAnswerAtTime(self, record, now):
641 """Adds an answer if if does not expire by a certain time"""
641 """Adds an answer if if does not expire by a certain time"""
642 if record is not None:
642 if record is not None:
643 if now == 0 or not record.isExpired(now):
643 if now == 0 or not record.isExpired(now):
644 self.answers.append((record, now))
644 self.answers.append((record, now))
645
645
646 def addAuthorativeAnswer(self, record):
646 def addAuthorativeAnswer(self, record):
647 """Adds an authoritative answer"""
647 """Adds an authoritative answer"""
648 self.authorities.append(record)
648 self.authorities.append(record)
649
649
650 def addAdditionalAnswer(self, record):
650 def addAdditionalAnswer(self, record):
651 """Adds an additional answer"""
651 """Adds an additional answer"""
652 self.additionals.append(record)
652 self.additionals.append(record)
653
653
654 def writeByte(self, value):
654 def writeByte(self, value):
655 """Writes a single byte to the packet"""
655 """Writes a single byte to the packet"""
656 format = '!c'
656 format = '!c'
657 self.data.append(struct.pack(format, chr(value)))
657 self.data.append(struct.pack(format, chr(value)))
658 self.size += 1
658 self.size += 1
659
659
660 def insertShort(self, index, value):
660 def insertShort(self, index, value):
661 """Inserts an unsigned short in a certain position in the packet"""
661 """Inserts an unsigned short in a certain position in the packet"""
662 format = '!H'
662 format = '!H'
663 self.data.insert(index, struct.pack(format, value))
663 self.data.insert(index, struct.pack(format, value))
664 self.size += 2
664 self.size += 2
665
665
666 def writeShort(self, value):
666 def writeShort(self, value):
667 """Writes an unsigned short to the packet"""
667 """Writes an unsigned short to the packet"""
668 format = '!H'
668 format = '!H'
669 self.data.append(struct.pack(format, value))
669 self.data.append(struct.pack(format, value))
670 self.size += 2
670 self.size += 2
671
671
672 def writeInt(self, value):
672 def writeInt(self, value):
673 """Writes an unsigned integer to the packet"""
673 """Writes an unsigned integer to the packet"""
674 format = '!I'
674 format = '!I'
675 self.data.append(struct.pack(format, int(value)))
675 self.data.append(struct.pack(format, int(value)))
676 self.size += 4
676 self.size += 4
677
677
678 def writeString(self, value, length):
678 def writeString(self, value, length):
679 """Writes a string to the packet"""
679 """Writes a string to the packet"""
680 format = '!' + str(length) + 's'
680 format = '!' + str(length) + 's'
681 self.data.append(struct.pack(format, value))
681 self.data.append(struct.pack(format, value))
682 self.size += length
682 self.size += length
683
683
684 def writeUTF(self, s):
684 def writeUTF(self, s):
685 """Writes a UTF-8 string of a given length to the packet"""
685 """Writes a UTF-8 string of a given length to the packet"""
686 utfstr = s.encode('utf-8')
686 utfstr = s.encode('utf-8')
687 length = len(utfstr)
687 length = len(utfstr)
688 if length > 64:
688 if length > 64:
689 raise NamePartTooLongException
689 raise NamePartTooLongException
690 self.writeByte(length)
690 self.writeByte(length)
691 self.writeString(utfstr, length)
691 self.writeString(utfstr, length)
692
692
693 def writeName(self, name):
693 def writeName(self, name):
694 """Writes a domain name to the packet"""
694 """Writes a domain name to the packet"""
695
695
696 try:
696 try:
697 # Find existing instance of this name in packet
697 # Find existing instance of this name in packet
698 #
698 #
699 index = self.names[name]
699 index = self.names[name]
700 except KeyError:
700 except KeyError:
701 # No record of this name already, so write it
701 # No record of this name already, so write it
702 # out as normal, recording the location of the name
702 # out as normal, recording the location of the name
703 # for future pointers to it.
703 # for future pointers to it.
704 #
704 #
705 self.names[name] = self.size
705 self.names[name] = self.size
706 parts = name.split('.')
706 parts = name.split('.')
707 if parts[-1] == '':
707 if parts[-1] == '':
708 parts = parts[:-1]
708 parts = parts[:-1]
709 for part in parts:
709 for part in parts:
710 self.writeUTF(part)
710 self.writeUTF(part)
711 self.writeByte(0)
711 self.writeByte(0)
712 return
712 return
713
713
714 # An index was found, so write a pointer to it
714 # An index was found, so write a pointer to it
715 #
715 #
716 self.writeByte((index >> 8) | 0xC0)
716 self.writeByte((index >> 8) | 0xC0)
717 self.writeByte(index)
717 self.writeByte(index)
718
718
719 def writeQuestion(self, question):
719 def writeQuestion(self, question):
720 """Writes a question to the packet"""
720 """Writes a question to the packet"""
721 self.writeName(question.name)
721 self.writeName(question.name)
722 self.writeShort(question.type)
722 self.writeShort(question.type)
723 self.writeShort(question.clazz)
723 self.writeShort(question.clazz)
724
724
725 def writeRecord(self, record, now):
725 def writeRecord(self, record, now):
726 """Writes a record (answer, authoritative answer, additional) to
726 """Writes a record (answer, authoritative answer, additional) to
727 the packet"""
727 the packet"""
728 self.writeName(record.name)
728 self.writeName(record.name)
729 self.writeShort(record.type)
729 self.writeShort(record.type)
730 if record.unique and self.multicast:
730 if record.unique and self.multicast:
731 self.writeShort(record.clazz | _CLASS_UNIQUE)
731 self.writeShort(record.clazz | _CLASS_UNIQUE)
732 else:
732 else:
733 self.writeShort(record.clazz)
733 self.writeShort(record.clazz)
734 if now == 0:
734 if now == 0:
735 self.writeInt(record.ttl)
735 self.writeInt(record.ttl)
736 else:
736 else:
737 self.writeInt(record.getRemainingTTL(now))
737 self.writeInt(record.getRemainingTTL(now))
738 index = len(self.data)
738 index = len(self.data)
739 # Adjust size for the short we will write before this record
739 # Adjust size for the short we will write before this record
740 #
740 #
741 self.size += 2
741 self.size += 2
742 record.write(self)
742 record.write(self)
743 self.size -= 2
743 self.size -= 2
744
744
745 length = len(''.join(self.data[index:]))
745 length = len(''.join(self.data[index:]))
746 self.insertShort(index, length) # Here is the short we adjusted for
746 self.insertShort(index, length) # Here is the short we adjusted for
747
747
748 def packet(self):
748 def packet(self):
749 """Returns a string containing the packet's bytes
749 """Returns a string containing the packet's bytes
750
750
751 No further parts should be added to the packet once this
751 No further parts should be added to the packet once this
752 is done."""
752 is done."""
753 if not self.finished:
753 if not self.finished:
754 self.finished = 1
754 self.finished = 1
755 for question in self.questions:
755 for question in self.questions:
756 self.writeQuestion(question)
756 self.writeQuestion(question)
757 for answer, time in self.answers:
757 for answer, time in self.answers:
758 self.writeRecord(answer, time)
758 self.writeRecord(answer, time)
759 for authority in self.authorities:
759 for authority in self.authorities:
760 self.writeRecord(authority, 0)
760 self.writeRecord(authority, 0)
761 for additional in self.additionals:
761 for additional in self.additionals:
762 self.writeRecord(additional, 0)
762 self.writeRecord(additional, 0)
763
763
764 self.insertShort(0, len(self.additionals))
764 self.insertShort(0, len(self.additionals))
765 self.insertShort(0, len(self.authorities))
765 self.insertShort(0, len(self.authorities))
766 self.insertShort(0, len(self.answers))
766 self.insertShort(0, len(self.answers))
767 self.insertShort(0, len(self.questions))
767 self.insertShort(0, len(self.questions))
768 self.insertShort(0, self.flags)
768 self.insertShort(0, self.flags)
769 if self.multicast:
769 if self.multicast:
770 self.insertShort(0, 0)
770 self.insertShort(0, 0)
771 else:
771 else:
772 self.insertShort(0, self.id)
772 self.insertShort(0, self.id)
773 return ''.join(self.data)
773 return ''.join(self.data)
774
774
775
775
776 class DNSCache(object):
776 class DNSCache(object):
777 """A cache of DNS entries"""
777 """A cache of DNS entries"""
778
778
779 def __init__(self):
779 def __init__(self):
780 self.cache = {}
780 self.cache = {}
781
781
782 def add(self, entry):
782 def add(self, entry):
783 """Adds an entry"""
783 """Adds an entry"""
784 try:
784 try:
785 list = self.cache[entry.key]
785 list = self.cache[entry.key]
786 except:
786 except:
787 list = self.cache[entry.key] = []
787 list = self.cache[entry.key] = []
788 list.append(entry)
788 list.append(entry)
789
789
790 def remove(self, entry):
790 def remove(self, entry):
791 """Removes an entry"""
791 """Removes an entry"""
792 try:
792 try:
793 list = self.cache[entry.key]
793 list = self.cache[entry.key]
794 list.remove(entry)
794 list.remove(entry)
795 except:
795 except:
796 pass
796 pass
797
797
798 def get(self, entry):
798 def get(self, entry):
799 """Gets an entry by key. Will return None if there is no
799 """Gets an entry by key. Will return None if there is no
800 matching entry."""
800 matching entry."""
801 try:
801 try:
802 list = self.cache[entry.key]
802 list = self.cache[entry.key]
803 return list[list.index(entry)]
803 return list[list.index(entry)]
804 except:
804 except:
805 return None
805 return None
806
806
807 def getByDetails(self, name, type, clazz):
807 def getByDetails(self, name, type, clazz):
808 """Gets an entry by details. Will return None if there is
808 """Gets an entry by details. Will return None if there is
809 no matching entry."""
809 no matching entry."""
810 entry = DNSEntry(name, type, clazz)
810 entry = DNSEntry(name, type, clazz)
811 return self.get(entry)
811 return self.get(entry)
812
812
813 def entriesWithName(self, name):
813 def entriesWithName(self, name):
814 """Returns a list of entries whose key matches the name."""
814 """Returns a list of entries whose key matches the name."""
815 try:
815 try:
816 return self.cache[name]
816 return self.cache[name]
817 except:
817 except:
818 return []
818 return []
819
819
820 def entries(self):
820 def entries(self):
821 """Returns a list of all entries"""
821 """Returns a list of all entries"""
822 def add(x, y): return x+y
822 def add(x, y): return x+y
823 try:
823 try:
824 return reduce(add, self.cache.values())
824 return reduce(add, self.cache.values())
825 except:
825 except:
826 return []
826 return []
827
827
828
828
829 class Engine(threading.Thread):
829 class Engine(threading.Thread):
830 """An engine wraps read access to sockets, allowing objects that
830 """An engine wraps read access to sockets, allowing objects that
831 need to receive data from sockets to be called back when the
831 need to receive data from sockets to be called back when the
832 sockets are ready.
832 sockets are ready.
833
833
834 A reader needs a handle_read() method, which is called when the socket
834 A reader needs a handle_read() method, which is called when the socket
835 it is interested in is ready for reading.
835 it is interested in is ready for reading.
836
836
837 Writers are not implemented here, because we only send short
837 Writers are not implemented here, because we only send short
838 packets.
838 packets.
839 """
839 """
840
840
841 def __init__(self, zeroconf):
841 def __init__(self, zeroconf):
842 threading.Thread.__init__(self)
842 threading.Thread.__init__(self)
843 self.zeroconf = zeroconf
843 self.zeroconf = zeroconf
844 self.readers = {} # maps socket to reader
844 self.readers = {} # maps socket to reader
845 self.timeout = 5
845 self.timeout = 5
846 self.condition = threading.Condition()
846 self.condition = threading.Condition()
847 self.start()
847 self.start()
848
848
849 def run(self):
849 def run(self):
850 while not globals()['_GLOBAL_DONE']:
850 while not globals()['_GLOBAL_DONE']:
851 rs = self.getReaders()
851 rs = self.getReaders()
852 if len(rs) == 0:
852 if len(rs) == 0:
853 # No sockets to manage, but we wait for the timeout
853 # No sockets to manage, but we wait for the timeout
854 # or addition of a socket
854 # or addition of a socket
855 #
855 #
856 self.condition.acquire()
856 self.condition.acquire()
857 self.condition.wait(self.timeout)
857 self.condition.wait(self.timeout)
858 self.condition.release()
858 self.condition.release()
859 else:
859 else:
860 try:
860 try:
861 rr, wr, er = select.select(rs, [], [], self.timeout)
861 rr, wr, er = select.select(rs, [], [], self.timeout)
862 for socket in rr:
862 for socket in rr:
863 try:
863 try:
864 self.readers[socket].handle_read()
864 self.readers[socket].handle_read()
865 except:
865 except:
866 traceback.print_exc()
866 traceback.print_exc()
867 except:
867 except:
868 pass
868 pass
869
869
870 def getReaders(self):
870 def getReaders(self):
871 self.condition.acquire()
871 self.condition.acquire()
872 result = self.readers.keys()
872 result = self.readers.keys()
873 self.condition.release()
873 self.condition.release()
874 return result
874 return result
875
875
876 def addReader(self, reader, socket):
876 def addReader(self, reader, socket):
877 self.condition.acquire()
877 self.condition.acquire()
878 self.readers[socket] = reader
878 self.readers[socket] = reader
879 self.condition.notify()
879 self.condition.notify()
880 self.condition.release()
880 self.condition.release()
881
881
882 def delReader(self, socket):
882 def delReader(self, socket):
883 self.condition.acquire()
883 self.condition.acquire()
884 del(self.readers[socket])
884 del(self.readers[socket])
885 self.condition.notify()
885 self.condition.notify()
886 self.condition.release()
886 self.condition.release()
887
887
888 def notify(self):
888 def notify(self):
889 self.condition.acquire()
889 self.condition.acquire()
890 self.condition.notify()
890 self.condition.notify()
891 self.condition.release()
891 self.condition.release()
892
892
893 class Listener(object):
893 class Listener(object):
894 """A Listener is used by this module to listen on the multicast
894 """A Listener is used by this module to listen on the multicast
895 group to which DNS messages are sent, allowing the implementation
895 group to which DNS messages are sent, allowing the implementation
896 to cache information as it arrives.
896 to cache information as it arrives.
897
897
898 It requires registration with an Engine object in order to have
898 It requires registration with an Engine object in order to have
899 the read() method called when a socket is availble for reading."""
899 the read() method called when a socket is availble for reading."""
900
900
901 def __init__(self, zeroconf):
901 def __init__(self, zeroconf):
902 self.zeroconf = zeroconf
902 self.zeroconf = zeroconf
903 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
903 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
904
904
905 def handle_read(self):
905 def handle_read(self):
906 data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
906 data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
907 self.data = data
907 self.data = data
908 msg = DNSIncoming(data)
908 msg = DNSIncoming(data)
909 if msg.isQuery():
909 if msg.isQuery():
910 # Always multicast responses
910 # Always multicast responses
911 #
911 #
912 if port == _MDNS_PORT:
912 if port == _MDNS_PORT:
913 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
913 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
914 # If it's not a multicast query, reply via unicast
914 # If it's not a multicast query, reply via unicast
915 # and multicast
915 # and multicast
916 #
916 #
917 elif port == _DNS_PORT:
917 elif port == _DNS_PORT:
918 self.zeroconf.handleQuery(msg, addr, port)
918 self.zeroconf.handleQuery(msg, addr, port)
919 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
919 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
920 else:
920 else:
921 self.zeroconf.handleResponse(msg)
921 self.zeroconf.handleResponse(msg)
922
922
923
923
924 class Reaper(threading.Thread):
924 class Reaper(threading.Thread):
925 """A Reaper is used by this module to remove cache entries that
925 """A Reaper is used by this module to remove cache entries that
926 have expired."""
926 have expired."""
927
927
928 def __init__(self, zeroconf):
928 def __init__(self, zeroconf):
929 threading.Thread.__init__(self)
929 threading.Thread.__init__(self)
930 self.zeroconf = zeroconf
930 self.zeroconf = zeroconf
931 self.start()
931 self.start()
932
932
933 def run(self):
933 def run(self):
934 while 1:
934 while 1:
935 self.zeroconf.wait(10 * 1000)
935 self.zeroconf.wait(10 * 1000)
936 if globals()['_GLOBAL_DONE']:
936 if globals()['_GLOBAL_DONE']:
937 return
937 return
938 now = currentTimeMillis()
938 now = currentTimeMillis()
939 for record in self.zeroconf.cache.entries():
939 for record in self.zeroconf.cache.entries():
940 if record.isExpired(now):
940 if record.isExpired(now):
941 self.zeroconf.updateRecord(now, record)
941 self.zeroconf.updateRecord(now, record)
942 self.zeroconf.cache.remove(record)
942 self.zeroconf.cache.remove(record)
943
943
944
944
945 class ServiceBrowser(threading.Thread):
945 class ServiceBrowser(threading.Thread):
946 """Used to browse for a service of a specific type.
946 """Used to browse for a service of a specific type.
947
947
948 The listener object will have its addService() and
948 The listener object will have its addService() and
949 removeService() methods called when this browser
949 removeService() methods called when this browser
950 discovers changes in the services availability."""
950 discovers changes in the services availability."""
951
951
952 def __init__(self, zeroconf, type, listener):
952 def __init__(self, zeroconf, type, listener):
953 """Creates a browser for a specific type"""
953 """Creates a browser for a specific type"""
954 threading.Thread.__init__(self)
954 threading.Thread.__init__(self)
955 self.zeroconf = zeroconf
955 self.zeroconf = zeroconf
956 self.type = type
956 self.type = type
957 self.listener = listener
957 self.listener = listener
958 self.services = {}
958 self.services = {}
959 self.nextTime = currentTimeMillis()
959 self.nextTime = currentTimeMillis()
960 self.delay = _BROWSER_TIME
960 self.delay = _BROWSER_TIME
961 self.list = []
961 self.list = []
962
962
963 self.done = 0
963 self.done = 0
964
964
965 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
965 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
966 self.start()
966 self.start()
967
967
968 def updateRecord(self, zeroconf, now, record):
968 def updateRecord(self, zeroconf, now, record):
969 """Callback invoked by Zeroconf when new information arrives.
969 """Callback invoked by Zeroconf when new information arrives.
970
970
971 Updates information required by browser in the Zeroconf cache."""
971 Updates information required by browser in the Zeroconf cache."""
972 if record.type == _TYPE_PTR and record.name == self.type:
972 if record.type == _TYPE_PTR and record.name == self.type:
973 expired = record.isExpired(now)
973 expired = record.isExpired(now)
974 try:
974 try:
975 oldrecord = self.services[record.alias.lower()]
975 oldrecord = self.services[record.alias.lower()]
976 if not expired:
976 if not expired:
977 oldrecord.resetTTL(record)
977 oldrecord.resetTTL(record)
978 else:
978 else:
979 del(self.services[record.alias.lower()])
979 del(self.services[record.alias.lower()])
980 callback = lambda x: self.listener.removeService(x, self.type, record.alias)
980 callback = lambda x: self.listener.removeService(x, self.type, record.alias)
981 self.list.append(callback)
981 self.list.append(callback)
982 return
982 return
983 except:
983 except:
984 if not expired:
984 if not expired:
985 self.services[record.alias.lower()] = record
985 self.services[record.alias.lower()] = record
986 callback = lambda x: self.listener.addService(x, self.type, record.alias)
986 callback = lambda x: self.listener.addService(x, self.type, record.alias)
987 self.list.append(callback)
987 self.list.append(callback)
988
988
989 expires = record.getExpirationTime(75)
989 expires = record.getExpirationTime(75)
990 if expires < self.nextTime:
990 if expires < self.nextTime:
991 self.nextTime = expires
991 self.nextTime = expires
992
992
993 def cancel(self):
993 def cancel(self):
994 self.done = 1
994 self.done = 1
995 self.zeroconf.notifyAll()
995 self.zeroconf.notifyAll()
996
996
997 def run(self):
997 def run(self):
998 while 1:
998 while 1:
999 event = None
999 event = None
1000 now = currentTimeMillis()
1000 now = currentTimeMillis()
1001 if len(self.list) == 0 and self.nextTime > now:
1001 if len(self.list) == 0 and self.nextTime > now:
1002 self.zeroconf.wait(self.nextTime - now)
1002 self.zeroconf.wait(self.nextTime - now)
1003 if globals()['_GLOBAL_DONE'] or self.done:
1003 if globals()['_GLOBAL_DONE'] or self.done:
1004 return
1004 return
1005 now = currentTimeMillis()
1005 now = currentTimeMillis()
1006
1006
1007 if self.nextTime <= now:
1007 if self.nextTime <= now:
1008 out = DNSOutgoing(_FLAGS_QR_QUERY)
1008 out = DNSOutgoing(_FLAGS_QR_QUERY)
1009 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1009 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1010 for record in self.services.values():
1010 for record in self.services.values():
1011 if not record.isExpired(now):
1011 if not record.isExpired(now):
1012 out.addAnswerAtTime(record, now)
1012 out.addAnswerAtTime(record, now)
1013 self.zeroconf.send(out)
1013 self.zeroconf.send(out)
1014 self.nextTime = now + self.delay
1014 self.nextTime = now + self.delay
1015 self.delay = min(20 * 1000, self.delay * 2)
1015 self.delay = min(20 * 1000, self.delay * 2)
1016
1016
1017 if len(self.list) > 0:
1017 if len(self.list) > 0:
1018 event = self.list.pop(0)
1018 event = self.list.pop(0)
1019
1019
1020 if event is not None:
1020 if event is not None:
1021 event(self.zeroconf)
1021 event(self.zeroconf)
1022
1022
1023
1023
1024 class ServiceInfo(object):
1024 class ServiceInfo(object):
1025 """Service information"""
1025 """Service information"""
1026
1026
1027 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
1027 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
1028 """Create a service description.
1028 """Create a service description.
1029
1029
1030 type: fully qualified service type name
1030 type: fully qualified service type name
1031 name: fully qualified service name
1031 name: fully qualified service name
1032 address: IP address as unsigned short, network byte order
1032 address: IP address as unsigned short, network byte order
1033 port: port that the service runs on
1033 port: port that the service runs on
1034 weight: weight of the service
1034 weight: weight of the service
1035 priority: priority of the service
1035 priority: priority of the service
1036 properties: dictionary of properties (or a string holding the bytes for the text field)
1036 properties: dictionary of properties (or a string holding the bytes for the text field)
1037 server: fully qualified name for service host (defaults to name)"""
1037 server: fully qualified name for service host (defaults to name)"""
1038
1038
1039 if not name.endswith(type):
1039 if not name.endswith(type):
1040 raise BadTypeInNameException
1040 raise BadTypeInNameException
1041 self.type = type
1041 self.type = type
1042 self.name = name
1042 self.name = name
1043 self.address = address
1043 self.address = address
1044 self.port = port
1044 self.port = port
1045 self.weight = weight
1045 self.weight = weight
1046 self.priority = priority
1046 self.priority = priority
1047 if server:
1047 if server:
1048 self.server = server
1048 self.server = server
1049 else:
1049 else:
1050 self.server = name
1050 self.server = name
1051 self.setProperties(properties)
1051 self.setProperties(properties)
1052
1052
1053 def setProperties(self, properties):
1053 def setProperties(self, properties):
1054 """Sets properties and text of this info from a dictionary"""
1054 """Sets properties and text of this info from a dictionary"""
1055 if isinstance(properties, dict):
1055 if isinstance(properties, dict):
1056 self.properties = properties
1056 self.properties = properties
1057 list = []
1057 list = []
1058 result = ''
1058 result = ''
1059 for key in properties:
1059 for key in properties:
1060 value = properties[key]
1060 value = properties[key]
1061 if value is None:
1061 if value is None:
1062 suffix = ''
1062 suffix = ''
1063 elif isinstance(value, str):
1063 elif isinstance(value, str):
1064 suffix = value
1064 suffix = value
1065 elif isinstance(value, int):
1065 elif isinstance(value, int):
1066 if value:
1066 if value:
1067 suffix = 'true'
1067 suffix = 'true'
1068 else:
1068 else:
1069 suffix = 'false'
1069 suffix = 'false'
1070 else:
1070 else:
1071 suffix = ''
1071 suffix = ''
1072 list.append('='.join((key, suffix)))
1072 list.append('='.join((key, suffix)))
1073 for item in list:
1073 for item in list:
1074 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
1074 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
1075 self.text = result
1075 self.text = result
1076 else:
1076 else:
1077 self.text = properties
1077 self.text = properties
1078
1078
1079 def setText(self, text):
1079 def setText(self, text):
1080 """Sets properties and text given a text field"""
1080 """Sets properties and text given a text field"""
1081 self.text = text
1081 self.text = text
1082 try:
1082 try:
1083 result = {}
1083 result = {}
1084 end = len(text)
1084 end = len(text)
1085 index = 0
1085 index = 0
1086 strs = []
1086 strs = []
1087 while index < end:
1087 while index < end:
1088 length = ord(text[index])
1088 length = ord(text[index])
1089 index += 1
1089 index += 1
1090 strs.append(text[index:index+length])
1090 strs.append(text[index:index+length])
1091 index += length
1091 index += length
1092
1092
1093 for s in strs:
1093 for s in strs:
1094 eindex = s.find('=')
1094 eindex = s.find('=')
1095 if eindex == -1:
1095 if eindex == -1:
1096 # No equals sign at all
1096 # No equals sign at all
1097 key = s
1097 key = s
1098 value = 0
1098 value = 0
1099 else:
1099 else:
1100 key = s[:eindex]
1100 key = s[:eindex]
1101 value = s[eindex+1:]
1101 value = s[eindex+1:]
1102 if value == 'true':
1102 if value == 'true':
1103 value = 1
1103 value = 1
1104 elif value == 'false' or not value:
1104 elif value == 'false' or not value:
1105 value = 0
1105 value = 0
1106
1106
1107 # Only update non-existent properties
1107 # Only update non-existent properties
1108 if key and result.get(key) == None:
1108 if key and result.get(key) == None:
1109 result[key] = value
1109 result[key] = value
1110
1110
1111 self.properties = result
1111 self.properties = result
1112 except:
1112 except:
1113 traceback.print_exc()
1113 traceback.print_exc()
1114 self.properties = None
1114 self.properties = None
1115
1115
1116 def getType(self):
1116 def getType(self):
1117 """Type accessor"""
1117 """Type accessor"""
1118 return self.type
1118 return self.type
1119
1119
1120 def getName(self):
1120 def getName(self):
1121 """Name accessor"""
1121 """Name accessor"""
1122 if self.type is not None and self.name.endswith("." + self.type):
1122 if self.type is not None and self.name.endswith("." + self.type):
1123 return self.name[:len(self.name) - len(self.type) - 1]
1123 return self.name[:len(self.name) - len(self.type) - 1]
1124 return self.name
1124 return self.name
1125
1125
1126 def getAddress(self):
1126 def getAddress(self):
1127 """Address accessor"""
1127 """Address accessor"""
1128 return self.address
1128 return self.address
1129
1129
1130 def getPort(self):
1130 def getPort(self):
1131 """Port accessor"""
1131 """Port accessor"""
1132 return self.port
1132 return self.port
1133
1133
1134 def getPriority(self):
1134 def getPriority(self):
1135 """Pirority accessor"""
1135 """Pirority accessor"""
1136 return self.priority
1136 return self.priority
1137
1137
1138 def getWeight(self):
1138 def getWeight(self):
1139 """Weight accessor"""
1139 """Weight accessor"""
1140 return self.weight
1140 return self.weight
1141
1141
1142 def getProperties(self):
1142 def getProperties(self):
1143 """Properties accessor"""
1143 """Properties accessor"""
1144 return self.properties
1144 return self.properties
1145
1145
1146 def getText(self):
1146 def getText(self):
1147 """Text accessor"""
1147 """Text accessor"""
1148 return self.text
1148 return self.text
1149
1149
1150 def getServer(self):
1150 def getServer(self):
1151 """Server accessor"""
1151 """Server accessor"""
1152 return self.server
1152 return self.server
1153
1153
1154 def updateRecord(self, zeroconf, now, record):
1154 def updateRecord(self, zeroconf, now, record):
1155 """Updates service information from a DNS record"""
1155 """Updates service information from a DNS record"""
1156 if record is not None and not record.isExpired(now):
1156 if record is not None and not record.isExpired(now):
1157 if record.type == _TYPE_A:
1157 if record.type == _TYPE_A:
1158 #if record.name == self.name:
1158 #if record.name == self.name:
1159 if record.name == self.server:
1159 if record.name == self.server:
1160 self.address = record.address
1160 self.address = record.address
1161 elif record.type == _TYPE_SRV:
1161 elif record.type == _TYPE_SRV:
1162 if record.name == self.name:
1162 if record.name == self.name:
1163 self.server = record.server
1163 self.server = record.server
1164 self.port = record.port
1164 self.port = record.port
1165 self.weight = record.weight
1165 self.weight = record.weight
1166 self.priority = record.priority
1166 self.priority = record.priority
1167 #self.address = None
1167 #self.address = None
1168 self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
1168 self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
1169 elif record.type == _TYPE_TXT:
1169 elif record.type == _TYPE_TXT:
1170 if record.name == self.name:
1170 if record.name == self.name:
1171 self.setText(record.text)
1171 self.setText(record.text)
1172
1172
1173 def request(self, zeroconf, timeout):
1173 def request(self, zeroconf, timeout):
1174 """Returns true if the service could be discovered on the
1174 """Returns true if the service could be discovered on the
1175 network, and updates this object with details discovered.
1175 network, and updates this object with details discovered.
1176 """
1176 """
1177 now = currentTimeMillis()
1177 now = currentTimeMillis()
1178 delay = _LISTENER_TIME
1178 delay = _LISTENER_TIME
1179 next = now + delay
1179 next = now + delay
1180 last = now + timeout
1180 last = now + timeout
1181 result = 0
1181 result = 0
1182 try:
1182 try:
1183 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
1183 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
1184 while self.server is None or self.address is None or self.text is None:
1184 while self.server is None or self.address is None or self.text is None:
1185 if last <= now:
1185 if last <= now:
1186 return 0
1186 return 0
1187 if next <= now:
1187 if next <= now:
1188 out = DNSOutgoing(_FLAGS_QR_QUERY)
1188 out = DNSOutgoing(_FLAGS_QR_QUERY)
1189 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
1189 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
1190 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
1190 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
1191 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
1191 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
1192 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
1192 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
1193 if self.server is not None:
1193 if self.server is not None:
1194 out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1194 out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1195 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
1195 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
1196 zeroconf.send(out)
1196 zeroconf.send(out)
1197 next = now + delay
1197 next = now + delay
1198 delay = delay * 2
1198 delay = delay * 2
1199
1199
1200 zeroconf.wait(min(next, last) - now)
1200 zeroconf.wait(min(next, last) - now)
1201 now = currentTimeMillis()
1201 now = currentTimeMillis()
1202 result = 1
1202 result = 1
1203 finally:
1203 finally:
1204 zeroconf.removeListener(self)
1204 zeroconf.removeListener(self)
1205
1205
1206 return result
1206 return result
1207
1207
1208 def __eq__(self, other):
1208 def __eq__(self, other):
1209 """Tests equality of service name"""
1209 """Tests equality of service name"""
1210 if isinstance(other, ServiceInfo):
1210 if isinstance(other, ServiceInfo):
1211 return other.name == self.name
1211 return other.name == self.name
1212 return 0
1212 return 0
1213
1213
1214 def __ne__(self, other):
1214 def __ne__(self, other):
1215 """Non-equality test"""
1215 """Non-equality test"""
1216 return not self.__eq__(other)
1216 return not self.__eq__(other)
1217
1217
1218 def __repr__(self):
1218 def __repr__(self):
1219 """String representation"""
1219 """String representation"""
1220 result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
1220 result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
1221 if self.text is None:
1221 if self.text is None:
1222 result += "None"
1222 result += "None"
1223 else:
1223 else:
1224 if len(self.text) < 20:
1224 if len(self.text) < 20:
1225 result += self.text
1225 result += self.text
1226 else:
1226 else:
1227 result += self.text[:17] + "..."
1227 result += self.text[:17] + "..."
1228 result += "]"
1228 result += "]"
1229 return result
1229 return result
1230
1230
1231
1231
1232 class Zeroconf(object):
1232 class Zeroconf(object):
1233 """Implementation of Zeroconf Multicast DNS Service Discovery
1233 """Implementation of Zeroconf Multicast DNS Service Discovery
1234
1234
1235 Supports registration, unregistration, queries and browsing.
1235 Supports registration, unregistration, queries and browsing.
1236 """
1236 """
1237 def __init__(self, bindaddress=None):
1237 def __init__(self, bindaddress=None):
1238 """Creates an instance of the Zeroconf class, establishing
1238 """Creates an instance of the Zeroconf class, establishing
1239 multicast communications, listening and reaping threads."""
1239 multicast communications, listening and reaping threads."""
1240 globals()['_GLOBAL_DONE'] = 0
1240 globals()['_GLOBAL_DONE'] = 0
1241 if bindaddress is None:
1241 if bindaddress is None:
1242 self.intf = socket.gethostbyname(socket.gethostname())
1242 self.intf = socket.gethostbyname(socket.gethostname())
1243 else:
1243 else:
1244 self.intf = bindaddress
1244 self.intf = bindaddress
1245 self.group = ('', _MDNS_PORT)
1245 self.group = ('', _MDNS_PORT)
1246 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1246 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1247 try:
1247 try:
1248 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1248 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1249 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1249 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1250 except:
1250 except:
1251 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1251 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1252 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1252 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1253 # Volume 2"), but some BSD-derived systems require
1253 # Volume 2"), but some BSD-derived systems require
1254 # SO_REUSEPORT to be specified explicity. Also, not all
1254 # SO_REUSEPORT to be specified explicity. Also, not all
1255 # versions of Python have SO_REUSEPORT available. So
1255 # versions of Python have SO_REUSEPORT available. So
1256 # if you're on a BSD-based system, and haven't upgraded
1256 # if you're on a BSD-based system, and haven't upgraded
1257 # to Python 2.3 yet, you may find this library doesn't
1257 # to Python 2.3 yet, you may find this library doesn't
1258 # work as expected.
1258 # work as expected.
1259 #
1259 #
1260 pass
1260 pass
1261 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
1261 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
1262 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
1262 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
1263 try:
1263 try:
1264 self.socket.bind(self.group)
1264 self.socket.bind(self.group)
1265 except:
1265 except:
1266 # Some versions of linux raise an exception even though
1266 # Some versions of linux raise an exception even though
1267 # the SO_REUSE* options have been set, so ignore it
1267 # the SO_REUSE* options have been set, so ignore it
1268 #
1268 #
1269 pass
1269 pass
1270 #self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
1270 #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_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1271 self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1272
1272
1273 self.listeners = []
1273 self.listeners = []
1274 self.browsers = []
1274 self.browsers = []
1275 self.services = {}
1275 self.services = {}
1276 self.servicetypes = {}
1276 self.servicetypes = {}
1277
1277
1278 self.cache = DNSCache()
1278 self.cache = DNSCache()
1279
1279
1280 self.condition = threading.Condition()
1280 self.condition = threading.Condition()
1281
1281
1282 self.engine = Engine(self)
1282 self.engine = Engine(self)
1283 self.listener = Listener(self)
1283 self.listener = Listener(self)
1284 self.reaper = Reaper(self)
1284 self.reaper = Reaper(self)
1285
1285
1286 def isLoopback(self):
1286 def isLoopback(self):
1287 return self.intf.startswith("127.0.0.1")
1287 return self.intf.startswith("127.0.0.1")
1288
1288
1289 def isLinklocal(self):
1289 def isLinklocal(self):
1290 return self.intf.startswith("169.254.")
1290 return self.intf.startswith("169.254.")
1291
1291
1292 def wait(self, timeout):
1292 def wait(self, timeout):
1293 """Calling thread waits for a given number of milliseconds or
1293 """Calling thread waits for a given number of milliseconds or
1294 until notified."""
1294 until notified."""
1295 self.condition.acquire()
1295 self.condition.acquire()
1296 self.condition.wait(timeout/1000)
1296 self.condition.wait(timeout/1000)
1297 self.condition.release()
1297 self.condition.release()
1298
1298
1299 def notifyAll(self):
1299 def notifyAll(self):
1300 """Notifies all waiting threads"""
1300 """Notifies all waiting threads"""
1301 self.condition.acquire()
1301 self.condition.acquire()
1302 self.condition.notifyAll()
1302 self.condition.notifyAll()
1303 self.condition.release()
1303 self.condition.release()
1304
1304
1305 def getServiceInfo(self, type, name, timeout=3000):
1305 def getServiceInfo(self, type, name, timeout=3000):
1306 """Returns network's service information for a particular
1306 """Returns network's service information for a particular
1307 name and type, or None if no service matches by the timeout,
1307 name and type, or None if no service matches by the timeout,
1308 which defaults to 3 seconds."""
1308 which defaults to 3 seconds."""
1309 info = ServiceInfo(type, name)
1309 info = ServiceInfo(type, name)
1310 if info.request(self, timeout):
1310 if info.request(self, timeout):
1311 return info
1311 return info
1312 return None
1312 return None
1313
1313
1314 def addServiceListener(self, type, listener):
1314 def addServiceListener(self, type, listener):
1315 """Adds a listener for a particular service type. This object
1315 """Adds a listener for a particular service type. This object
1316 will then have its updateRecord method called when information
1316 will then have its updateRecord method called when information
1317 arrives for that type."""
1317 arrives for that type."""
1318 self.removeServiceListener(listener)
1318 self.removeServiceListener(listener)
1319 self.browsers.append(ServiceBrowser(self, type, listener))
1319 self.browsers.append(ServiceBrowser(self, type, listener))
1320
1320
1321 def removeServiceListener(self, listener):
1321 def removeServiceListener(self, listener):
1322 """Removes a listener from the set that is currently listening."""
1322 """Removes a listener from the set that is currently listening."""
1323 for browser in self.browsers:
1323 for browser in self.browsers:
1324 if browser.listener == listener:
1324 if browser.listener == listener:
1325 browser.cancel()
1325 browser.cancel()
1326 del(browser)
1326 del(browser)
1327
1327
1328 def registerService(self, info, ttl=_DNS_TTL):
1328 def registerService(self, info, ttl=_DNS_TTL):
1329 """Registers service information to the network with a default TTL
1329 """Registers service information to the network with a default TTL
1330 of 60 seconds. Zeroconf will then respond to requests for
1330 of 60 seconds. Zeroconf will then respond to requests for
1331 information for that service. The name of the service may be
1331 information for that service. The name of the service may be
1332 changed if needed to make it unique on the network."""
1332 changed if needed to make it unique on the network."""
1333 self.checkService(info)
1333 self.checkService(info)
1334 self.services[info.name.lower()] = info
1334 self.services[info.name.lower()] = info
1335 if self.servicetypes.has_key(info.type):
1335 if self.servicetypes.has_key(info.type):
1336 self.servicetypes[info.type]+=1
1336 self.servicetypes[info.type]+=1
1337 else:
1337 else:
1338 self.servicetypes[info.type]=1
1338 self.servicetypes[info.type]=1
1339 now = currentTimeMillis()
1339 now = currentTimeMillis()
1340 nextTime = now
1340 nextTime = now
1341 i = 0
1341 i = 0
1342 while i < 3:
1342 while i < 3:
1343 if now < nextTime:
1343 if now < nextTime:
1344 self.wait(nextTime - now)
1344 self.wait(nextTime - now)
1345 now = currentTimeMillis()
1345 now = currentTimeMillis()
1346 continue
1346 continue
1347 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1347 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1348 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
1348 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
1349 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
1349 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
1350 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
1350 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
1351 if info.address:
1351 if info.address:
1352 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
1352 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
1353 self.send(out)
1353 self.send(out)
1354 i += 1
1354 i += 1
1355 nextTime += _REGISTER_TIME
1355 nextTime += _REGISTER_TIME
1356
1356
1357 def unregisterService(self, info):
1357 def unregisterService(self, info):
1358 """Unregister a service."""
1358 """Unregister a service."""
1359 try:
1359 try:
1360 del(self.services[info.name.lower()])
1360 del(self.services[info.name.lower()])
1361 if self.servicetypes[info.type]>1:
1361 if self.servicetypes[info.type]>1:
1362 self.servicetypes[info.type]-=1
1362 self.servicetypes[info.type]-=1
1363 else:
1363 else:
1364 del self.servicetypes[info.type]
1364 del self.servicetypes[info.type]
1365 except:
1365 except:
1366 pass
1366 pass
1367 now = currentTimeMillis()
1367 now = currentTimeMillis()
1368 nextTime = now
1368 nextTime = now
1369 i = 0
1369 i = 0
1370 while i < 3:
1370 while i < 3:
1371 if now < nextTime:
1371 if now < nextTime:
1372 self.wait(nextTime - now)
1372 self.wait(nextTime - now)
1373 now = currentTimeMillis()
1373 now = currentTimeMillis()
1374 continue
1374 continue
1375 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1375 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1376 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1376 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1377 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
1377 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
1378 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1378 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1379 if info.address:
1379 if info.address:
1380 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1380 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1381 self.send(out)
1381 self.send(out)
1382 i += 1
1382 i += 1
1383 nextTime += _UNREGISTER_TIME
1383 nextTime += _UNREGISTER_TIME
1384
1384
1385 def unregisterAllServices(self):
1385 def unregisterAllServices(self):
1386 """Unregister all registered services."""
1386 """Unregister all registered services."""
1387 if len(self.services) > 0:
1387 if len(self.services) > 0:
1388 now = currentTimeMillis()
1388 now = currentTimeMillis()
1389 nextTime = now
1389 nextTime = now
1390 i = 0
1390 i = 0
1391 while i < 3:
1391 while i < 3:
1392 if now < nextTime:
1392 if now < nextTime:
1393 self.wait(nextTime - now)
1393 self.wait(nextTime - now)
1394 now = currentTimeMillis()
1394 now = currentTimeMillis()
1395 continue
1395 continue
1396 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1396 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1397 for info in self.services.values():
1397 for info in self.services.values():
1398 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1398 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1399 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
1399 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
1400 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1400 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1401 if info.address:
1401 if info.address:
1402 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1402 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1403 self.send(out)
1403 self.send(out)
1404 i += 1
1404 i += 1
1405 nextTime += _UNREGISTER_TIME
1405 nextTime += _UNREGISTER_TIME
1406
1406
1407 def checkService(self, info):
1407 def checkService(self, info):
1408 """Checks the network for a unique service name, modifying the
1408 """Checks the network for a unique service name, modifying the
1409 ServiceInfo passed in if it is not unique."""
1409 ServiceInfo passed in if it is not unique."""
1410 now = currentTimeMillis()
1410 now = currentTimeMillis()
1411 nextTime = now
1411 nextTime = now
1412 i = 0
1412 i = 0
1413 while i < 3:
1413 while i < 3:
1414 for record in self.cache.entriesWithName(info.type):
1414 for record in self.cache.entriesWithName(info.type):
1415 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
1415 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
1416 if (info.name.find('.') < 0):
1416 if (info.name.find('.') < 0):
1417 info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
1417 info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
1418 self.checkService(info)
1418 self.checkService(info)
1419 return
1419 return
1420 raise NonUniqueNameException
1420 raise NonUniqueNameException
1421 if now < nextTime:
1421 if now < nextTime:
1422 self.wait(nextTime - now)
1422 self.wait(nextTime - now)
1423 now = currentTimeMillis()
1423 now = currentTimeMillis()
1424 continue
1424 continue
1425 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1425 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1426 self.debug = out
1426 self.debug = out
1427 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1427 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1428 out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1428 out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1429 self.send(out)
1429 self.send(out)
1430 i += 1
1430 i += 1
1431 nextTime += _CHECK_TIME
1431 nextTime += _CHECK_TIME
1432
1432
1433 def addListener(self, listener, question):
1433 def addListener(self, listener, question):
1434 """Adds a listener for a given question. The listener will have
1434 """Adds a listener for a given question. The listener will have
1435 its updateRecord method called when information is available to
1435 its updateRecord method called when information is available to
1436 answer the question."""
1436 answer the question."""
1437 now = currentTimeMillis()
1437 now = currentTimeMillis()
1438 self.listeners.append(listener)
1438 self.listeners.append(listener)
1439 if question is not None:
1439 if question is not None:
1440 for record in self.cache.entriesWithName(question.name):
1440 for record in self.cache.entriesWithName(question.name):
1441 if question.answeredBy(record) and not record.isExpired(now):
1441 if question.answeredBy(record) and not record.isExpired(now):
1442 listener.updateRecord(self, now, record)
1442 listener.updateRecord(self, now, record)
1443 self.notifyAll()
1443 self.notifyAll()
1444
1444
1445 def removeListener(self, listener):
1445 def removeListener(self, listener):
1446 """Removes a listener."""
1446 """Removes a listener."""
1447 try:
1447 try:
1448 self.listeners.remove(listener)
1448 self.listeners.remove(listener)
1449 self.notifyAll()
1449 self.notifyAll()
1450 except:
1450 except:
1451 pass
1451 pass
1452
1452
1453 def updateRecord(self, now, rec):
1453 def updateRecord(self, now, rec):
1454 """Used to notify listeners of new information that has updated
1454 """Used to notify listeners of new information that has updated
1455 a record."""
1455 a record."""
1456 for listener in self.listeners:
1456 for listener in self.listeners:
1457 listener.updateRecord(self, now, rec)
1457 listener.updateRecord(self, now, rec)
1458 self.notifyAll()
1458 self.notifyAll()
1459
1459
1460 def handleResponse(self, msg):
1460 def handleResponse(self, msg):
1461 """Deal with incoming response packets. All answers
1461 """Deal with incoming response packets. All answers
1462 are held in the cache, and listeners are notified."""
1462 are held in the cache, and listeners are notified."""
1463 now = currentTimeMillis()
1463 now = currentTimeMillis()
1464 for record in msg.answers:
1464 for record in msg.answers:
1465 expired = record.isExpired(now)
1465 expired = record.isExpired(now)
1466 if record in self.cache.entries():
1466 if record in self.cache.entries():
1467 if expired:
1467 if expired:
1468 self.cache.remove(record)
1468 self.cache.remove(record)
1469 else:
1469 else:
1470 entry = self.cache.get(record)
1470 entry = self.cache.get(record)
1471 if entry is not None:
1471 if entry is not None:
1472 entry.resetTTL(record)
1472 entry.resetTTL(record)
1473 record = entry
1473 record = entry
1474 else:
1474 else:
1475 self.cache.add(record)
1475 self.cache.add(record)
1476
1476
1477 self.updateRecord(now, record)
1477 self.updateRecord(now, record)
1478
1478
1479 def handleQuery(self, msg, addr, port):
1479 def handleQuery(self, msg, addr, port):
1480 """Deal with incoming query packets. Provides a response if
1480 """Deal with incoming query packets. Provides a response if
1481 possible."""
1481 possible."""
1482 out = None
1482 out = None
1483
1483
1484 # Support unicast client responses
1484 # Support unicast client responses
1485 #
1485 #
1486 if port != _MDNS_PORT:
1486 if port != _MDNS_PORT:
1487 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1487 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1488 for question in msg.questions:
1488 for question in msg.questions:
1489 out.addQuestion(question)
1489 out.addQuestion(question)
1490
1490
1491 for question in msg.questions:
1491 for question in msg.questions:
1492 if question.type == _TYPE_PTR:
1492 if question.type == _TYPE_PTR:
1493 if question.name == "_services._dns-sd._udp.local.":
1493 if question.name == "_services._dns-sd._udp.local.":
1494 for stype in self.servicetypes.keys():
1494 for stype in self.servicetypes.keys():
1495 if out is None:
1495 if out is None:
1496 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1496 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1497 out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
1497 out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
1498 for service in self.services.values():
1498 for service in self.services.values():
1499 if question.name == service.type:
1499 if question.name == service.type:
1500 if out is None:
1500 if out is None:
1501 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1501 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1502 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
1502 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
1503 else:
1503 else:
1504 try:
1504 try:
1505 if out is None:
1505 if out is None:
1506 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1506 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1507
1507
1508 # Answer A record queries for any service addresses we know
1508 # Answer A record queries for any service addresses we know
1509 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1509 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1510 for service in self.services.values():
1510 for service in self.services.values():
1511 if service.server == question.name.lower():
1511 if service.server == question.name.lower():
1512 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1512 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1513
1513
1514 service = self.services.get(question.name.lower(), None)
1514 service = self.services.get(question.name.lower(), None)
1515 if not service: continue
1515 if not service: continue
1516
1516
1517 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1517 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1518 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
1518 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
1519 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1519 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1520 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1520 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1521 if question.type == _TYPE_SRV:
1521 if question.type == _TYPE_SRV:
1522 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1522 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1523 except:
1523 except:
1524 traceback.print_exc()
1524 traceback.print_exc()
1525
1525
1526 if out is not None and out.answers:
1526 if out is not None and out.answers:
1527 out.id = msg.id
1527 out.id = msg.id
1528 self.send(out, addr, port)
1528 self.send(out, addr, port)
1529
1529
1530 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
1530 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
1531 """Sends an outgoing packet."""
1531 """Sends an outgoing packet."""
1532 # This is a quick test to see if we can parse the packets we generate
1532 # This is a quick test to see if we can parse the packets we generate
1533 #temp = DNSIncoming(out.packet())
1533 #temp = DNSIncoming(out.packet())
1534 try:
1534 try:
1535 self.socket.sendto(out.packet(), 0, (addr, port))
1535 self.socket.sendto(out.packet(), 0, (addr, port))
1536 except:
1536 except:
1537 # Ignore this, it may be a temporary loss of network connection
1537 # Ignore this, it may be a temporary loss of network connection
1538 pass
1538 pass
1539
1539
1540 def close(self):
1540 def close(self):
1541 """Ends the background threads, and prevent this instance from
1541 """Ends the background threads, and prevent this instance from
1542 servicing further queries."""
1542 servicing further queries."""
1543 if globals()['_GLOBAL_DONE'] == 0:
1543 if globals()['_GLOBAL_DONE'] == 0:
1544 globals()['_GLOBAL_DONE'] = 1
1544 globals()['_GLOBAL_DONE'] = 1
1545 self.notifyAll()
1545 self.notifyAll()
1546 self.engine.notify()
1546 self.engine.notify()
1547 self.unregisterAllServices()
1547 self.unregisterAllServices()
1548 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1548 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.close()
1549 self.socket.close()
1550
1550
1551 # Test a few module features, including service registration, service
1551 # Test a few module features, including service registration, service
1552 # query (for Zoe), and service unregistration.
1552 # query (for Zoe), and service unregistration.
1553
1553
1554 if __name__ == '__main__':
1554 if __name__ == '__main__':
1555 print "Multicast DNS Service Discovery for Python, version", __version__
1555 print "Multicast DNS Service Discovery for Python, version", __version__
1556 r = Zeroconf()
1556 r = Zeroconf()
1557 print "1. Testing registration of a service..."
1557 print "1. Testing registration of a service..."
1558 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1558 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1559 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1559 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1560 print " Registering service..."
1560 print " Registering service..."
1561 r.registerService(info)
1561 r.registerService(info)
1562 print " Registration done."
1562 print " Registration done."
1563 print "2. Testing query of service information..."
1563 print "2. Testing query of service information..."
1564 print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
1564 print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
1565 print " Query done."
1565 print " Query done."
1566 print "3. Testing query of own service..."
1566 print "3. Testing query of own service..."
1567 print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
1567 print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
1568 print " Query done."
1568 print " Query done."
1569 print "4. Testing unregister of service information..."
1569 print "4. Testing unregister of service information..."
1570 r.unregisterService(info)
1570 r.unregisterService(info)
1571 print " Unregister done."
1571 print " Unregister done."
1572 r.close()
1572 r.close()
1573
1573
1574 # no-check-code
1574 # no-check-code
General Comments 0
You need to be logged in to leave comments. Login now