root/trunk/twisted/names/dns.py

Revision 34055, 53.8 KB (checked in by itamarst, 7 weeks ago)

Apply 4549 patch: Get rid of deprecated Record_MX.exchange.

Author: ashfall
Review: itamar
Fixes: #4549

Get rid of a deprecated attribute.

Line 
1# -*- test-case-name: twisted.names.test.test_dns -*-
2# Copyright (c) Twisted Matrix Laboratories.
3# See LICENSE for details.
4
5"""
6DNS protocol implementation.
7
8Future 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
47import warnings
48
49import struct, random, types, socket
50
51import cStringIO as StringIO
52
53AF_INET6 = socket.AF_INET6
54
55from zope.interface import implements, Interface, Attribute
56
57
58# Twisted imports
59from twisted.internet import protocol, defer
60from twisted.internet.error import CannotListenError
61from twisted.python import log, failure
62from twisted.python import util as tputil
63from twisted.python import randbytes
64
65
66def 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
73PORT = 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)
77AAAA = 28
78SRV = 33
79NAPTR = 35
80A6 = 38
81DNAME = 39
82SPF = 99
83
84QUERY_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
114IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
115
116# "Extended" queries (Hey, half of these are deprecated, good job)
117EXT_QUERIES = {
118    IXFR: 'IXFR',
119    AXFR: 'AXFR',
120    MAILB: 'MAILB',
121    MAILA: 'MAILA',
122    ALL_RECORDS: 'ALL_RECORDS'
123}
124
125REV_TYPES = dict([
126    (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
127])
128
129IN, CS, CH, HS = range(1, 5)
130ANY = 255
131
132QUERY_CLASSES = {
133    IN: 'IN',
134    CS: 'CS',
135    CH: 'CH',
136    HS: 'HS',
137    ANY: 'ANY'
138}
139REV_CLASSES = dict([
140    (v, k) for (k, v) in QUERY_CLASSES.items()
141])
142
143
144# Opcodes
145OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
146OP_NOTIFY = 4 # RFC 1996
147OP_UPDATE = 5 # RFC 2136
148
149
150# Response Codes
151OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
152
153class 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
163from twisted.names.error import DomainError, AuthoritativeDomainError
164from twisted.names.error import DNSQueryTimeoutError
165
166
167def 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
184def readPrecisely(file, l):
185    buff = file.read(l)
186    if len(buff) < l:
187        raise EOFError
188    return buff
189
190
191class 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
228class 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
282class 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
374class 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
437class 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
529class 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!
567class Record_NS(SimpleRecord):
568    """
569    An authoritative nameserver.
570    """
571    TYPE = NS
572    fancybasename = 'NS'
573
574
575
576class 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
589class 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
602class Record_CNAME(SimpleRecord):
603    """
604    The canonical name for an alias.
605    """
606    TYPE = CNAME
607    fancybasename = 'CNAME'
608
609
610
611class 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
622class 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
633class 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
644class Record_PTR(SimpleRecord):
645    """
646    A domain name pointer.
647    """
648    TYPE = PTR
649    fancybasename = 'PTR'
650
651
652
653class 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
669class 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
716class 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
799class 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
834class 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
889class 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
930class 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
1014class 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
1081class 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
1175class 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
1224class 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
1272class 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
1322class 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
1376class 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.
1419class 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
1468class 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
1515class 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
1531class 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
1690class 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
1771class 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
1875class 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)
Note: See TracBrowser for help on using the browser.