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