OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.names.test.test_dns -*- | |
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 """ | |
7 DNS protocol implementation. | |
8 | |
9 Future Plans: | |
10 - Get rid of some toplevels, maybe. | |
11 - Put in a better lookupRecordType implementation. | |
12 | |
13 @author: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>}, | |
14 U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | |
15 """ | |
16 | |
17 # System imports | |
18 import warnings | |
19 | |
20 import struct, random, types, socket | |
21 | |
22 try: | |
23 import cStringIO as StringIO | |
24 except ImportError: | |
25 import StringIO | |
26 | |
27 AF_INET6 = socket.AF_INET6 | |
28 | |
29 from zope.interface import implements, Interface | |
30 | |
31 | |
32 # Twisted imports | |
33 from twisted.internet import protocol, defer | |
34 from twisted.internet.error import CannotListenError | |
35 from twisted.python import log, failure | |
36 from twisted.python import util as tputil | |
37 from twisted.python import randbytes | |
38 | |
39 | |
40 def randomSource(): | |
41 """ | |
42 Wrapper around L{randbytes.secureRandom} to return 2 random chars. | |
43 """ | |
44 return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0] | |
45 | |
46 | |
47 PORT = 53 | |
48 | |
49 (A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT, | |
50 RP, AFSDB) = range(1, 19) | |
51 AAAA = 28 | |
52 SRV = 33 | |
53 A6 = 38 | |
54 DNAME = 39 | |
55 | |
56 QUERY_TYPES = { | |
57 A: 'A', | |
58 NS: 'NS', | |
59 MD: 'MD', | |
60 MF: 'MF', | |
61 CNAME: 'CNAME', | |
62 SOA: 'SOA', | |
63 MB: 'MB', | |
64 MG: 'MG', | |
65 MR: 'MR', | |
66 NULL: 'NULL', | |
67 WKS: 'WKS', | |
68 PTR: 'PTR', | |
69 HINFO: 'HINFO', | |
70 MINFO: 'MINFO', | |
71 MX: 'MX', | |
72 TXT: 'TXT', | |
73 RP: 'RP', | |
74 AFSDB: 'AFSDB', | |
75 | |
76 # 19 through 27? Eh, I'll get to 'em. | |
77 | |
78 AAAA: 'AAAA', | |
79 SRV: 'SRV', | |
80 | |
81 A6: 'A6', | |
82 DNAME: 'DNAME' | |
83 } | |
84 | |
85 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256) | |
86 | |
87 # "Extended" queries (Hey, half of these are deprecated, good job) | |
88 EXT_QUERIES = { | |
89 IXFR: 'IXFR', | |
90 AXFR: 'AXFR', | |
91 MAILB: 'MAILB', | |
92 MAILA: 'MAILA', | |
93 ALL_RECORDS: 'ALL_RECORDS' | |
94 } | |
95 | |
96 REV_TYPES = dict([ | |
97 (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items() | |
98 ]) | |
99 | |
100 IN, CS, CH, HS = range(1, 5) | |
101 ANY = 255 | |
102 | |
103 QUERY_CLASSES = { | |
104 IN: 'IN', | |
105 CS: 'CS', | |
106 CH: 'CH', | |
107 HS: 'HS', | |
108 ANY: 'ANY' | |
109 } | |
110 REV_CLASSES = dict([ | |
111 (v, k) for (k, v) in QUERY_CLASSES.items() | |
112 ]) | |
113 | |
114 | |
115 # Opcodes | |
116 OP_QUERY, OP_INVERSE, OP_STATUS = range(3) | |
117 OP_NOTIFY = 4 # RFC 1996 | |
118 OP_UPDATE = 5 # RFC 2136 | |
119 | |
120 | |
121 # Response Codes | |
122 OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6) | |
123 | |
124 class IRecord(Interface): | |
125 """ | |
126 An single entry in a zone of authority. | |
127 | |
128 @cvar TYPE: An indicator of what kind of record this is. | |
129 """ | |
130 | |
131 | |
132 # Backwards compatibility aliases - these should be deprecated or something I | |
133 # suppose. -exarkun | |
134 from twisted.names.error import DomainError, AuthoritativeDomainError | |
135 from twisted.names.error import DNSQueryTimeoutError | |
136 | |
137 | |
138 def str2time(s): | |
139 suffixes = ( | |
140 ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24), | |
141 ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365) | |
142 ) | |
143 if isinstance(s, types.StringType): | |
144 s = s.upper().strip() | |
145 for (suff, mult) in suffixes: | |
146 if s.endswith(suff): | |
147 return int(float(s[:-1]) * mult) | |
148 try: | |
149 s = int(s) | |
150 except ValueError: | |
151 raise ValueError, "Invalid time interval specifier: " + s | |
152 return s | |
153 | |
154 | |
155 def readPrecisely(file, l): | |
156 buff = file.read(l) | |
157 if len(buff) < l: | |
158 raise EOFError | |
159 return buff | |
160 | |
161 | |
162 class IEncodable(Interface): | |
163 """ | |
164 Interface for something which can be encoded to and decoded | |
165 from a file object. | |
166 """ | |
167 def encode(strio, compDict = None): | |
168 """ | |
169 Write a representation of this object to the given | |
170 file object. | |
171 | |
172 @type strio: File-like object | |
173 @param strio: The stream to which to write bytes | |
174 | |
175 @type compDict: C{dict} or C{None} | |
176 @param compDict: A dictionary of backreference addresses that have | |
177 have already been written to this stream and that may be used for | |
178 compression. | |
179 """ | |
180 | |
181 def decode(strio, length = None): | |
182 """ | |
183 Reconstruct an object from data read from the given | |
184 file object. | |
185 | |
186 @type strio: File-like object | |
187 @param strio: The stream from which bytes may be read | |
188 | |
189 @type length: C{int} or C{None} | |
190 @param length: The number of bytes in this RDATA field. Most | |
191 implementations can ignore this value. Only in the case of | |
192 records similar to TXT where the total length is in no way | |
193 encoded in the data is it necessary. | |
194 """ | |
195 | |
196 | |
197 class Name: | |
198 implements(IEncodable) | |
199 | |
200 def __init__(self, name=''): | |
201 assert isinstance(name, types.StringTypes), "%r is not a string" % (name
,) | |
202 self.name = name | |
203 | |
204 def encode(self, strio, compDict=None): | |
205 """ | |
206 Encode this Name into the appropriate byte format. | |
207 | |
208 @type strio: file | |
209 @param strio: The byte representation of this Name will be written to | |
210 this file. | |
211 | |
212 @type compDict: dict | |
213 @param compDict: dictionary of Names that have already been encoded | |
214 and whose addresses may be backreferenced by this Name (for the purpose | |
215 of reducing the message size). | |
216 """ | |
217 name = self.name | |
218 while name: | |
219 if compDict is not None: | |
220 if name in compDict: | |
221 strio.write( | |
222 struct.pack("!H", 0xc000 | compDict[name])) | |
223 return | |
224 else: | |
225 compDict[name] = strio.tell() + Message.headerSize | |
226 ind = name.find('.') | |
227 if ind > 0: | |
228 label, name = name[:ind], name[ind + 1:] | |
229 else: | |
230 label, name = name, '' | |
231 ind = len(label) | |
232 strio.write(chr(ind)) | |
233 strio.write(label) | |
234 strio.write(chr(0)) | |
235 | |
236 | |
237 def decode(self, strio, length=None): | |
238 """ | |
239 Decode a byte string into this Name. | |
240 | |
241 @type strio: file | |
242 @param strio: Bytes will be read from this file until the full Name | |
243 is decoded. | |
244 | |
245 @raise EOFError: Raised when there are not enough bytes available | |
246 from C{strio}. | |
247 """ | |
248 self.name = '' | |
249 off = 0 | |
250 while 1: | |
251 l = ord(readPrecisely(strio, 1)) | |
252 if l == 0: | |
253 if off > 0: | |
254 strio.seek(off) | |
255 return | |
256 if (l >> 6) == 3: | |
257 new_off = ((l&63) << 8 | |
258 | ord(readPrecisely(strio, 1))) | |
259 if off == 0: | |
260 off = strio.tell() | |
261 strio.seek(new_off) | |
262 continue | |
263 label = readPrecisely(strio, l) | |
264 if self.name == '': | |
265 self.name = label | |
266 else: | |
267 self.name = self.name + '.' + label | |
268 | |
269 def __eq__(self, other): | |
270 if isinstance(other, Name): | |
271 return str(self) == str(other) | |
272 return 0 | |
273 | |
274 | |
275 def __hash__(self): | |
276 return hash(str(self)) | |
277 | |
278 | |
279 def __str__(self): | |
280 return self.name | |
281 | |
282 class Query: | |
283 """ | |
284 Represent a single DNS query. | |
285 | |
286 @ivar name: The name about which this query is requesting information. | |
287 @ivar type: The query type. | |
288 @ivar cls: The query class. | |
289 """ | |
290 | |
291 implements(IEncodable) | |
292 | |
293 name = None | |
294 type = None | |
295 cls = None | |
296 | |
297 def __init__(self, name='', type=A, cls=IN): | |
298 """ | |
299 @type name: C{str} | |
300 @param name: The name about which to request information. | |
301 | |
302 @type type: C{int} | |
303 @param type: The query type. | |
304 | |
305 @type cls: C{int} | |
306 @param cls: The query class. | |
307 """ | |
308 self.name = Name(name) | |
309 self.type = type | |
310 self.cls = cls | |
311 | |
312 | |
313 def encode(self, strio, compDict=None): | |
314 self.name.encode(strio, compDict) | |
315 strio.write(struct.pack("!HH", self.type, self.cls)) | |
316 | |
317 | |
318 def decode(self, strio, length = None): | |
319 self.name.decode(strio) | |
320 buff = readPrecisely(strio, 4) | |
321 self.type, self.cls = struct.unpack("!HH", buff) | |
322 | |
323 | |
324 def __hash__(self): | |
325 return hash((str(self.name).lower(), self.type, self.cls)) | |
326 | |
327 | |
328 def __cmp__(self, other): | |
329 return isinstance(other, Query) and cmp( | |
330 (str(self.name).lower(), self.type, self.cls), | |
331 (str(other.name).lower(), other.type, other.cls) | |
332 ) or cmp(self.__class__, other.__class__) | |
333 | |
334 | |
335 def __str__(self): | |
336 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)'
% self.type)) | |
337 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls) | |
338 return '<Query %s %s %s>' % (self.name, t, c) | |
339 | |
340 | |
341 def __repr__(self): | |
342 return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls) | |
343 | |
344 | |
345 class RRHeader: | |
346 """ | |
347 A resource record header. | |
348 | |
349 @cvar fmt: C{str} specifying the byte format of an RR. | |
350 | |
351 @ivar name: The name about which this reply contains information. | |
352 @ivar type: The query type of the original request. | |
353 @ivar cls: The query class of the original request. | |
354 @ivar ttl: The time-to-live for this record. | |
355 @ivar payload: An object that implements the IEncodable interface | |
356 @ivar auth: Whether this header is authoritative or not. | |
357 """ | |
358 | |
359 implements(IEncodable) | |
360 | |
361 fmt = "!HHIH" | |
362 | |
363 name = None | |
364 type = None | |
365 cls = None | |
366 ttl = None | |
367 payload = None | |
368 rdlength = None | |
369 | |
370 cachedResponse = None | |
371 | |
372 def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False)
: | |
373 """ | |
374 @type name: C{str} | |
375 @param name: The name about which this reply contains information. | |
376 | |
377 @type type: C{int} | |
378 @param type: The query type. | |
379 | |
380 @type cls: C{int} | |
381 @param cls: The query class. | |
382 | |
383 @type ttl: C{int} | |
384 @param ttl: Time to live for this record. | |
385 | |
386 @type payload: An object implementing C{IEncodable} | |
387 @param payload: A Query Type specific data object. | |
388 """ | |
389 assert (payload is None) or (payload.TYPE == type) | |
390 | |
391 self.name = Name(name) | |
392 self.type = type | |
393 self.cls = cls | |
394 self.ttl = ttl | |
395 self.payload = payload | |
396 self.auth = auth | |
397 | |
398 | |
399 def encode(self, strio, compDict=None): | |
400 self.name.encode(strio, compDict) | |
401 strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0)) | |
402 if self.payload: | |
403 prefix = strio.tell() | |
404 self.payload.encode(strio, compDict) | |
405 aft = strio.tell() | |
406 strio.seek(prefix - 2, 0) | |
407 strio.write(struct.pack('!H', aft - prefix)) | |
408 strio.seek(aft, 0) | |
409 | |
410 | |
411 def decode(self, strio, length = None): | |
412 self.name.decode(strio) | |
413 l = struct.calcsize(self.fmt) | |
414 buff = readPrecisely(strio, l) | |
415 r = struct.unpack(self.fmt, buff) | |
416 self.type, self.cls, self.ttl, self.rdlength = r | |
417 | |
418 | |
419 def isAuthoritative(self): | |
420 return self.auth | |
421 | |
422 | |
423 def __str__(self): | |
424 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)'
% self.type)) | |
425 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls) | |
426 return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t,
c, self.ttl, self.auth and 'True' or 'False') | |
427 | |
428 | |
429 __repr__ = __str__ | |
430 | |
431 | |
432 | |
433 class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin): | |
434 """ | |
435 A Resource Record which consists of a single RFC 1035 domain-name. | |
436 | |
437 @type name: L{Name} | |
438 @ivar name: The name associated with this record. | |
439 | |
440 @type ttl: C{int} | |
441 @ivar ttl: The maximum number of seconds which this record should be | |
442 cached. | |
443 """ | |
444 implements(IEncodable, IRecord) | |
445 | |
446 showAttributes = (('name', 'name', '%s'), 'ttl') | |
447 compareAttributes = ('name', 'ttl') | |
448 | |
449 TYPE = None | |
450 name = None | |
451 | |
452 def __init__(self, name='', ttl=None): | |
453 self.name = Name(name) | |
454 self.ttl = str2time(ttl) | |
455 | |
456 | |
457 def encode(self, strio, compDict = None): | |
458 self.name.encode(strio, compDict) | |
459 | |
460 | |
461 def decode(self, strio, length = None): | |
462 self.name = Name() | |
463 self.name.decode(strio) | |
464 | |
465 | |
466 def __hash__(self): | |
467 return hash(self.name) | |
468 | |
469 | |
470 # Kinds of RRs - oh my! | |
471 class Record_NS(SimpleRecord): | |
472 """ | |
473 An authoritative nameserver. | |
474 """ | |
475 TYPE = NS | |
476 | |
477 | |
478 | |
479 class Record_MD(SimpleRecord): | |
480 """ | |
481 A mail destination. | |
482 | |
483 This record type is obsolete. | |
484 | |
485 @see: L{Record_MX} | |
486 """ | |
487 TYPE = MD | |
488 | |
489 | |
490 | |
491 class Record_MF(SimpleRecord): | |
492 """ | |
493 A mail forwarder. | |
494 | |
495 This record type is obsolete. | |
496 | |
497 @see: L{Record_MX} | |
498 """ | |
499 TYPE = MF | |
500 | |
501 | |
502 | |
503 class Record_CNAME(SimpleRecord): | |
504 """ | |
505 The canonical name for an alias. | |
506 """ | |
507 TYPE = CNAME | |
508 | |
509 | |
510 | |
511 class Record_MB(SimpleRecord): | |
512 """ | |
513 A mailbox domain name. | |
514 | |
515 This is an experimental record type. | |
516 """ | |
517 TYPE = MB | |
518 | |
519 | |
520 | |
521 class Record_MG(SimpleRecord): | |
522 """ | |
523 A mail group member. | |
524 | |
525 This is an experimental record type. | |
526 """ | |
527 TYPE = MG | |
528 | |
529 | |
530 | |
531 class Record_MR(SimpleRecord): | |
532 """ | |
533 A mail rename domain name. | |
534 | |
535 This is an experimental record type. | |
536 """ | |
537 TYPE = MR | |
538 | |
539 | |
540 | |
541 class Record_PTR(SimpleRecord): | |
542 """ | |
543 A domain name pointer. | |
544 """ | |
545 TYPE = PTR | |
546 | |
547 | |
548 | |
549 class Record_DNAME(SimpleRecord): | |
550 """ | |
551 A non-terminal DNS name redirection. | |
552 | |
553 This record type provides the capability to map an entire subtree of the | |
554 DNS name space to another domain. It differs from the CNAME record which | |
555 maps a single node of the name space. | |
556 | |
557 @see: U{http://www.faqs.org/rfcs/rfc2672.html} | |
558 """ | |
559 TYPE = DNAME | |
560 | |
561 | |
562 | |
563 class Record_A(tputil.FancyEqMixin): | |
564 """ | |
565 An IPv4 host address. | |
566 | |
567 @type address: C{str} | |
568 @ivar address: The packed network-order representation of the IPv4 address | |
569 associated with this record. | |
570 | |
571 @type ttl: C{int} | |
572 @ivar ttl: The maximum number of seconds which this record should be | |
573 cached. | |
574 """ | |
575 implements(IEncodable, IRecord) | |
576 | |
577 compareAttributes = ('address', 'ttl') | |
578 | |
579 TYPE = A | |
580 address = None | |
581 | |
582 def __init__(self, address='0.0.0.0', ttl=None): | |
583 address = socket.inet_aton(address) | |
584 self.address = address | |
585 self.ttl = str2time(ttl) | |
586 | |
587 | |
588 def encode(self, strio, compDict = None): | |
589 strio.write(self.address) | |
590 | |
591 | |
592 def decode(self, strio, length = None): | |
593 self.address = readPrecisely(strio, 4) | |
594 | |
595 | |
596 def __hash__(self): | |
597 return hash(self.address) | |
598 | |
599 | |
600 def __str__(self): | |
601 return '<A %s ttl=%s>' % (self.dottedQuad(), self.ttl) | |
602 | |
603 | |
604 def dottedQuad(self): | |
605 return socket.inet_ntoa(self.address) | |
606 | |
607 | |
608 | |
609 class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin): | |
610 """ | |
611 Marks the start of a zone of authority. | |
612 | |
613 This record describes parameters which are shared by all records within a | |
614 particular zone. | |
615 | |
616 @type mname: L{Name} | |
617 @ivar mname: The domain-name of the name server that was the original or | |
618 primary source of data for this zone. | |
619 | |
620 @type rname: L{Name} | |
621 @ivar rname: A domain-name which specifies the mailbox of the person | |
622 responsible for this zone. | |
623 | |
624 @type serial: C{int} | |
625 @ivar serial: The unsigned 32 bit version number of the original copy of | |
626 the zone. Zone transfers preserve this value. This value wraps and | |
627 should be compared using sequence space arithmetic. | |
628 | |
629 @type refresh: C{int} | |
630 @ivar refresh: A 32 bit time interval before the zone should be refreshed. | |
631 | |
632 @type minimum: C{int} | |
633 @ivar minimum: The unsigned 32 bit minimum TTL field that should be | |
634 exported with any RR from this zone. | |
635 | |
636 @type expire: C{int} | |
637 @ivar expire: A 32 bit time value that specifies the upper limit on the | |
638 time interval that can elapse before the zone is no longer | |
639 authoritative. | |
640 | |
641 @type retry: C{int} | |
642 @ivar retry: A 32 bit time interval that should elapse before a failed | |
643 refresh should be retried. | |
644 | |
645 @type ttl: C{int} | |
646 @ivar ttl: The default TTL to use for records served from this zone. | |
647 """ | |
648 implements(IEncodable, IRecord) | |
649 | |
650 compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry
', 'ttl') | |
651 showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'seria
l', 'refresh', 'retry', 'expire', 'minimum', 'ttl') | |
652 | |
653 TYPE = SOA | |
654 | |
655 def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=
0, minimum=0, ttl=None): | |
656 self.mname, self.rname = Name(mname), Name(rname) | |
657 self.serial, self.refresh = str2time(serial), str2time(refresh) | |
658 self.minimum, self.expire = str2time(minimum), str2time(expire) | |
659 self.retry = str2time(retry) | |
660 self.ttl = str2time(ttl) | |
661 | |
662 | |
663 def encode(self, strio, compDict = None): | |
664 self.mname.encode(strio, compDict) | |
665 self.rname.encode(strio, compDict) | |
666 strio.write( | |
667 struct.pack( | |
668 '!LlllL', | |
669 self.serial, self.refresh, self.retry, self.expire, | |
670 self.minimum | |
671 ) | |
672 ) | |
673 | |
674 | |
675 def decode(self, strio, length = None): | |
676 self.mname, self.rname = Name(), Name() | |
677 self.mname.decode(strio) | |
678 self.rname.decode(strio) | |
679 r = struct.unpack('!LlllL', readPrecisely(strio, 20)) | |
680 self.serial, self.refresh, self.retry, self.expire, self.minimum = r | |
681 | |
682 | |
683 def __hash__(self): | |
684 return hash(( | |
685 self.serial, self.mname, self.rname, | |
686 self.refresh, self.expire, self.retry | |
687 )) | |
688 | |
689 | |
690 | |
691 class Record_NULL: # EXPERIMENTAL | |
692 """ | |
693 A null record. | |
694 | |
695 This is an experimental record type. | |
696 | |
697 @type ttl: C{int} | |
698 @ivar ttl: The maximum number of seconds which this record should be | |
699 cached. | |
700 """ | |
701 implements(IEncodable, IRecord) | |
702 | |
703 TYPE = NULL | |
704 | |
705 def __init__(self, payload=None, ttl=None): | |
706 self.payload = payload | |
707 self.ttl = str2time(ttl) | |
708 | |
709 | |
710 def encode(self, strio, compDict = None): | |
711 strio.write(self.payload) | |
712 | |
713 | |
714 def decode(self, strio, length = None): | |
715 self.payload = readPrecisely(strio, length) | |
716 | |
717 | |
718 def __hash__(self): | |
719 return hash(self.payload) | |
720 | |
721 | |
722 | |
723 class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
# OBSOLETE | |
724 """ | |
725 A well known service description. | |
726 | |
727 This record type is obsolete. See L{Record_SRV}. | |
728 | |
729 @type address: C{str} | |
730 @ivar address: The packed network-order representation of the IPv4 address | |
731 associated with this record. | |
732 | |
733 @type protocol: C{int} | |
734 @ivar protocol: The 8 bit IP protocol number for which this service map is | |
735 relevant. | |
736 | |
737 @type map: C{str} | |
738 @ivar map: A bitvector indicating the services available at the specified | |
739 address. | |
740 | |
741 @type ttl: C{int} | |
742 @ivar ttl: The maximum number of seconds which this record should be | |
743 cached. | |
744 """ | |
745 implements(IEncodable, IRecord) | |
746 | |
747 compareAttributes = ('address', 'protocol', 'map', 'ttl') | |
748 showAttributes = ('address', 'protocol', 'ttl') | |
749 | |
750 TYPE = WKS | |
751 | |
752 def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None): | |
753 self.address = socket.inet_aton(address) | |
754 self.protocol, self.map = protocol, map | |
755 self.ttl = str2time(ttl) | |
756 | |
757 | |
758 def encode(self, strio, compDict = None): | |
759 strio.write(self.address) | |
760 strio.write(struct.pack('!B', self.protocol)) | |
761 strio.write(self.map) | |
762 | |
763 | |
764 def decode(self, strio, length = None): | |
765 self.address = readPrecisely(strio, 4) | |
766 self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0] | |
767 self.map = readPrecisely(strio, length - 5) | |
768 | |
769 | |
770 def __hash__(self): | |
771 return hash((self.address, self.protocol, self.map)) | |
772 | |
773 | |
774 | |
775 class Record_AAAA(tputil.FancyEqMixin): # OBSOLETE (or headed ther
e) | |
776 """ | |
777 An IPv6 host address. | |
778 | |
779 This record type is obsolete. | |
780 | |
781 @type address: C{str} | |
782 @ivar address: The packed network-order representation of the IPv6 address | |
783 associated with this record. | |
784 | |
785 @type ttl: C{int} | |
786 @ivar ttl: The maximum number of seconds which this record should be | |
787 cached. | |
788 | |
789 @see: L{Record_A6} | |
790 """ | |
791 implements(IEncodable, IRecord) | |
792 TYPE = AAAA | |
793 | |
794 compareAttributes = ('address', 'ttl') | |
795 | |
796 def __init__(self, address = '::', ttl=None): | |
797 self.address = socket.inet_pton(AF_INET6, address) | |
798 self.ttl = str2time(ttl) | |
799 | |
800 | |
801 def encode(self, strio, compDict = None): | |
802 strio.write(self.address) | |
803 | |
804 | |
805 def decode(self, strio, length = None): | |
806 self.address = readPrecisely(strio, 16) | |
807 | |
808 | |
809 def __hash__(self): | |
810 return hash(self.address) | |
811 | |
812 | |
813 def __str__(self): | |
814 return '<AAAA %s ttl=%s>' % (socket.inet_ntop(AF_INET6, self.address), s
elf.ttl) | |
815 | |
816 | |
817 | |
818 class Record_A6: | |
819 """ | |
820 An IPv6 address. | |
821 | |
822 @type prefixLen: C{int} | |
823 @ivar prefixLen: The length of the suffix. | |
824 | |
825 @type suffix: C{str} | |
826 @ivar suffix: An IPv6 address suffix in network order. | |
827 | |
828 @type prefix: L{Name} | |
829 @ivar prefix: If specified, a name which will be used as a prefix for other | |
830 A6 records. | |
831 | |
832 @type bytes: C{int} | |
833 @ivar bytes: The length of the prefix. | |
834 | |
835 @type ttl: C{int} | |
836 @ivar ttl: The maximum number of seconds which this record should be | |
837 cached. | |
838 | |
839 @see: U{http://www.faqs.org/rfcs/rfc2874.html} | |
840 """ | |
841 implements(IEncodable, IRecord) | |
842 TYPE = A6 | |
843 | |
844 def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None): | |
845 self.prefixLen = prefixLen | |
846 self.suffix = socket.inet_pton(AF_INET6, suffix) | |
847 self.prefix = Name(prefix) | |
848 self.bytes = int((128 - self.prefixLen) / 8.0) | |
849 self.ttl = str2time(ttl) | |
850 | |
851 | |
852 def encode(self, strio, compDict = None): | |
853 strio.write(struct.pack('!B', self.prefixLen)) | |
854 if self.bytes: | |
855 strio.write(self.suffix[-self.bytes:]) | |
856 if self.prefixLen: | |
857 # This may not be compressed | |
858 self.prefix.encode(strio, None) | |
859 | |
860 | |
861 def decode(self, strio, length = None): | |
862 self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0] | |
863 self.bytes = int((128 - self.prefixLen) / 8.0) | |
864 if self.bytes: | |
865 self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self
.bytes) | |
866 if self.prefixLen: | |
867 self.prefix.decode(strio) | |
868 | |
869 | |
870 def __eq__(self, other): | |
871 if isinstance(other, Record_A6): | |
872 return (self.prefixLen == other.prefixLen and | |
873 self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and | |
874 self.prefix == other.prefix and | |
875 self.ttl == other.ttl) | |
876 return 0 | |
877 | |
878 | |
879 def __hash__(self): | |
880 return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix)) | |
881 | |
882 | |
883 def __str__(self): | |
884 return '<A6 %s %s (%d) ttl=%s>' % ( | |
885 self.prefix, | |
886 socket.inet_ntop(AF_INET6, self.suffix), | |
887 self.prefixLen, self.ttl | |
888 ) | |
889 | |
890 | |
891 | |
892 class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin): | |
893 """ | |
894 The location of the server(s) for a specific protocol and domain. | |
895 | |
896 This is an experimental record type. | |
897 | |
898 @type priority: C{int} | |
899 @ivar priority: The priority of this target host. A client MUST attempt to | |
900 contact the target host with the lowest-numbered priority it can reach; | |
901 target hosts with the same priority SHOULD be tried in an order defined | |
902 by the weight field. | |
903 | |
904 @type weight: C{int} | |
905 @ivar weight: Specifies a relative weight for entries with the same | |
906 priority. Larger weights SHOULD be given a proportionately higher | |
907 probability of being selected. | |
908 | |
909 @type port: C{int} | |
910 @ivar port: The port on this target host of this service. | |
911 | |
912 @type target: L{Name} | |
913 @ivar target: The domain name of the target host. There MUST be one or | |
914 more address records for this name, the name MUST NOT be an alias (in | |
915 the sense of RFC 1034 or RFC 2181). Implementors are urged, but not | |
916 required, to return the address record(s) in the Additional Data | |
917 section. Unless and until permitted by future standards action, name | |
918 compression is not to be used for this field. | |
919 | |
920 @type ttl: C{int} | |
921 @ivar ttl: The maximum number of seconds which this record should be | |
922 cached. | |
923 | |
924 @see: U{http://www.faqs.org/rfcs/rfc2782.html} | |
925 """ | |
926 implements(IEncodable, IRecord) | |
927 TYPE = SRV | |
928 | |
929 compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl') | |
930 showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port',
'ttl') | |
931 | |
932 def __init__(self, priority=0, weight=0, port=0, target='', ttl=None): | |
933 self.priority = int(priority) | |
934 self.weight = int(weight) | |
935 self.port = int(port) | |
936 self.target = Name(target) | |
937 self.ttl = str2time(ttl) | |
938 | |
939 | |
940 def encode(self, strio, compDict = None): | |
941 strio.write(struct.pack('!HHH', self.priority, self.weight, self.port)) | |
942 # This can't be compressed | |
943 self.target.encode(strio, None) | |
944 | |
945 | |
946 def decode(self, strio, length = None): | |
947 r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH'))) | |
948 self.priority, self.weight, self.port = r | |
949 self.target = Name() | |
950 self.target.decode(strio) | |
951 | |
952 | |
953 def __hash__(self): | |
954 return hash((self.priority, self.weight, self.port, self.target)) | |
955 | |
956 | |
957 | |
958 class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin): | |
959 """ | |
960 Map from a domain name to the name of an AFS cell database server. | |
961 | |
962 @type subtype: C{int} | |
963 @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0 | |
964 Volume Location Server for the named AFS cell. In the case of subtype | |
965 2, the host has an authenticated name server holding the cell-root | |
966 directory node for the named DCE/NCA cell. | |
967 | |
968 @type hostname: L{Name} | |
969 @ivar hostname: The domain name of a host that has a server for the cell | |
970 named by this record. | |
971 | |
972 @type ttl: C{int} | |
973 @ivar ttl: The maximum number of seconds which this record should be | |
974 cached. | |
975 | |
976 @see: U{http://www.faqs.org/rfcs/rfc1183.html} | |
977 """ | |
978 implements(IEncodable, IRecord) | |
979 TYPE = AFSDB | |
980 | |
981 compareAttributes = ('subtype', 'hostname', 'ttl') | |
982 showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl') | |
983 | |
984 def __init__(self, subtype=0, hostname='', ttl=None): | |
985 self.subtype = int(subtype) | |
986 self.hostname = Name(hostname) | |
987 self.ttl = str2time(ttl) | |
988 | |
989 | |
990 def encode(self, strio, compDict = None): | |
991 strio.write(struct.pack('!H', self.subtype)) | |
992 self.hostname.encode(strio, compDict) | |
993 | |
994 | |
995 def decode(self, strio, length = None): | |
996 r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H'))) | |
997 self.subtype, = r | |
998 self.hostname.decode(strio) | |
999 | |
1000 | |
1001 def __hash__(self): | |
1002 return hash((self.subtype, self.hostname)) | |
1003 | |
1004 | |
1005 | |
1006 class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin): | |
1007 """ | |
1008 The responsible person for a domain. | |
1009 | |
1010 @type mbox: L{Name} | |
1011 @ivar mbox: A domain name that specifies the mailbox for the responsible | |
1012 person. | |
1013 | |
1014 @type txt: L{Name} | |
1015 @ivar txt: A domain name for which TXT RR's exist (indirection through | |
1016 which allows information sharing about the contents of this RP record). | |
1017 | |
1018 @type ttl: C{int} | |
1019 @ivar ttl: The maximum number of seconds which this record should be | |
1020 cached. | |
1021 | |
1022 @see: U{http://www.faqs.org/rfcs/rfc1183.html} | |
1023 """ | |
1024 implements(IEncodable, IRecord) | |
1025 TYPE = RP | |
1026 | |
1027 compareAttributes = ('mbox', 'txt', 'ttl') | |
1028 showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl') | |
1029 | |
1030 def __init__(self, mbox='', txt='', ttl=None): | |
1031 self.mbox = Name(mbox) | |
1032 self.txt = Name(txt) | |
1033 self.ttl = str2time(ttl) | |
1034 | |
1035 | |
1036 def encode(self, strio, compDict = None): | |
1037 self.mbox.encode(strio, compDict) | |
1038 self.txt.encode(strio, compDict) | |
1039 | |
1040 | |
1041 def decode(self, strio, length = None): | |
1042 self.mbox = Name() | |
1043 self.txt = Name() | |
1044 self.mbox.decode(strio) | |
1045 self.txt.decode(strio) | |
1046 | |
1047 | |
1048 def __hash__(self): | |
1049 return hash((self.mbox, self.txt)) | |
1050 | |
1051 | |
1052 | |
1053 class Record_HINFO(tputil.FancyStrMixin): | |
1054 """ | |
1055 Host information. | |
1056 | |
1057 @type cpu: C{str} | |
1058 @ivar cpu: Specifies the CPU type. | |
1059 | |
1060 @type os: C{str} | |
1061 @ivar os: Specifies the OS. | |
1062 | |
1063 @type ttl: C{int} | |
1064 @ivar ttl: The maximum number of seconds which this record should be | |
1065 cached. | |
1066 """ | |
1067 implements(IEncodable, IRecord) | |
1068 TYPE = HINFO | |
1069 | |
1070 showAttributes = ('cpu', 'os', 'ttl') | |
1071 | |
1072 def __init__(self, cpu='', os='', ttl=None): | |
1073 self.cpu, self.os = cpu, os | |
1074 self.ttl = str2time(ttl) | |
1075 | |
1076 | |
1077 def encode(self, strio, compDict = None): | |
1078 strio.write(struct.pack('!B', len(self.cpu)) + self.cpu) | |
1079 strio.write(struct.pack('!B', len(self.os)) + self.os) | |
1080 | |
1081 | |
1082 def decode(self, strio, length = None): | |
1083 cpu = struct.unpack('!B', readPrecisely(strio, 1))[0] | |
1084 self.cpu = readPrecisely(strio, cpu) | |
1085 os = struct.unpack('!B', readPrecisely(strio, 1))[0] | |
1086 self.os = readPrecisely(strio, os) | |
1087 | |
1088 | |
1089 def __eq__(self, other): | |
1090 if isinstance(other, Record_HINFO): | |
1091 return (self.os.lower() == other.os.lower() and | |
1092 self.cpu.lower() == other.cpu.lower() and | |
1093 self.ttl == other.ttl) | |
1094 return 0 | |
1095 | |
1096 | |
1097 def __hash__(self): | |
1098 return hash((self.os.lower(), self.cpu.lower())) | |
1099 | |
1100 | |
1101 | |
1102 class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin): | |
1103 """ | |
1104 Mailbox or mail list information. | |
1105 | |
1106 This is an experimental record type. | |
1107 | |
1108 @type rmailbx: L{Name} | |
1109 @ivar rmailbx: A domain-name which specifies a mailbox which is responsible | |
1110 for the mailing list or mailbox. If this domain name names the root, | |
1111 the owner of the MINFO RR is responsible for itself. | |
1112 | |
1113 @type emailbx: L{Name} | |
1114 @ivar emailbx: A domain-name which specifies a mailbox which is to receive | |
1115 error messages related to the mailing list or mailbox specified by the | |
1116 owner of the MINFO record. If this domain name names the root, errors | |
1117 should be returned to the sender of the message. | |
1118 | |
1119 @type ttl: C{int} | |
1120 @ivar ttl: The maximum number of seconds which this record should be | |
1121 cached. | |
1122 """ | |
1123 implements(IEncodable, IRecord) | |
1124 TYPE = MINFO | |
1125 | |
1126 rmailbx = None | |
1127 emailbx = None | |
1128 | |
1129 compareAttributes = ('rmailbx', 'emailbx', 'ttl') | |
1130 showAttributes = (('rmailbx', 'responsibility', '%s'), | |
1131 ('emailbx', 'errors', '%s'), | |
1132 'ttl') | |
1133 | |
1134 def __init__(self, rmailbx='', emailbx='', ttl=None): | |
1135 self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx) | |
1136 self.ttl = str2time(ttl) | |
1137 | |
1138 | |
1139 def encode(self, strio, compDict = None): | |
1140 self.rmailbx.encode(strio, compDict) | |
1141 self.emailbx.encode(strio, compDict) | |
1142 | |
1143 | |
1144 def decode(self, strio, length = None): | |
1145 self.rmailbx, self.emailbx = Name(), Name() | |
1146 self.rmailbx.decode(strio) | |
1147 self.emailbx.decode(strio) | |
1148 | |
1149 | |
1150 def __hash__(self): | |
1151 return hash((self.rmailbx, self.emailbx)) | |
1152 | |
1153 | |
1154 | |
1155 class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin): | |
1156 """ | |
1157 Mail exchange. | |
1158 | |
1159 @type preference: C{int} | |
1160 @ivar preference: Specifies the preference given to this RR among others at | |
1161 the same owner. Lower values are preferred. | |
1162 | |
1163 @type name: L{Name} | |
1164 @ivar name: A domain-name which specifies a host willing to act as a mail | |
1165 exchange. | |
1166 | |
1167 @type ttl: C{int} | |
1168 @ivar ttl: The maximum number of seconds which this record should be | |
1169 cached. | |
1170 """ | |
1171 implements(IEncodable, IRecord) | |
1172 TYPE = MX | |
1173 | |
1174 compareAttributes = ('preference', 'name', 'ttl') | |
1175 showAttributes = ('preference', ('name', 'name', '%s'), 'ttl') | |
1176 | |
1177 def __init__(self, preference=0, name='', ttl=None, **kwargs): | |
1178 self.preference, self.name = int(preference), Name(kwargs.get('exchange'
, name)) | |
1179 self.ttl = str2time(ttl) | |
1180 | |
1181 def encode(self, strio, compDict = None): | |
1182 strio.write(struct.pack('!H', self.preference)) | |
1183 self.name.encode(strio, compDict) | |
1184 | |
1185 | |
1186 def decode(self, strio, length = None): | |
1187 self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0] | |
1188 self.name = Name() | |
1189 self.name.decode(strio) | |
1190 | |
1191 def exchange(self): | |
1192 warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklev
el=2) | |
1193 return self.name | |
1194 | |
1195 exchange = property(exchange) | |
1196 | |
1197 def __hash__(self): | |
1198 return hash((self.preference, self.name)) | |
1199 | |
1200 | |
1201 | |
1202 # Oh god, Record_TXT how I hate thee. | |
1203 class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin): | |
1204 """ | |
1205 Freeform text. | |
1206 | |
1207 @type data: C{list} of C{str} | |
1208 @ivar data: Freeform text which makes up this record. | |
1209 """ | |
1210 implements(IEncodable, IRecord) | |
1211 | |
1212 TYPE = TXT | |
1213 | |
1214 showAttributes = compareAttributes = ('data', 'ttl') | |
1215 | |
1216 def __init__(self, *data, **kw): | |
1217 self.data = list(data) | |
1218 # arg man python sucks so bad | |
1219 self.ttl = str2time(kw.get('ttl', None)) | |
1220 | |
1221 | |
1222 def encode(self, strio, compDict = None): | |
1223 for d in self.data: | |
1224 strio.write(struct.pack('!B', len(d)) + d) | |
1225 | |
1226 | |
1227 def decode(self, strio, length = None): | |
1228 soFar = 0 | |
1229 self.data = [] | |
1230 while soFar < length: | |
1231 L = struct.unpack('!B', readPrecisely(strio, 1))[0] | |
1232 self.data.append(readPrecisely(strio, L)) | |
1233 soFar += L + 1 | |
1234 if soFar != length: | |
1235 log.msg( | |
1236 "Decoded %d bytes in TXT record, but rdlength is %d" % ( | |
1237 soFar, length | |
1238 ) | |
1239 ) | |
1240 | |
1241 | |
1242 def __hash__(self): | |
1243 return hash(tuple(self.data)) | |
1244 | |
1245 | |
1246 | |
1247 class Message: | |
1248 headerFmt = "!H2B4H" | |
1249 headerSize = struct.calcsize(headerFmt) | |
1250 | |
1251 # Question, answer, additional, and nameserver lists | |
1252 queries = answers = add = ns = None | |
1253 | |
1254 def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0, | |
1255 auth=0, rCode=OK, trunc=0, maxSize=512): | |
1256 self.maxSize = maxSize | |
1257 self.id = id | |
1258 self.answer = answer | |
1259 self.opCode = opCode | |
1260 self.auth = auth | |
1261 self.trunc = trunc | |
1262 self.recDes = recDes | |
1263 self.recAv = recAv | |
1264 self.rCode = rCode | |
1265 self.queries = [] | |
1266 self.answers = [] | |
1267 self.authority = [] | |
1268 self.additional = [] | |
1269 | |
1270 | |
1271 def addQuery(self, name, type=ALL_RECORDS, cls=IN): | |
1272 """ | |
1273 Add another query to this Message. | |
1274 | |
1275 @type name: C{str} | |
1276 @param name: The name to query. | |
1277 | |
1278 @type type: C{int} | |
1279 @param type: Query type | |
1280 | |
1281 @type cls: C{int} | |
1282 @param cls: Query class | |
1283 """ | |
1284 self.queries.append(Query(name, type, cls)) | |
1285 | |
1286 | |
1287 def encode(self, strio): | |
1288 compDict = {} | |
1289 body_tmp = StringIO.StringIO() | |
1290 for q in self.queries: | |
1291 q.encode(body_tmp, compDict) | |
1292 for q in self.answers: | |
1293 q.encode(body_tmp, compDict) | |
1294 for q in self.authority: | |
1295 q.encode(body_tmp, compDict) | |
1296 for q in self.additional: | |
1297 q.encode(body_tmp, compDict) | |
1298 body = body_tmp.getvalue() | |
1299 size = len(body) + self.headerSize | |
1300 if self.maxSize and size > self.maxSize: | |
1301 self.trunc = 1 | |
1302 body = body[:self.maxSize - self.headerSize] | |
1303 byte3 = (( ( self.answer & 1 ) << 7 ) | |
1304 | ((self.opCode & 0xf ) << 3 ) | |
1305 | ((self.auth & 1 ) << 2 ) | |
1306 | ((self.trunc & 1 ) << 1 ) | |
1307 | ( self.recDes & 1 ) ) | |
1308 byte4 = ( ( (self.recAv & 1 ) << 7 ) | |
1309 | (self.rCode & 0xf ) ) | |
1310 | |
1311 strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4, | |
1312 len(self.queries), len(self.answers), | |
1313 len(self.authority), len(self.additional))) | |
1314 strio.write(body) | |
1315 | |
1316 | |
1317 def decode(self, strio, length=None): | |
1318 self.maxSize = 0 | |
1319 header = readPrecisely(strio, self.headerSize) | |
1320 r = struct.unpack(self.headerFmt, header) | |
1321 self.id, byte3, byte4, nqueries, nans, nns, nadd = r | |
1322 self.answer = ( byte3 >> 7 ) & 1 | |
1323 self.opCode = ( byte3 >> 3 ) & 0xf | |
1324 self.auth = ( byte3 >> 2 ) & 1 | |
1325 self.trunc = ( byte3 >> 1 ) & 1 | |
1326 self.recDes = byte3 & 1 | |
1327 self.recAv = ( byte4 >> 7 ) & 1 | |
1328 self.rCode = byte4 & 0xf | |
1329 | |
1330 self.queries = [] | |
1331 for i in range(nqueries): | |
1332 q = Query() | |
1333 try: | |
1334 q.decode(strio) | |
1335 except EOFError: | |
1336 return | |
1337 self.queries.append(q) | |
1338 | |
1339 items = ((self.answers, nans), (self.authority, nns), (self.additional,
nadd)) | |
1340 for (l, n) in items: | |
1341 self.parseRecords(l, n, strio) | |
1342 | |
1343 | |
1344 def parseRecords(self, list, num, strio): | |
1345 for i in range(num): | |
1346 header = RRHeader() | |
1347 try: | |
1348 header.decode(strio) | |
1349 except EOFError: | |
1350 return | |
1351 t = self.lookupRecordType(header.type) | |
1352 if not t: | |
1353 continue | |
1354 header.payload = t(ttl=header.ttl) | |
1355 try: | |
1356 header.payload.decode(strio, header.rdlength) | |
1357 except EOFError: | |
1358 return | |
1359 list.append(header) | |
1360 | |
1361 | |
1362 def lookupRecordType(self, type): | |
1363 return globals().get('Record_' + QUERY_TYPES.get(type, ''), None) | |
1364 | |
1365 | |
1366 def toStr(self): | |
1367 strio = StringIO.StringIO() | |
1368 self.encode(strio) | |
1369 return strio.getvalue() | |
1370 | |
1371 | |
1372 def fromStr(self, str): | |
1373 strio = StringIO.StringIO(str) | |
1374 self.decode(strio) | |
1375 | |
1376 class DNSMixin(object): | |
1377 """ | |
1378 DNS protocol mixin shared by UDP and TCP implementations. | |
1379 """ | |
1380 id = None | |
1381 liveMessages = None | |
1382 | |
1383 def __init__(self, controller): | |
1384 self.controller = controller | |
1385 self.id = random.randrange(2 ** 10, 2 ** 15) | |
1386 | |
1387 def pickID(self): | |
1388 """ | |
1389 Return a unique ID for queries. | |
1390 """ | |
1391 while True: | |
1392 self.id += randomSource() % (2 ** 10) | |
1393 self.id %= 2 ** 16 | |
1394 if self.id not in self.liveMessages: | |
1395 break | |
1396 return self.id | |
1397 | |
1398 def callLater(self, period, func, *args): | |
1399 """ | |
1400 Wrapper around reactor.callLater, mainly for test purpose. | |
1401 """ | |
1402 from twisted.internet import reactor | |
1403 return reactor.callLater(period, func, *args) | |
1404 | |
1405 def _query(self, queries, timeout, id, writeMessage): | |
1406 """ | |
1407 Send out a message with the given queries. | |
1408 | |
1409 @type queries: C{list} of C{Query} instances | |
1410 @param queries: The queries to transmit | |
1411 | |
1412 @type timeout: C{int} or C{float} | |
1413 @param timeout: How long to wait before giving up | |
1414 | |
1415 @type id: C{int} | |
1416 @param id: Unique key for this request | |
1417 | |
1418 @type writeMessage: C{callable} | |
1419 @param writeMessage: One-parameter callback which writes the message | |
1420 | |
1421 @rtype: C{Deferred} | |
1422 @return: a C{Deferred} which will be fired with the result of the | |
1423 query, or errbacked with any errors that could happen (exceptions | |
1424 during writing of the query, timeout errors, ...). | |
1425 """ | |
1426 m = Message(id, recDes=1) | |
1427 m.queries = queries | |
1428 | |
1429 try: | |
1430 writeMessage(m) | |
1431 except: | |
1432 return defer.fail() | |
1433 | |
1434 resultDeferred = defer.Deferred() | |
1435 cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred,
id) | |
1436 self.liveMessages[id] = (resultDeferred, cancelCall) | |
1437 | |
1438 return resultDeferred | |
1439 | |
1440 def _clearFailed(self, deferred, id): | |
1441 """ | |
1442 Clean the Deferred after a timeout. | |
1443 """ | |
1444 try: | |
1445 del self.liveMessages[id] | |
1446 except KeyError: | |
1447 pass | |
1448 deferred.errback(failure.Failure(DNSQueryTimeoutError(id))) | |
1449 | |
1450 | |
1451 class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol): | |
1452 """ | |
1453 DNS protocol over UDP. | |
1454 """ | |
1455 resends = None | |
1456 | |
1457 def stopProtocol(self): | |
1458 """ | |
1459 Stop protocol: reset state variables. | |
1460 """ | |
1461 self.liveMessages = {} | |
1462 self.resends = {} | |
1463 self.transport = None | |
1464 | |
1465 def startProtocol(self): | |
1466 """ | |
1467 Upon start, reset internal state. | |
1468 """ | |
1469 self.liveMessages = {} | |
1470 self.resends = {} | |
1471 | |
1472 def writeMessage(self, message, address): | |
1473 """ | |
1474 Send a message holding DNS queries. | |
1475 | |
1476 @type message: L{Message} | |
1477 """ | |
1478 self.transport.write(message.toStr(), address) | |
1479 | |
1480 def startListening(self): | |
1481 from twisted.internet import reactor | |
1482 reactor.listenUDP(0, self, maxPacketSize=512) | |
1483 | |
1484 def datagramReceived(self, data, addr): | |
1485 """ | |
1486 Read a datagram, extract the message in it and trigger the associated | |
1487 Deferred. | |
1488 """ | |
1489 m = Message() | |
1490 try: | |
1491 m.fromStr(data) | |
1492 except EOFError: | |
1493 log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr)) | |
1494 return | |
1495 except: | |
1496 # Nothing should trigger this, but since we're potentially | |
1497 # invoking a lot of different decoding methods, we might as well | |
1498 # be extra cautious. Anything that triggers this is itself | |
1499 # buggy. | |
1500 log.err(failure.Failure(), "Unexpected decoding error") | |
1501 return | |
1502 | |
1503 if m.id in self.liveMessages: | |
1504 d, canceller = self.liveMessages[m.id] | |
1505 del self.liveMessages[m.id] | |
1506 canceller.cancel() | |
1507 # XXX we shouldn't need this hack of catching exception on callback(
) | |
1508 try: | |
1509 d.callback(m) | |
1510 except: | |
1511 log.err() | |
1512 else: | |
1513 if m.id not in self.resends: | |
1514 self.controller.messageReceived(m, self, addr) | |
1515 | |
1516 | |
1517 def removeResend(self, id): | |
1518 """ | |
1519 Mark message ID as no longer having duplication suppression. | |
1520 """ | |
1521 try: | |
1522 del self.resends[id] | |
1523 except KeyError: | |
1524 pass | |
1525 | |
1526 def query(self, address, queries, timeout=10, id=None): | |
1527 """ | |
1528 Send out a message with the given queries. | |
1529 | |
1530 @type address: C{tuple} of C{str} and C{int} | |
1531 @param address: The address to which to send the query | |
1532 | |
1533 @type queries: C{list} of C{Query} instances | |
1534 @param queries: The queries to transmit | |
1535 | |
1536 @rtype: C{Deferred} | |
1537 """ | |
1538 if not self.transport: | |
1539 # XXX transport might not get created automatically, use callLater? | |
1540 try: | |
1541 self.startListening() | |
1542 except CannotListenError: | |
1543 return defer.fail() | |
1544 | |
1545 if id is None: | |
1546 id = self.pickID() | |
1547 else: | |
1548 self.resends[id] = 1 | |
1549 | |
1550 def writeMessage(m): | |
1551 self.writeMessage(m, address) | |
1552 | |
1553 return self._query(queries, timeout, id, writeMessage) | |
1554 | |
1555 | |
1556 class DNSProtocol(DNSMixin, protocol.Protocol): | |
1557 """ | |
1558 DNS protocol over TCP. | |
1559 """ | |
1560 length = None | |
1561 buffer = '' | |
1562 | |
1563 def writeMessage(self, message): | |
1564 """ | |
1565 Send a message holding DNS queries. | |
1566 | |
1567 @type message: L{Message} | |
1568 """ | |
1569 s = message.toStr() | |
1570 self.transport.write(struct.pack('!H', len(s)) + s) | |
1571 | |
1572 def connectionMade(self): | |
1573 """ | |
1574 Connection is made: reset internal state, and notify the controller. | |
1575 """ | |
1576 self.liveMessages = {} | |
1577 self.controller.connectionMade(self) | |
1578 | |
1579 def dataReceived(self, data): | |
1580 self.buffer += data | |
1581 | |
1582 while self.buffer: | |
1583 if self.length is None and len(self.buffer) >= 2: | |
1584 self.length = struct.unpack('!H', self.buffer[:2])[0] | |
1585 self.buffer = self.buffer[2:] | |
1586 | |
1587 if len(self.buffer) >= self.length: | |
1588 myChunk = self.buffer[:self.length] | |
1589 m = Message() | |
1590 m.fromStr(myChunk) | |
1591 | |
1592 try: | |
1593 d, canceller = self.liveMessages[m.id] | |
1594 except KeyError: | |
1595 self.controller.messageReceived(m, self) | |
1596 else: | |
1597 del self.liveMessages[m.id] | |
1598 canceller.cancel() | |
1599 # XXX we shouldn't need this hack | |
1600 try: | |
1601 d.callback(m) | |
1602 except: | |
1603 log.err() | |
1604 | |
1605 self.buffer = self.buffer[self.length:] | |
1606 self.length = None | |
1607 else: | |
1608 break | |
1609 | |
1610 def query(self, queries, timeout=60): | |
1611 """ | |
1612 Send out a message with the given queries. | |
1613 | |
1614 @type queries: C{list} of C{Query} instances | |
1615 @param queries: The queries to transmit | |
1616 | |
1617 @rtype: C{Deferred} | |
1618 """ | |
1619 id = self.pickID() | |
1620 return self._query(queries, timeout, id, self.writeMessage) | |
1621 | |
OLD | NEW |