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