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