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