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
12# System imports
13import inspect
14import random
15import socket
16import struct
17from io import BytesIO
18from itertools import chain
19from typing import Optional, SupportsInt, Union
20
21from zope.interface import Attribute, Interface, implementer
22
23# Twisted imports
24from twisted.internet import defer, protocol
25from twisted.internet.error import CannotListenError
26from twisted.python import failure, log, randbytes, util as tputil
27from twisted.python.compat import cmp, comparable, nativeString
28
29__all__ = [
30    "IEncodable",
31    "IRecord",
32    "IEncodableRecord",
33    "A",
34    "A6",
35    "AAAA",
36    "AFSDB",
37    "CNAME",
38    "DNAME",
39    "HINFO",
40    "MAILA",
41    "MAILB",
42    "MB",
43    "MD",
44    "MF",
45    "MG",
46    "MINFO",
47    "MR",
48    "MX",
49    "NAPTR",
50    "NS",
51    "NULL",
52    "OPT",
53    "PTR",
54    "RP",
55    "SOA",
56    "SPF",
57    "SRV",
58    "TXT",
59    "SSHFP",
60    "TSIG",
61    "WKS",
62    "ANY",
63    "CH",
64    "CS",
65    "HS",
66    "IN",
67    "ALL_RECORDS",
68    "AXFR",
69    "IXFR",
70    "EFORMAT",
71    "ENAME",
72    "ENOTIMP",
73    "EREFUSED",
74    "ESERVER",
75    "EBADVERSION",
76    "EBADSIG",
77    "EBADKEY",
78    "EBADTIME",
79    "Record_A",
80    "Record_A6",
81    "Record_AAAA",
82    "Record_AFSDB",
83    "Record_CNAME",
84    "Record_DNAME",
85    "Record_HINFO",
86    "Record_MB",
87    "Record_MD",
88    "Record_MF",
89    "Record_MG",
90    "Record_MINFO",
91    "Record_MR",
92    "Record_MX",
93    "Record_NAPTR",
94    "Record_NS",
95    "Record_NULL",
96    "Record_PTR",
97    "Record_RP",
98    "Record_SOA",
99    "Record_SPF",
100    "Record_SRV",
101    "Record_SSHFP",
102    "Record_TSIG",
103    "Record_TXT",
104    "Record_WKS",
105    "UnknownRecord",
106    "QUERY_CLASSES",
107    "QUERY_TYPES",
108    "REV_CLASSES",
109    "REV_TYPES",
110    "EXT_QUERIES",
111    "Charstr",
112    "Message",
113    "Name",
114    "Query",
115    "RRHeader",
116    "SimpleRecord",
117    "DNSDatagramProtocol",
118    "DNSMixin",
119    "DNSProtocol",
120    "OK",
121    "OP_INVERSE",
122    "OP_NOTIFY",
123    "OP_QUERY",
124    "OP_STATUS",
125    "OP_UPDATE",
126    "PORT",
127    "AuthoritativeDomainError",
128    "DNSQueryTimeoutError",
129    "DomainError",
130]
131
132
133AF_INET6 = socket.AF_INET6
134
135
136def _ord2bytes(ordinal):
137    """
138    Construct a bytes object representing a single byte with the given
139    ordinal value.
140
141    @type ordinal: L{int}
142    @rtype: L{bytes}
143    """
144    return bytes([ordinal])
145
146
147def _nicebytes(bytes):
148    """
149    Represent a mostly textful bytes object in a way suitable for
150    presentation to an end user.
151
152    @param bytes: The bytes to represent.
153    @rtype: L{str}
154    """
155    return repr(bytes)[1:]
156
157
158def _nicebyteslist(list):
159    """
160    Represent a list of mostly textful bytes objects in a way suitable for
161    presentation to an end user.
162
163    @param list: The list of bytes to represent.
164    @rtype: L{str}
165    """
166    return "[{}]".format(", ".join([_nicebytes(b) for b in list]))
167
168
169def randomSource():
170    """
171    Wrapper around L{twisted.python.randbytes.RandomFactory.secureRandom} to
172    return 2 random bytes.
173
174    @rtype: L{bytes}
175    """
176    return struct.unpack("H", randbytes.secureRandom(2, fallback=True))[0]
177
178
179PORT = 53
180
181(
182    A,
183    NS,
184    MD,
185    MF,
186    CNAME,
187    SOA,
188    MB,
189    MG,
190    MR,
191    NULL,
192    WKS,
193    PTR,
194    HINFO,
195    MINFO,
196    MX,
197    TXT,
198    RP,
199    AFSDB,
200) = range(1, 19)
201AAAA = 28
202SRV = 33
203NAPTR = 35
204A6 = 38
205DNAME = 39
206OPT = 41
207SSHFP = 44
208SPF = 99
209
210# These record types do not exist in zones, but are transferred in
211# messages the same way normal RRs are.
212TKEY = 249
213TSIG = 250
214
215QUERY_TYPES = {
216    A: "A",
217    NS: "NS",
218    MD: "MD",
219    MF: "MF",
220    CNAME: "CNAME",
221    SOA: "SOA",
222    MB: "MB",
223    MG: "MG",
224    MR: "MR",
225    NULL: "NULL",
226    WKS: "WKS",
227    PTR: "PTR",
228    HINFO: "HINFO",
229    MINFO: "MINFO",
230    MX: "MX",
231    TXT: "TXT",
232    RP: "RP",
233    AFSDB: "AFSDB",
234    # 19 through 27?  Eh, I'll get to 'em.
235    AAAA: "AAAA",
236    SRV: "SRV",
237    NAPTR: "NAPTR",
238    A6: "A6",
239    DNAME: "DNAME",
240    OPT: "OPT",
241    SSHFP: "SSHFP",
242    SPF: "SPF",
243    TKEY: "TKEY",
244    TSIG: "TSIG",
245}
246
247IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
248
249# "Extended" queries (Hey, half of these are deprecated, good job)
250EXT_QUERIES = {
251    IXFR: "IXFR",
252    AXFR: "AXFR",
253    MAILB: "MAILB",
254    MAILA: "MAILA",
255    ALL_RECORDS: "ALL_RECORDS",
256}
257
258REV_TYPES = {v: k for (k, v) in chain(QUERY_TYPES.items(), EXT_QUERIES.items())}
259
260IN, CS, CH, HS = range(1, 5)
261ANY = 255
262
263QUERY_CLASSES = {IN: "IN", CS: "CS", CH: "CH", HS: "HS", ANY: "ANY"}
264REV_CLASSES = {v: k for (k, v) in QUERY_CLASSES.items()}
265
266
267# Opcodes
268OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
269OP_NOTIFY = 4  # RFC 1996
270OP_UPDATE = 5  # RFC 2136
271
272
273# Response Codes
274OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
275# https://tools.ietf.org/html/rfc6891#section-9
276EBADVERSION = 16
277# RFC 2845
278EBADSIG, EBADKEY, EBADTIME = range(16, 19)
279
280
281class IRecord(Interface):
282    """
283    A single entry in a zone of authority.
284    """
285
286    TYPE = Attribute("An indicator of what kind of record this is.")
287
288
289# Backwards compatibility aliases - these should be deprecated or something I
290# suppose. -exarkun
291from twisted.names.error import (
292    AuthoritativeDomainError,
293    DNSQueryTimeoutError,
294    DomainError,
295)
296
297
298def _nameToLabels(name):
299    """
300    Split a domain name into its constituent labels.
301
302    @type name: L{bytes}
303    @param name: A fully qualified domain name (with or without a
304        trailing dot).
305
306    @return: A L{list} of labels ending with an empty label
307        representing the DNS root zone.
308    @rtype: L{list} of L{bytes}
309    """
310    if name in (b"", b"."):
311        return [b""]
312    labels = name.split(b".")
313    if labels[-1] != b"":
314        labels.append(b"")
315    return labels
316
317
318def domainString(domain):
319    """
320    Coerce a domain name string to bytes.
321
322    L{twisted.names} represents domain names as L{bytes}, but many interfaces
323    accept L{bytes} or a text string (L{unicode} on Python 2, L{str} on Python
324    3). This function coerces text strings using IDNA encoding --- see
325    L{encodings.idna}.
326
327    Note that DNS is I{case insensitive} but I{case preserving}. This function
328    doesn't normalize case, so you'll still need to do that whenever comparing
329    the strings it returns.
330
331    @param domain: A domain name.  If passed as a text string it will be
332        C{idna} encoded.
333    @type domain: L{bytes} or L{str}
334
335    @returns: L{bytes} suitable for network transmission.
336    @rtype: L{bytes}
337
338    @since: Twisted 20.3.0
339    """
340    if isinstance(domain, str):
341        domain = domain.encode("idna")
342    if not isinstance(domain, bytes):
343        raise TypeError(
344            "Expected {} or {} but found {!r} of type {}".format(
345                bytes.__name__, str.__name__, domain, type(domain)
346            )
347        )
348    return domain
349
350
351def _isSubdomainOf(descendantName, ancestorName):
352    """
353    Test whether C{descendantName} is equal to or is a I{subdomain} of
354    C{ancestorName}.
355
356    The names are compared case-insensitively.
357
358    The names are treated as byte strings containing one or more
359    DNS labels separated by B{.}.
360
361    C{descendantName} is considered equal if its sequence of labels
362    exactly matches the labels of C{ancestorName}.
363
364    C{descendantName} is considered a I{subdomain} if its sequence of
365    labels ends with the labels of C{ancestorName}.
366
367    @type descendantName: L{bytes}
368    @param descendantName: The DNS subdomain name.
369
370    @type ancestorName: L{bytes}
371    @param ancestorName: The DNS parent or ancestor domain name.
372
373    @return: C{True} if C{descendantName} is equal to or if it is a
374        subdomain of C{ancestorName}. Otherwise returns C{False}.
375    """
376    descendantLabels = _nameToLabels(descendantName.lower())
377    ancestorLabels = _nameToLabels(ancestorName.lower())
378    return descendantLabels[-len(ancestorLabels) :] == ancestorLabels
379
380
381def str2time(s):
382    """
383    Parse a string description of an interval into an integer number of seconds.
384
385    @param s: An interval definition constructed as an interval duration
386        followed by an interval unit.  An interval duration is a base ten
387        representation of an integer.  An interval unit is one of the following
388        letters: S (seconds), M (minutes), H (hours), D (days), W (weeks), or Y
389        (years).  For example: C{"3S"} indicates an interval of three seconds;
390        C{"5D"} indicates an interval of five days.  Alternatively, C{s} may be
391        any non-string and it will be returned unmodified.
392    @type s: text string (L{bytes} or L{str}) for parsing; anything else
393        for passthrough.
394
395    @return: an L{int} giving the interval represented by the string C{s}, or
396        whatever C{s} is if it is not a string.
397    """
398    suffixes = (
399        ("S", 1),
400        ("M", 60),
401        ("H", 60 * 60),
402        ("D", 60 * 60 * 24),
403        ("W", 60 * 60 * 24 * 7),
404        ("Y", 60 * 60 * 24 * 365),
405    )
406    if isinstance(s, bytes):
407        s = s.decode("ascii")
408
409    if isinstance(s, str):
410        s = s.upper().strip()
411        for (suff, mult) in suffixes:
412            if s.endswith(suff):
413                return int(float(s[:-1]) * mult)
414        try:
415            s = int(s)
416        except ValueError:
417            raise ValueError("Invalid time interval specifier: " + s)
418    return s
419
420
421def readPrecisely(file, l):
422    buff = file.read(l)
423    if len(buff) < l:
424        raise EOFError
425    return buff
426
427
428class IEncodable(Interface):
429    """
430    Interface for something which can be encoded to and decoded
431    to the DNS wire format.
432
433    A binary-mode file object (such as L{io.BytesIO}) is used as a buffer when
434    encoding or decoding.
435    """
436
437    def encode(strio, compDict=None):
438        """
439        Write a representation of this object to the given
440        file object.
441
442        @type strio: File-like object
443        @param strio: The buffer to write to. It must have a C{tell()} method.
444
445        @type compDict: L{dict} of L{bytes} to L{int} r L{None}
446        @param compDict: A mapping of names to byte offsets that have already
447        been written to the buffer, which may be used for compression (see RFC
448        1035 section 4.1.4). When L{None}, encode without compression.
449        """
450
451    def decode(strio, length=None):
452        """
453        Reconstruct an object from data read from the given
454        file object.
455
456        @type strio: File-like object
457        @param strio: A seekable buffer from which bytes may be read.
458
459        @type length: L{int} or L{None}
460        @param length: The number of bytes in this RDATA field.  Most
461        implementations can ignore this value.  Only in the case of
462        records similar to TXT where the total length is in no way
463        encoded in the data is it necessary.
464        """
465
466
467class IEncodableRecord(IEncodable, IRecord):
468    """
469    Interface for DNS records that can be encoded and decoded.
470
471    @since: Twisted 21.2.0
472    """
473
474
475@implementer(IEncodable)
476class Charstr:
477    def __init__(self, string=b""):
478        if not isinstance(string, bytes):
479            raise ValueError(f"{string!r} is not a byte string")
480        self.string = string
481
482    def encode(self, strio, compDict=None):
483        """
484        Encode this Character string into the appropriate byte format.
485
486        @type strio: file
487        @param strio: The byte representation of this Charstr will be written
488            to this file.
489        """
490        string = self.string
491        ind = len(string)
492        strio.write(_ord2bytes(ind))
493        strio.write(string)
494
495    def decode(self, strio, length=None):
496        """
497        Decode a byte string into this Charstr.
498
499        @type strio: file
500        @param strio: Bytes will be read from this file until the full string
501            is decoded.
502
503        @raise EOFError: Raised when there are not enough bytes available from
504            C{strio}.
505        """
506        self.string = b""
507        l = ord(readPrecisely(strio, 1))
508        self.string = readPrecisely(strio, l)
509
510    def __eq__(self, other: object) -> bool:
511        if isinstance(other, Charstr):
512            return self.string == other.string
513        return NotImplemented
514
515    def __hash__(self):
516        return hash(self.string)
517
518    def __str__(self) -> str:
519        """
520        Represent this L{Charstr} instance by its string value.
521        """
522        return nativeString(self.string)
523
524
525@implementer(IEncodable)
526class Name:
527    """
528    A name in the domain name system, made up of multiple labels.  For example,
529    I{twistedmatrix.com}.
530
531    @ivar name: A byte string giving the name.
532    @type name: L{bytes}
533    """
534
535    def __init__(self, name=b""):
536        """
537        @param name: A name.
538        @type name: L{bytes} or L{str}
539        """
540        self.name = domainString(name)
541
542    def encode(self, strio, compDict=None):
543        """
544        Encode this Name into the appropriate byte format.
545
546        @type strio: file
547        @param strio: The byte representation of this Name will be written to
548        this file.
549
550        @type compDict: dict
551        @param compDict: dictionary of Names that have already been encoded
552        and whose addresses may be backreferenced by this Name (for the purpose
553        of reducing the message size).
554        """
555        name = self.name
556        while name:
557            if compDict is not None:
558                if name in compDict:
559                    strio.write(struct.pack("!H", 0xC000 | compDict[name]))
560                    return
561                else:
562                    compDict[name] = strio.tell() + Message.headerSize
563            ind = name.find(b".")
564            if ind > 0:
565                label, name = name[:ind], name[ind + 1 :]
566            else:
567                # This is the last label, end the loop after handling it.
568                label = name
569                name = None
570                ind = len(label)
571            strio.write(_ord2bytes(ind))
572            strio.write(label)
573        strio.write(b"\x00")
574
575    def decode(self, strio, length=None):
576        """
577        Decode a byte string into this Name.
578
579        @type strio: file
580        @param strio: Bytes will be read from this file until the full Name
581        is decoded.
582
583        @raise EOFError: Raised when there are not enough bytes available
584        from C{strio}.
585
586        @raise ValueError: Raised when the name cannot be decoded (for example,
587            because it contains a loop).
588        """
589        visited = set()
590        self.name = b""
591        off = 0
592        while 1:
593            l = ord(readPrecisely(strio, 1))
594            if l == 0:
595                if off > 0:
596                    strio.seek(off)
597                return
598            if (l >> 6) == 3:
599                new_off = (l & 63) << 8 | ord(readPrecisely(strio, 1))
600                if new_off in visited:
601                    raise ValueError("Compression loop in encoded name")
602                visited.add(new_off)
603                if off == 0:
604                    off = strio.tell()
605                strio.seek(new_off)
606                continue
607            label = readPrecisely(strio, l)
608            if self.name == b"":
609                self.name = label
610            else:
611                self.name = self.name + b"." + label
612
613    def __eq__(self, other: object) -> bool:
614        if isinstance(other, Name):
615            return self.name.lower() == other.name.lower()
616        return NotImplemented
617
618    def __hash__(self):
619        return hash(self.name)
620
621    def __str__(self) -> str:
622        """
623        Represent this L{Name} instance by its string name.
624        """
625        return nativeString(self.name)
626
627
628@comparable
629@implementer(IEncodable)
630class Query:
631    """
632    Represent a single DNS query.
633
634    @ivar name: The name about which this query is requesting information.
635    @type name: L{Name}
636
637    @ivar type: The query type.
638    @type type: L{int}
639
640    @ivar cls: The query class.
641    @type cls: L{int}
642    """
643
644    def __init__(self, name: Union[bytes, str] = b"", type: int = A, cls: int = IN):
645        """
646        @type name: L{bytes} or L{str}
647        @param name: See L{Query.name}
648
649        @type type: L{int}
650        @param type: The query type.
651
652        @type cls: L{int}
653        @param cls: The query class.
654        """
655        self.name = Name(name)
656        self.type = type
657        self.cls = cls
658
659    def encode(self, strio, compDict=None):
660        self.name.encode(strio, compDict)
661        strio.write(struct.pack("!HH", self.type, self.cls))
662
663    def decode(self, strio, length=None):
664        self.name.decode(strio)
665        buff = readPrecisely(strio, 4)
666        self.type, self.cls = struct.unpack("!HH", buff)
667
668    def __hash__(self):
669        return hash((self.name.name.lower(), self.type, self.cls))
670
671    def __cmp__(self, other):
672        if isinstance(other, Query):
673            return cmp(
674                (self.name.name.lower(), self.type, self.cls),
675                (other.name.name.lower(), other.type, other.cls),
676            )
677        return NotImplemented
678
679    def __str__(self) -> str:
680        t = QUERY_TYPES.get(
681            self.type, EXT_QUERIES.get(self.type, "UNKNOWN (%d)" % self.type)
682        )
683        c = QUERY_CLASSES.get(self.cls, "UNKNOWN (%d)" % self.cls)
684        return f"<Query {self.name} {t} {c}>"
685
686    def __repr__(self) -> str:
687        return f"Query({self.name.name!r}, {self.type!r}, {self.cls!r})"
688
689
690@implementer(IEncodable)
691class _OPTHeader(tputil.FancyStrMixin, tputil.FancyEqMixin):
692    """
693    An OPT record header.
694
695    @ivar name: The DNS name associated with this record. Since this
696        is a pseudo record, the name is always an L{Name} instance
697        with value b'', which represents the DNS root zone. This
698        attribute is a readonly property.
699
700    @ivar type: The DNS record type. This is a fixed value of 41
701        C{dns.OPT} for OPT Record. This attribute is a readonly
702        property.
703
704    @see: L{_OPTHeader.__init__} for documentation of other public
705        instance attributes.
706
707    @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
708
709    @since: 13.2
710    """
711
712    showAttributes = (
713        ("name", lambda n: nativeString(n.name)),
714        "type",
715        "udpPayloadSize",
716        "extendedRCODE",
717        "version",
718        "dnssecOK",
719        "options",
720    )
721
722    compareAttributes = (
723        "name",
724        "type",
725        "udpPayloadSize",
726        "extendedRCODE",
727        "version",
728        "dnssecOK",
729        "options",
730    )
731
732    def __init__(
733        self,
734        udpPayloadSize=4096,
735        extendedRCODE=0,
736        version=0,
737        dnssecOK=False,
738        options=None,
739    ):
740        """
741        @type udpPayloadSize: L{int}
742        @param udpPayloadSize: The number of octets of the largest UDP
743            payload that can be reassembled and delivered in the
744            requestor's network stack.
745
746        @type extendedRCODE: L{int}
747        @param extendedRCODE: Forms the upper 8 bits of extended
748            12-bit RCODE (together with the 4 bits defined in
749            [RFC1035].  Note that EXTENDED-RCODE value 0 indicates
750            that an unextended RCODE is in use (values 0 through 15).
751
752        @type version: L{int}
753        @param version: Indicates the implementation level of the
754            setter.  Full conformance with this specification is
755            indicated by version C{0}.
756
757        @type dnssecOK: L{bool}
758        @param dnssecOK: DNSSEC OK bit as defined by [RFC3225].
759
760        @type options: L{list}
761        @param options: A L{list} of 0 or more L{_OPTVariableOption}
762            instances.
763        """
764        self.udpPayloadSize = udpPayloadSize
765        self.extendedRCODE = extendedRCODE
766        self.version = version
767        self.dnssecOK = dnssecOK
768
769        if options is None:
770            options = []
771        self.options = options
772
773    @property
774    def name(self):
775        """
776        A readonly property for accessing the C{name} attribute of
777        this record.
778
779        @return: The DNS name associated with this record. Since this
780            is a pseudo record, the name is always an L{Name} instance
781            with value b'', which represents the DNS root zone.
782        """
783        return Name(b"")
784
785    @property
786    def type(self):
787        """
788        A readonly property for accessing the C{type} attribute of
789        this record.
790
791        @return: The DNS record type. This is a fixed value of 41
792            (C{dns.OPT} for OPT Record.
793        """
794        return OPT
795
796    def encode(self, strio, compDict=None):
797        """
798        Encode this L{_OPTHeader} instance to bytes.
799
800        @type strio: file
801        @param strio: the byte representation of this L{_OPTHeader}
802            will be written to this file.
803
804        @type compDict: L{dict} or L{None}
805        @param compDict: A dictionary of backreference addresses that
806            have already been written to this stream and that may
807            be used for DNS name compression.
808        """
809        b = BytesIO()
810        for o in self.options:
811            o.encode(b)
812        optionBytes = b.getvalue()
813
814        RRHeader(
815            name=self.name.name,
816            type=self.type,
817            cls=self.udpPayloadSize,
818            ttl=(self.extendedRCODE << 24 | self.version << 16 | self.dnssecOK << 15),
819            payload=UnknownRecord(optionBytes),
820        ).encode(strio, compDict)
821
822    def decode(self, strio, length=None):
823        """
824        Decode bytes into an L{_OPTHeader} instance.
825
826        @type strio: file
827        @param strio: Bytes will be read from this file until the full
828            L{_OPTHeader} is decoded.
829
830        @type length: L{int} or L{None}
831        @param length: Not used.
832        """
833
834        h = RRHeader()
835        h.decode(strio, length)
836        h.payload = UnknownRecord(readPrecisely(strio, h.rdlength))
837
838        newOptHeader = self.fromRRHeader(h)
839
840        for attrName in self.compareAttributes:
841            if attrName not in ("name", "type"):
842                setattr(self, attrName, getattr(newOptHeader, attrName))
843
844    @classmethod
845    def fromRRHeader(cls, rrHeader):
846        """
847        A classmethod for constructing a new L{_OPTHeader} from the
848        attributes and payload of an existing L{RRHeader} instance.
849
850        @type rrHeader: L{RRHeader}
851        @param rrHeader: An L{RRHeader} instance containing an
852            L{UnknownRecord} payload.
853
854        @return: An instance of L{_OPTHeader}.
855        @rtype: L{_OPTHeader}
856        """
857        options = None
858        if rrHeader.payload is not None:
859            options = []
860            optionsBytes = BytesIO(rrHeader.payload.data)
861            optionsBytesLength = len(rrHeader.payload.data)
862            while optionsBytes.tell() < optionsBytesLength:
863                o = _OPTVariableOption()
864                o.decode(optionsBytes)
865                options.append(o)
866
867        # Decode variable options if present
868        return cls(
869            udpPayloadSize=rrHeader.cls,
870            extendedRCODE=rrHeader.ttl >> 24,
871            version=rrHeader.ttl >> 16 & 0xFF,
872            dnssecOK=(rrHeader.ttl & 0xFFFF) >> 15,
873            options=options,
874        )
875
876
877@implementer(IEncodable)
878class _OPTVariableOption(tputil.FancyStrMixin, tputil.FancyEqMixin):
879    """
880    A class to represent OPT record variable options.
881
882    @see: L{_OPTVariableOption.__init__} for documentation of public
883        instance attributes.
884
885    @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
886
887    @since: 13.2
888    """
889
890    showAttributes = ("code", ("data", nativeString))
891    compareAttributes = ("code", "data")
892
893    _fmt = "!HH"
894
895    def __init__(self, code=0, data=b""):
896        """
897        @type code: L{int}
898        @param code: The option code
899
900        @type data: L{bytes}
901        @param data: The option data
902        """
903        self.code = code
904        self.data = data
905
906    def encode(self, strio, compDict=None):
907        """
908        Encode this L{_OPTVariableOption} to bytes.
909
910        @type strio: file
911        @param strio: the byte representation of this
912            L{_OPTVariableOption} will be written to this file.
913
914        @type compDict: L{dict} or L{None}
915        @param compDict: A dictionary of backreference addresses that
916            have already been written to this stream and that may
917            be used for DNS name compression.
918        """
919        strio.write(struct.pack(self._fmt, self.code, len(self.data)) + self.data)
920
921    def decode(self, strio, length=None):
922        """
923        Decode bytes into an L{_OPTVariableOption} instance.
924
925        @type strio: file
926        @param strio: Bytes will be read from this file until the full
927            L{_OPTVariableOption} is decoded.
928
929        @type length: L{int} or L{None}
930        @param length: Not used.
931        """
932        l = struct.calcsize(self._fmt)
933        buff = readPrecisely(strio, l)
934        self.code, length = struct.unpack(self._fmt, buff)
935        self.data = readPrecisely(strio, length)
936
937
938@implementer(IEncodable)
939class RRHeader(tputil.FancyEqMixin):
940    """
941    A resource record header.
942
943    @cvar fmt: L{str} specifying the byte format of an RR.
944
945    @ivar name: The name about which this reply contains information.
946    @type name: L{Name}
947
948    @ivar type: The query type of the original request.
949    @type type: L{int}
950
951    @ivar cls: The query class of the original request.
952
953    @ivar ttl: The time-to-live for this record.
954    @type ttl: L{int}
955
956    @ivar payload: The record described by this header.
957    @type payload: L{IEncodableRecord} or L{None}
958
959    @ivar auth: A L{bool} indicating whether this C{RRHeader} was parsed from
960        an authoritative message.
961    """
962
963    compareAttributes = ("name", "type", "cls", "ttl", "payload", "auth")
964
965    fmt = "!HHIH"
966
967    rdlength = None
968
969    cachedResponse = None
970
971    def __init__(
972        self,
973        name: Union[bytes, str] = b"",
974        type: int = A,
975        cls: int = IN,
976        ttl: SupportsInt = 0,
977        payload: Optional[IEncodableRecord] = None,
978        auth: bool = False,
979    ):
980        """
981        @type name: L{bytes} or L{str}
982        @param name: See L{RRHeader.name}
983
984        @type type: L{int}
985        @param type: The query type.
986
987        @type cls: L{int}
988        @param cls: The query class.
989
990        @type ttl: L{int}
991        @param ttl: Time to live for this record.  This will be
992            converted to an L{int}.
993
994        @type payload: L{IEncodableRecord} or L{None}
995        @param payload: An optional Query Type specific data object.
996
997        @raises TypeError: if the ttl cannot be converted to an L{int}.
998        @raises ValueError: if the ttl is negative.
999        @raises ValueError: if the payload type is not equal to the C{type}
1000                            argument.
1001        """
1002        payloadType = None if payload is None else payload.TYPE
1003        if payloadType is not None and payloadType != type:
1004            raise ValueError(
1005                "Payload type (%s) does not match given type (%s)"
1006                % (
1007                    QUERY_TYPES.get(payloadType, payloadType),
1008                    QUERY_TYPES.get(type, type),
1009                )
1010            )
1011
1012        integralTTL = int(ttl)
1013
1014        if integralTTL < 0:
1015            raise ValueError("TTL cannot be negative")
1016
1017        self.name = Name(name)
1018        self.type = type
1019        self.cls = cls
1020        self.ttl = integralTTL
1021        self.payload = payload
1022        self.auth = auth
1023
1024    def encode(self, strio, compDict=None):
1025        self.name.encode(strio, compDict)
1026        strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
1027        if self.payload:
1028            prefix = strio.tell()
1029            self.payload.encode(strio, compDict)
1030            aft = strio.tell()
1031            strio.seek(prefix - 2, 0)
1032            strio.write(struct.pack("!H", aft - prefix))
1033            strio.seek(aft, 0)
1034
1035    def decode(self, strio, length=None):
1036        self.name.decode(strio)
1037        l = struct.calcsize(self.fmt)
1038        buff = readPrecisely(strio, l)
1039        r = struct.unpack(self.fmt, buff)
1040        self.type, self.cls, self.ttl, self.rdlength = r
1041
1042    def isAuthoritative(self):
1043        return self.auth
1044
1045    def __str__(self) -> str:
1046        t = QUERY_TYPES.get(
1047            self.type, EXT_QUERIES.get(self.type, "UNKNOWN (%d)" % self.type)
1048        )
1049        c = QUERY_CLASSES.get(self.cls, "UNKNOWN (%d)" % self.cls)
1050        return "<RR name=%s type=%s class=%s ttl=%ds auth=%s>" % (
1051            self.name,
1052            t,
1053            c,
1054            self.ttl,
1055            self.auth and "True" or "False",
1056        )
1057
1058    __repr__ = __str__
1059
1060
1061@implementer(IEncodableRecord)
1062class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
1063    """
1064    A Resource Record which consists of a single RFC 1035 domain-name.
1065
1066    @type name: L{Name}
1067    @ivar name: The name associated with this record.
1068
1069    @type ttl: L{int}
1070    @ivar ttl: The maximum number of seconds which this record should be
1071        cached.
1072    """
1073
1074    showAttributes = (("name", "name", "%s"), "ttl")
1075    compareAttributes = ("name", "ttl")
1076
1077    TYPE: Optional[int] = None
1078    name = None
1079
1080    def __init__(self, name=b"", ttl=None):
1081        """
1082        @param name: See L{SimpleRecord.name}
1083        @type name: L{bytes} or L{str}
1084        """
1085        self.name = Name(name)
1086        self.ttl = str2time(ttl)
1087
1088    def encode(self, strio, compDict=None):
1089        self.name.encode(strio, compDict)
1090
1091    def decode(self, strio, length=None):
1092        self.name = Name()
1093        self.name.decode(strio)
1094
1095    def __hash__(self):
1096        return hash(self.name)
1097
1098
1099# Kinds of RRs - oh my!
1100class Record_NS(SimpleRecord):
1101    """
1102    An authoritative nameserver.
1103    """
1104
1105    TYPE = NS
1106    fancybasename = "NS"
1107
1108
1109class Record_MD(SimpleRecord):
1110    """
1111    A mail destination.
1112
1113    This record type is obsolete.
1114
1115    @see: L{Record_MX}
1116    """
1117
1118    TYPE = MD
1119    fancybasename = "MD"
1120
1121
1122class Record_MF(SimpleRecord):
1123    """
1124    A mail forwarder.
1125
1126    This record type is obsolete.
1127
1128    @see: L{Record_MX}
1129    """
1130
1131    TYPE = MF
1132    fancybasename = "MF"
1133
1134
1135class Record_CNAME(SimpleRecord):
1136    """
1137    The canonical name for an alias.
1138    """
1139
1140    TYPE = CNAME
1141    fancybasename = "CNAME"
1142
1143
1144class Record_MB(SimpleRecord):
1145    """
1146    A mailbox domain name.
1147
1148    This is an experimental record type.
1149    """
1150
1151    TYPE = MB
1152    fancybasename = "MB"
1153
1154
1155class Record_MG(SimpleRecord):
1156    """
1157    A mail group member.
1158
1159    This is an experimental record type.
1160    """
1161
1162    TYPE = MG
1163    fancybasename = "MG"
1164
1165
1166class Record_MR(SimpleRecord):
1167    """
1168    A mail rename domain name.
1169
1170    This is an experimental record type.
1171    """
1172
1173    TYPE = MR
1174    fancybasename = "MR"
1175
1176
1177class Record_PTR(SimpleRecord):
1178    """
1179    A domain name pointer.
1180    """
1181
1182    TYPE = PTR
1183    fancybasename = "PTR"
1184
1185
1186class Record_DNAME(SimpleRecord):
1187    """
1188    A non-terminal DNS name redirection.
1189
1190    This record type provides the capability to map an entire subtree of the
1191    DNS name space to another domain.  It differs from the CNAME record which
1192    maps a single node of the name space.
1193
1194    @see: U{http://www.faqs.org/rfcs/rfc2672.html}
1195    @see: U{http://www.faqs.org/rfcs/rfc3363.html}
1196    """
1197
1198    TYPE = DNAME
1199    fancybasename = "DNAME"
1200
1201
1202@implementer(IEncodableRecord)
1203class Record_A(tputil.FancyEqMixin):
1204    """
1205    An IPv4 host address.
1206
1207    @type address: L{bytes}
1208    @ivar address: The packed network-order representation of the IPv4 address
1209        associated with this record.
1210
1211    @type ttl: L{int}
1212    @ivar ttl: The maximum number of seconds which this record should be
1213        cached.
1214    """
1215
1216    compareAttributes = ("address", "ttl")
1217
1218    TYPE = A
1219    address = None
1220
1221    def __init__(self, address="0.0.0.0", ttl=None):
1222        """
1223        @type address: L{bytes} or L{str}
1224        @param address: The IPv4 address associated with this record, in
1225            quad-dotted notation.
1226        """
1227        if isinstance(address, bytes):
1228            address = address.decode("ascii")
1229
1230        address = socket.inet_aton(address)
1231        self.address = address
1232        self.ttl = str2time(ttl)
1233
1234    def encode(self, strio, compDict=None):
1235        strio.write(self.address)
1236
1237    def decode(self, strio, length=None):
1238        self.address = readPrecisely(strio, 4)
1239
1240    def __hash__(self):
1241        return hash(self.address)
1242
1243    def __str__(self) -> str:
1244        return f"<A address={self.dottedQuad()} ttl={self.ttl}>"
1245
1246    __repr__ = __str__
1247
1248    def dottedQuad(self):
1249        return socket.inet_ntoa(self.address)
1250
1251
1252@implementer(IEncodableRecord)
1253class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
1254    """
1255    Marks the start of a zone of authority.
1256
1257    This record describes parameters which are shared by all records within a
1258    particular zone.
1259
1260    @type mname: L{Name}
1261    @ivar mname: The domain-name of the name server that was the original or
1262        primary source of data for this zone.
1263
1264    @type rname: L{Name}
1265    @ivar rname: A domain-name which specifies the mailbox of the person
1266        responsible for this zone.
1267
1268    @type serial: L{int}
1269    @ivar serial: The unsigned 32 bit version number of the original copy of
1270        the zone.  Zone transfers preserve this value.  This value wraps and
1271        should be compared using sequence space arithmetic.
1272
1273    @type refresh: L{int}
1274    @ivar refresh: A 32 bit time interval before the zone should be refreshed.
1275
1276    @type minimum: L{int}
1277    @ivar minimum: The unsigned 32 bit minimum TTL field that should be
1278        exported with any RR from this zone.
1279
1280    @type expire: L{int}
1281    @ivar expire: A 32 bit time value that specifies the upper limit on the
1282        time interval that can elapse before the zone is no longer
1283        authoritative.
1284
1285    @type retry: L{int}
1286    @ivar retry: A 32 bit time interval that should elapse before a failed
1287        refresh should be retried.
1288
1289    @type ttl: L{int}
1290    @ivar ttl: The default TTL to use for records served from this zone.
1291    """
1292
1293    fancybasename = "SOA"
1294    compareAttributes = (
1295        "serial",
1296        "mname",
1297        "rname",
1298        "refresh",
1299        "expire",
1300        "retry",
1301        "minimum",
1302        "ttl",
1303    )
1304    showAttributes = (
1305        ("mname", "mname", "%s"),
1306        ("rname", "rname", "%s"),
1307        "serial",
1308        "refresh",
1309        "retry",
1310        "expire",
1311        "minimum",
1312        "ttl",
1313    )
1314
1315    TYPE = SOA
1316
1317    def __init__(
1318        self,
1319        mname=b"",
1320        rname=b"",
1321        serial=0,
1322        refresh=0,
1323        retry=0,
1324        expire=0,
1325        minimum=0,
1326        ttl=None,
1327    ):
1328        """
1329        @param mname: See L{Record_SOA.mname}
1330        @type mname: L{bytes} or L{str}
1331
1332        @param rname: See L{Record_SOA.rname}
1333        @type rname: L{bytes} or L{str}
1334        """
1335        self.mname, self.rname = Name(mname), Name(rname)
1336        self.serial, self.refresh = str2time(serial), str2time(refresh)
1337        self.minimum, self.expire = str2time(minimum), str2time(expire)
1338        self.retry = str2time(retry)
1339        self.ttl = str2time(ttl)
1340
1341    def encode(self, strio, compDict=None):
1342        self.mname.encode(strio, compDict)
1343        self.rname.encode(strio, compDict)
1344        strio.write(
1345            struct.pack(
1346                "!LlllL",
1347                self.serial,
1348                self.refresh,
1349                self.retry,
1350                self.expire,
1351                self.minimum,
1352            )
1353        )
1354
1355    def decode(self, strio, length=None):
1356        self.mname, self.rname = Name(), Name()
1357        self.mname.decode(strio)
1358        self.rname.decode(strio)
1359        r = struct.unpack("!LlllL", readPrecisely(strio, 20))
1360        self.serial, self.refresh, self.retry, self.expire, self.minimum = r
1361
1362    def __hash__(self):
1363        return hash(
1364            (self.serial, self.mname, self.rname, self.refresh, self.expire, self.retry)
1365        )
1366
1367
1368@implementer(IEncodableRecord)
1369class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
1370    """
1371    A null record.
1372
1373    This is an experimental record type.
1374
1375    @type ttl: L{int}
1376    @ivar ttl: The maximum number of seconds which this record should be
1377        cached.
1378    """
1379
1380    fancybasename = "NULL"
1381    showAttributes = (("payload", _nicebytes), "ttl")
1382    compareAttributes = ("payload", "ttl")
1383
1384    TYPE = NULL
1385
1386    def __init__(self, payload=None, ttl=None):
1387        self.payload = payload
1388        self.ttl = str2time(ttl)
1389
1390    def encode(self, strio, compDict=None):
1391        strio.write(self.payload)
1392
1393    def decode(self, strio, length=None):
1394        self.payload = readPrecisely(strio, length)
1395
1396    def __hash__(self):
1397        return hash(self.payload)
1398
1399
1400@implementer(IEncodableRecord)
1401class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
1402    """
1403    A well known service description.
1404
1405    This record type is obsolete.  See L{Record_SRV}.
1406
1407    @type address: L{bytes}
1408    @ivar address: The packed network-order representation of the IPv4 address
1409        associated with this record.
1410
1411    @type protocol: L{int}
1412    @ivar protocol: The 8 bit IP protocol number for which this service map is
1413        relevant.
1414
1415    @type map: L{bytes}
1416    @ivar map: A bitvector indicating the services available at the specified
1417        address.
1418
1419    @type ttl: L{int}
1420    @ivar ttl: The maximum number of seconds which this record should be
1421        cached.
1422    """
1423
1424    fancybasename = "WKS"
1425    compareAttributes = ("address", "protocol", "map", "ttl")
1426    showAttributes = [("_address", "address", "%s"), "protocol", "ttl"]
1427
1428    TYPE = WKS
1429
1430    @property
1431    def _address(self):
1432        return socket.inet_ntoa(self.address)
1433
1434    def __init__(self, address="0.0.0.0", protocol=0, map=b"", ttl=None):
1435        """
1436        @type address: L{bytes} or L{str}
1437        @param address: The IPv4 address associated with this record, in
1438            quad-dotted notation.
1439        """
1440        if isinstance(address, bytes):
1441            address = address.decode("idna")
1442
1443        self.address = socket.inet_aton(address)
1444        self.protocol, self.map = protocol, map
1445        self.ttl = str2time(ttl)
1446
1447    def encode(self, strio, compDict=None):
1448        strio.write(self.address)
1449        strio.write(struct.pack("!B", self.protocol))
1450        strio.write(self.map)
1451
1452    def decode(self, strio, length=None):
1453        self.address = readPrecisely(strio, 4)
1454        self.protocol = struct.unpack("!B", readPrecisely(strio, 1))[0]
1455        self.map = readPrecisely(strio, length - 5)
1456
1457    def __hash__(self):
1458        return hash((self.address, self.protocol, self.map))
1459
1460
1461@implementer(IEncodableRecord)
1462class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
1463    """
1464    An IPv6 host address.
1465
1466    @type address: L{bytes}
1467    @ivar address: The packed network-order representation of the IPv6 address
1468        associated with this record.
1469
1470    @type ttl: L{int}
1471    @ivar ttl: The maximum number of seconds which this record should be
1472        cached.
1473
1474    @see: U{http://www.faqs.org/rfcs/rfc1886.html}
1475    """
1476
1477    TYPE = AAAA
1478
1479    fancybasename = "AAAA"
1480    showAttributes = (("_address", "address", "%s"), "ttl")
1481    compareAttributes = ("address", "ttl")
1482
1483    @property
1484    def _address(self):
1485        return socket.inet_ntop(AF_INET6, self.address)
1486
1487    def __init__(self, address="::", ttl=None):
1488        """
1489        @type address: L{bytes} or L{str}
1490        @param address: The IPv6 address for this host, in RFC 2373 format.
1491        """
1492        if isinstance(address, bytes):
1493            address = address.decode("idna")
1494
1495        self.address = socket.inet_pton(AF_INET6, address)
1496        self.ttl = str2time(ttl)
1497
1498    def encode(self, strio, compDict=None):
1499        strio.write(self.address)
1500
1501    def decode(self, strio, length=None):
1502        self.address = readPrecisely(strio, 16)
1503
1504    def __hash__(self):
1505        return hash(self.address)
1506
1507
1508@implementer(IEncodableRecord)
1509class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
1510    """
1511    An IPv6 address.
1512
1513    This is an experimental record type.
1514
1515    @type prefixLen: L{int}
1516    @ivar prefixLen: The length of the suffix.
1517
1518    @type suffix: L{bytes}
1519    @ivar suffix: An IPv6 address suffix in network order.
1520
1521    @type prefix: L{Name}
1522    @ivar prefix: If specified, a name which will be used as a prefix for other
1523        A6 records.
1524
1525    @type bytes: L{int}
1526    @ivar bytes: The length of the prefix.
1527
1528    @type ttl: L{int}
1529    @ivar ttl: The maximum number of seconds which this record should be
1530        cached.
1531
1532    @see: U{http://www.faqs.org/rfcs/rfc2874.html}
1533    @see: U{http://www.faqs.org/rfcs/rfc3363.html}
1534    @see: U{http://www.faqs.org/rfcs/rfc3364.html}
1535    """
1536
1537    TYPE = A6
1538
1539    fancybasename = "A6"
1540    showAttributes = (("_suffix", "suffix", "%s"), ("prefix", "prefix", "%s"), "ttl")
1541    compareAttributes = ("prefixLen", "prefix", "suffix", "ttl")
1542
1543    @property
1544    def _suffix(self):
1545        return socket.inet_ntop(AF_INET6, self.suffix)
1546
1547    def __init__(self, prefixLen=0, suffix="::", prefix=b"", ttl=None):
1548        """
1549        @param suffix: An IPv6 address suffix in in RFC 2373 format.
1550        @type suffix: L{bytes} or L{str}
1551
1552        @param prefix: An IPv6 address prefix for other A6 records.
1553        @type prefix: L{bytes} or L{str}
1554        """
1555        if isinstance(suffix, bytes):
1556            suffix = suffix.decode("idna")
1557
1558        self.prefixLen = prefixLen
1559        self.suffix = socket.inet_pton(AF_INET6, suffix)
1560        self.prefix = Name(prefix)
1561        self.bytes = int((128 - self.prefixLen) / 8.0)
1562        self.ttl = str2time(ttl)
1563
1564    def encode(self, strio, compDict=None):
1565        strio.write(struct.pack("!B", self.prefixLen))
1566        if self.bytes:
1567            strio.write(self.suffix[-self.bytes :])
1568        if self.prefixLen:
1569            # This may not be compressed
1570            self.prefix.encode(strio, None)
1571
1572    def decode(self, strio, length=None):
1573        self.prefixLen = struct.unpack("!B", readPrecisely(strio, 1))[0]
1574        self.bytes = int((128 - self.prefixLen) / 8.0)
1575        if self.bytes:
1576            self.suffix = b"\x00" * (16 - self.bytes) + readPrecisely(strio, self.bytes)
1577        if self.prefixLen:
1578            self.prefix.decode(strio)
1579
1580    def __eq__(self, other: object) -> bool:
1581        if isinstance(other, Record_A6):
1582            return (
1583                self.prefixLen == other.prefixLen
1584                and self.suffix[-self.bytes :] == other.suffix[-self.bytes :]
1585                and self.prefix == other.prefix
1586                and self.ttl == other.ttl
1587            )
1588        return NotImplemented
1589
1590    def __hash__(self):
1591        return hash((self.prefixLen, self.suffix[-self.bytes :], self.prefix))
1592
1593    def __str__(self) -> str:
1594        return "<A6 %s %s (%d) ttl=%s>" % (
1595            self.prefix,
1596            socket.inet_ntop(AF_INET6, self.suffix),
1597            self.prefixLen,
1598            self.ttl,
1599        )
1600
1601
1602@implementer(IEncodableRecord)
1603class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
1604    """
1605    The location of the server(s) for a specific protocol and domain.
1606
1607    This is an experimental record type.
1608
1609    @type priority: L{int}
1610    @ivar priority: The priority of this target host.  A client MUST attempt to
1611        contact the target host with the lowest-numbered priority it can reach;
1612        target hosts with the same priority SHOULD be tried in an order defined
1613        by the weight field.
1614
1615    @type weight: L{int}
1616    @ivar weight: Specifies a relative weight for entries with the same
1617        priority. Larger weights SHOULD be given a proportionately higher
1618        probability of being selected.
1619
1620    @type port: L{int}
1621    @ivar port: The port on this target host of this service.
1622
1623    @type target: L{Name}
1624    @ivar target: The domain name of the target host.  There MUST be one or
1625        more address records for this name, the name MUST NOT be an alias (in
1626        the sense of RFC 1034 or RFC 2181).  Implementors are urged, but not
1627        required, to return the address record(s) in the Additional Data
1628        section.  Unless and until permitted by future standards action, name
1629        compression is not to be used for this field.
1630
1631    @type ttl: L{int}
1632    @ivar ttl: The maximum number of seconds which this record should be
1633        cached.
1634
1635    @see: U{http://www.faqs.org/rfcs/rfc2782.html}
1636    """
1637
1638    TYPE = SRV
1639
1640    fancybasename = "SRV"
1641    compareAttributes = ("priority", "weight", "target", "port", "ttl")
1642    showAttributes = ("priority", "weight", ("target", "target", "%s"), "port", "ttl")
1643
1644    def __init__(self, priority=0, weight=0, port=0, target=b"", ttl=None):
1645        """
1646        @param target: See L{Record_SRV.target}
1647        @type target: L{bytes} or L{str}
1648        """
1649        self.priority = int(priority)
1650        self.weight = int(weight)
1651        self.port = int(port)
1652        self.target = Name(target)
1653        self.ttl = str2time(ttl)
1654
1655    def encode(self, strio, compDict=None):
1656        strio.write(struct.pack("!HHH", self.priority, self.weight, self.port))
1657        # This can't be compressed
1658        self.target.encode(strio, None)
1659
1660    def decode(self, strio, length=None):
1661        r = struct.unpack("!HHH", readPrecisely(strio, struct.calcsize("!HHH")))
1662        self.priority, self.weight, self.port = r
1663        self.target = Name()
1664        self.target.decode(strio)
1665
1666    def __hash__(self):
1667        return hash((self.priority, self.weight, self.port, self.target))
1668
1669
1670@implementer(IEncodableRecord)
1671class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
1672    """
1673    The location of the server(s) for a specific protocol and domain.
1674
1675    @type order: L{int}
1676    @ivar order: An integer specifying the order in which the NAPTR records
1677        MUST be processed to ensure the correct ordering of rules.  Low numbers
1678        are processed before high numbers.
1679
1680    @type preference: L{int}
1681    @ivar preference: An integer that specifies the order in which NAPTR
1682        records with equal "order" values SHOULD be processed, low numbers
1683        being processed before high numbers.
1684
1685    @type flag: L{Charstr}
1686    @ivar flag: A <character-string> containing flags to control aspects of the
1687        rewriting and interpretation of the fields in the record.  Flags
1688        are single characters from the set [A-Z0-9].  The case of the alphabetic
1689        characters is not significant.
1690
1691        At this time only four flags, "S", "A", "U", and "P", are defined.
1692
1693    @type service: L{Charstr}
1694    @ivar service: Specifies the service(s) available down this rewrite path.
1695        It may also specify the particular protocol that is used to talk with a
1696        service.  A protocol MUST be specified if the flags field states that
1697        the NAPTR is terminal.
1698
1699    @type regexp: L{Charstr}
1700    @ivar regexp: A STRING containing a substitution expression that is applied
1701        to the original string held by the client in order to construct the
1702        next domain name to lookup.
1703
1704    @type replacement: L{Name}
1705    @ivar replacement: The next NAME to query for NAPTR, SRV, or address
1706        records depending on the value of the flags field.  This MUST be a
1707        fully qualified domain-name.
1708
1709    @type ttl: L{int}
1710    @ivar ttl: The maximum number of seconds which this record should be
1711        cached.
1712
1713    @see: U{http://www.faqs.org/rfcs/rfc2915.html}
1714    """
1715
1716    TYPE = NAPTR
1717
1718    compareAttributes = (
1719        "order",
1720        "preference",
1721        "flags",
1722        "service",
1723        "regexp",
1724        "replacement",
1725    )
1726    fancybasename = "NAPTR"
1727
1728    showAttributes = (
1729        "order",
1730        "preference",
1731        ("flags", "flags", "%s"),
1732        ("service", "service", "%s"),
1733        ("regexp", "regexp", "%s"),
1734        ("replacement", "replacement", "%s"),
1735        "ttl",
1736    )
1737
1738    def __init__(
1739        self,
1740        order=0,
1741        preference=0,
1742        flags=b"",
1743        service=b"",
1744        regexp=b"",
1745        replacement=b"",
1746        ttl=None,
1747    ):
1748        """
1749        @param replacement: See L{Record_NAPTR.replacement}
1750        @type replacement: L{bytes} or L{str}
1751        """
1752        self.order = int(order)
1753        self.preference = int(preference)
1754        self.flags = Charstr(flags)
1755        self.service = Charstr(service)
1756        self.regexp = Charstr(regexp)
1757        self.replacement = Name(replacement)
1758        self.ttl = str2time(ttl)
1759
1760    def encode(self, strio, compDict=None):
1761        strio.write(struct.pack("!HH", self.order, self.preference))
1762        # This can't be compressed
1763        self.flags.encode(strio, None)
1764        self.service.encode(strio, None)
1765        self.regexp.encode(strio, None)
1766        self.replacement.encode(strio, None)
1767
1768    def decode(self, strio, length=None):
1769        r = struct.unpack("!HH", readPrecisely(strio, struct.calcsize("!HH")))
1770        self.order, self.preference = r
1771        self.flags = Charstr()
1772        self.service = Charstr()
1773        self.regexp = Charstr()
1774        self.replacement = Name()
1775        self.flags.decode(strio)
1776        self.service.decode(strio)
1777        self.regexp.decode(strio)
1778        self.replacement.decode(strio)
1779
1780    def __hash__(self):
1781        return hash(
1782            (
1783                self.order,
1784                self.preference,
1785                self.flags,
1786                self.service,
1787                self.regexp,
1788                self.replacement,
1789            )
1790        )
1791
1792
1793@implementer(IEncodableRecord)
1794class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
1795    """
1796    Map from a domain name to the name of an AFS cell database server.
1797
1798    @type subtype: L{int}
1799    @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
1800        Volume Location Server for the named AFS cell.  In the case of subtype
1801        2, the host has an authenticated name server holding the cell-root
1802        directory node for the named DCE/NCA cell.
1803
1804    @type hostname: L{Name}
1805    @ivar hostname: The domain name of a host that has a server for the cell
1806        named by this record.
1807
1808    @type ttl: L{int}
1809    @ivar ttl: The maximum number of seconds which this record should be
1810        cached.
1811
1812    @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1813    """
1814
1815    TYPE = AFSDB
1816
1817    fancybasename = "AFSDB"
1818    compareAttributes = ("subtype", "hostname", "ttl")
1819    showAttributes = ("subtype", ("hostname", "hostname", "%s"), "ttl")
1820
1821    def __init__(self, subtype=0, hostname=b"", ttl=None):
1822        """
1823        @param hostname: See L{Record_AFSDB.hostname}
1824        @type hostname: L{bytes} or L{str}
1825        """
1826        self.subtype = int(subtype)
1827        self.hostname = Name(hostname)
1828        self.ttl = str2time(ttl)
1829
1830    def encode(self, strio, compDict=None):
1831        strio.write(struct.pack("!H", self.subtype))
1832        self.hostname.encode(strio, compDict)
1833
1834    def decode(self, strio, length=None):
1835        r = struct.unpack("!H", readPrecisely(strio, struct.calcsize("!H")))
1836        (self.subtype,) = r
1837        self.hostname.decode(strio)
1838
1839    def __hash__(self):
1840        return hash((self.subtype, self.hostname))
1841
1842
1843@implementer(IEncodableRecord)
1844class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
1845    """
1846    The responsible person for a domain.
1847
1848    @type mbox: L{Name}
1849    @ivar mbox: A domain name that specifies the mailbox for the responsible
1850        person.
1851
1852    @type txt: L{Name}
1853    @ivar txt: A domain name for which TXT RR's exist (indirection through
1854        which allows information sharing about the contents of this RP record).
1855
1856    @type ttl: L{int}
1857    @ivar ttl: The maximum number of seconds which this record should be
1858        cached.
1859
1860    @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1861    """
1862
1863    TYPE = RP
1864
1865    fancybasename = "RP"
1866    compareAttributes = ("mbox", "txt", "ttl")
1867    showAttributes = (("mbox", "mbox", "%s"), ("txt", "txt", "%s"), "ttl")
1868
1869    def __init__(self, mbox=b"", txt=b"", ttl=None):
1870        """
1871        @param mbox: See L{Record_RP.mbox}.
1872        @type mbox: L{bytes} or L{str}
1873
1874        @param txt: See L{Record_RP.txt}
1875        @type txt: L{bytes} or L{str}
1876        """
1877        self.mbox = Name(mbox)
1878        self.txt = Name(txt)
1879        self.ttl = str2time(ttl)
1880
1881    def encode(self, strio, compDict=None):
1882        self.mbox.encode(strio, compDict)
1883        self.txt.encode(strio, compDict)
1884
1885    def decode(self, strio, length=None):
1886        self.mbox = Name()
1887        self.txt = Name()
1888        self.mbox.decode(strio)
1889        self.txt.decode(strio)
1890
1891    def __hash__(self):
1892        return hash((self.mbox, self.txt))
1893
1894
1895@implementer(IEncodableRecord)
1896class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
1897    """
1898    Host information.
1899
1900    @type cpu: L{bytes}
1901    @ivar cpu: Specifies the CPU type.
1902
1903    @type os: L{bytes}
1904    @ivar os: Specifies the OS.
1905
1906    @type ttl: L{int}
1907    @ivar ttl: The maximum number of seconds which this record should be
1908        cached.
1909    """
1910
1911    TYPE = HINFO
1912
1913    fancybasename = "HINFO"
1914    showAttributes = (("cpu", _nicebytes), ("os", _nicebytes), "ttl")
1915    compareAttributes = ("cpu", "os", "ttl")
1916
1917    def __init__(self, cpu=b"", os=b"", ttl=None):
1918        self.cpu, self.os = cpu, os
1919        self.ttl = str2time(ttl)
1920
1921    def encode(self, strio, compDict=None):
1922        strio.write(struct.pack("!B", len(self.cpu)) + self.cpu)
1923        strio.write(struct.pack("!B", len(self.os)) + self.os)
1924
1925    def decode(self, strio, length=None):
1926        cpu = struct.unpack("!B", readPrecisely(strio, 1))[0]
1927        self.cpu = readPrecisely(strio, cpu)
1928        os = struct.unpack("!B", readPrecisely(strio, 1))[0]
1929        self.os = readPrecisely(strio, os)
1930
1931    def __eq__(self, other: object) -> bool:
1932        if isinstance(other, Record_HINFO):
1933            return (
1934                self.os.lower() == other.os.lower()
1935                and self.cpu.lower() == other.cpu.lower()
1936                and self.ttl == other.ttl
1937            )
1938        return NotImplemented
1939
1940    def __hash__(self):
1941        return hash((self.os.lower(), self.cpu.lower()))
1942
1943
1944@implementer(IEncodableRecord)
1945class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
1946    """
1947    Mailbox or mail list information.
1948
1949    This is an experimental record type.
1950
1951    @type rmailbx: L{Name}
1952    @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
1953        for the mailing list or mailbox.  If this domain name names the root,
1954        the owner of the MINFO RR is responsible for itself.
1955
1956    @type emailbx: L{Name}
1957    @ivar emailbx: A domain-name which specifies a mailbox which is to receive
1958        error messages related to the mailing list or mailbox specified by the
1959        owner of the MINFO record.  If this domain name names the root, errors
1960        should be returned to the sender of the message.
1961
1962    @type ttl: L{int}
1963    @ivar ttl: The maximum number of seconds which this record should be
1964        cached.
1965    """
1966
1967    TYPE = MINFO
1968
1969    rmailbx = None
1970    emailbx = None
1971
1972    fancybasename = "MINFO"
1973    compareAttributes = ("rmailbx", "emailbx", "ttl")
1974    showAttributes = (
1975        ("rmailbx", "responsibility", "%s"),
1976        ("emailbx", "errors", "%s"),
1977        "ttl",
1978    )
1979
1980    def __init__(self, rmailbx=b"", emailbx=b"", ttl=None):
1981        """
1982        @param rmailbx: See L{Record_MINFO.rmailbx}.
1983        @type rmailbx: L{bytes} or L{str}
1984
1985        @param emailbx: See L{Record_MINFO.rmailbx}.
1986        @type emailbx: L{bytes} or L{str}
1987        """
1988        self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
1989        self.ttl = str2time(ttl)
1990
1991    def encode(self, strio, compDict=None):
1992        self.rmailbx.encode(strio, compDict)
1993        self.emailbx.encode(strio, compDict)
1994
1995    def decode(self, strio, length=None):
1996        self.rmailbx, self.emailbx = Name(), Name()
1997        self.rmailbx.decode(strio)
1998        self.emailbx.decode(strio)
1999
2000    def __hash__(self):
2001        return hash((self.rmailbx, self.emailbx))
2002
2003
2004@implementer(IEncodableRecord)
2005class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
2006    """
2007    Mail exchange.
2008
2009    @type preference: L{int}
2010    @ivar preference: Specifies the preference given to this RR among others at
2011        the same owner.  Lower values are preferred.
2012
2013    @type name: L{Name}
2014    @ivar name: A domain-name which specifies a host willing to act as a mail
2015        exchange.
2016
2017    @type ttl: L{int}
2018    @ivar ttl: The maximum number of seconds which this record should be
2019        cached.
2020    """
2021
2022    TYPE = MX
2023
2024    fancybasename = "MX"
2025    compareAttributes = ("preference", "name", "ttl")
2026    showAttributes = ("preference", ("name", "name", "%s"), "ttl")
2027
2028    def __init__(self, preference=0, name=b"", ttl=None, **kwargs):
2029        """
2030        @param name: See L{Record_MX.name}.
2031        @type name: L{bytes} or L{str}
2032        """
2033        self.preference = int(preference)
2034        self.name = Name(kwargs.get("exchange", name))
2035        self.ttl = str2time(ttl)
2036
2037    def encode(self, strio, compDict=None):
2038        strio.write(struct.pack("!H", self.preference))
2039        self.name.encode(strio, compDict)
2040
2041    def decode(self, strio, length=None):
2042        self.preference = struct.unpack("!H", readPrecisely(strio, 2))[0]
2043        self.name = Name()
2044        self.name.decode(strio)
2045
2046    def __hash__(self):
2047        return hash((self.preference, self.name))
2048
2049
2050@implementer(IEncodableRecord)
2051class Record_SSHFP(tputil.FancyEqMixin, tputil.FancyStrMixin):
2052    """
2053    A record containing the fingerprint of an SSH key.
2054
2055    @type algorithm: L{int}
2056    @ivar algorithm: The SSH key's algorithm, such as L{ALGORITHM_RSA}.
2057        Note that the numbering used for SSH key algorithms is specific
2058        to the SSHFP record, and is not the same as the numbering
2059        used for KEY or SIG records.
2060
2061    @type fingerprintType: L{int}
2062    @ivar fingerprintType: The fingerprint type,
2063        such as L{FINGERPRINT_TYPE_SHA256}.
2064
2065    @type fingerprint: L{bytes}
2066    @ivar fingerprint: The key's fingerprint, e.g. a 32-byte SHA-256 digest.
2067
2068    @cvar ALGORITHM_RSA: The algorithm value for C{ssh-rsa} keys.
2069    @cvar ALGORITHM_DSS: The algorithm value for C{ssh-dss} keys.
2070    @cvar ALGORITHM_ECDSA: The algorithm value for C{ecdsa-sha2-*} keys.
2071    @cvar ALGORITHM_Ed25519: The algorithm value for C{ed25519} keys.
2072
2073    @cvar FINGERPRINT_TYPE_SHA1: The type for SHA-1 fingerprints.
2074    @cvar FINGERPRINT_TYPE_SHA256: The type for SHA-256 fingerprints.
2075
2076    @see: U{RFC 4255 <https://tools.ietf.org/html/rfc4255>}
2077          and
2078          U{RFC 6594 <https://tools.ietf.org/html/rfc6594>}
2079    """
2080
2081    fancybasename = "SSHFP"
2082    compareAttributes = ("algorithm", "fingerprintType", "fingerprint", "ttl")
2083    showAttributes = ("algorithm", "fingerprintType", "fingerprint")
2084
2085    TYPE = SSHFP
2086
2087    ALGORITHM_RSA = 1
2088    ALGORITHM_DSS = 2
2089    ALGORITHM_ECDSA = 3
2090    ALGORITHM_Ed25519 = 4
2091
2092    FINGERPRINT_TYPE_SHA1 = 1
2093    FINGERPRINT_TYPE_SHA256 = 2
2094
2095    def __init__(self, algorithm=0, fingerprintType=0, fingerprint=b"", ttl=0):
2096        self.algorithm = algorithm
2097        self.fingerprintType = fingerprintType
2098        self.fingerprint = fingerprint
2099        self.ttl = ttl
2100
2101    def encode(self, strio, compDict=None):
2102        strio.write(struct.pack("!BB", self.algorithm, self.fingerprintType))
2103        strio.write(self.fingerprint)
2104
2105    def decode(self, strio, length=None):
2106        r = struct.unpack("!BB", readPrecisely(strio, 2))
2107        (self.algorithm, self.fingerprintType) = r
2108        self.fingerprint = readPrecisely(strio, length - 2)
2109
2110    def __hash__(self):
2111        return hash((self.algorithm, self.fingerprintType, self.fingerprint))
2112
2113
2114@implementer(IEncodableRecord)
2115class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
2116    """
2117    Freeform text.
2118
2119    @type data: L{list} of L{bytes}
2120    @ivar data: Freeform text which makes up this record.
2121
2122    @type ttl: L{int}
2123    @ivar ttl: The maximum number of seconds which this record should be cached.
2124    """
2125
2126    TYPE = TXT
2127
2128    fancybasename = "TXT"
2129    showAttributes = (("data", _nicebyteslist), "ttl")
2130    compareAttributes = ("data", "ttl")
2131
2132    def __init__(self, *data, **kw):
2133        self.data = list(data)
2134        # arg man python sucks so bad
2135        self.ttl = str2time(kw.get("ttl", None))
2136
2137    def encode(self, strio, compDict=None):
2138        for d in self.data:
2139            strio.write(struct.pack("!B", len(d)) + d)
2140
2141    def decode(self, strio, length=None):
2142        soFar = 0
2143        self.data = []
2144        while soFar < length:
2145            L = struct.unpack("!B", readPrecisely(strio, 1))[0]
2146            self.data.append(readPrecisely(strio, L))
2147            soFar += L + 1
2148        if soFar != length:
2149            log.msg(
2150                "Decoded %d bytes in %s record, but rdlength is %d"
2151                % (soFar, self.fancybasename, length)
2152            )
2153
2154    def __hash__(self):
2155        return hash(tuple(self.data))
2156
2157
2158@implementer(IEncodableRecord)
2159class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin):
2160    """
2161    Encapsulate the wire data for unknown record types so that they can
2162    pass through the system unchanged.
2163
2164    @type data: L{bytes}
2165    @ivar data: Wire data which makes up this record.
2166
2167    @type ttl: L{int}
2168    @ivar ttl: The maximum number of seconds which this record should be cached.
2169
2170    @since: 11.1
2171    """
2172
2173    TYPE = None
2174
2175    fancybasename = "UNKNOWN"
2176    compareAttributes = ("data", "ttl")
2177    showAttributes = (("data", _nicebytes), "ttl")
2178
2179    def __init__(self, data=b"", ttl=None):
2180        self.data = data
2181        self.ttl = str2time(ttl)
2182
2183    def encode(self, strio, compDict=None):
2184        """
2185        Write the raw bytes corresponding to this record's payload to the
2186        stream.
2187        """
2188        strio.write(self.data)
2189
2190    def decode(self, strio, length=None):
2191        """
2192        Load the bytes which are part of this record from the stream and store
2193        them unparsed and unmodified.
2194        """
2195        if length is None:
2196            raise Exception("must know length for unknown record types")
2197        self.data = readPrecisely(strio, length)
2198
2199    def __hash__(self):
2200        return hash((self.data, self.ttl))
2201
2202
2203class Record_SPF(Record_TXT):
2204    """
2205    Structurally, freeform text. Semantically, a policy definition, formatted
2206    as defined in U{rfc 4408<http://www.faqs.org/rfcs/rfc4408.html>}.
2207
2208    @type data: L{list} of L{bytes}
2209    @ivar data: Freeform text which makes up this record.
2210
2211    @type ttl: L{int}
2212    @ivar ttl: The maximum number of seconds
2213               which this record should be cached.
2214    """
2215
2216    TYPE = SPF
2217    fancybasename = "SPF"
2218
2219
2220@implementer(IEncodableRecord)
2221class Record_TSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
2222    """
2223    A transaction signature, encapsulated in a RR, as described
2224    in U{RFC 2845 <https://tools.ietf.org/html/rfc2845>}.
2225
2226    @type algorithm: L{Name}
2227    @ivar algorithm: The name of the signature or MAC algorithm.
2228
2229    @type timeSigned: L{int}
2230    @ivar timeSigned: Signing time, as seconds from the POSIX epoch.
2231
2232    @type fudge: L{int}
2233    @ivar fudge: Allowable time skew, in seconds.
2234
2235    @type MAC: L{bytes}
2236    @ivar MAC: The message digest or signature.
2237
2238    @type originalID: L{int}
2239    @ivar originalID: A message ID.
2240
2241    @type error: L{int}
2242    @ivar error: An error code (extended C{RCODE}) carried
2243          in exceptional cases.
2244
2245    @type otherData: L{bytes}
2246    @ivar otherData: Other data carried in exceptional cases.
2247
2248    """
2249
2250    fancybasename = "TSIG"
2251    compareAttributes = (
2252        "algorithm",
2253        "timeSigned",
2254        "fudge",
2255        "MAC",
2256        "originalID",
2257        "error",
2258        "otherData",
2259        "ttl",
2260    )
2261    showAttributes = ["algorithm", "timeSigned", "MAC", "error", "otherData"]
2262
2263    TYPE = TSIG
2264
2265    def __init__(
2266        self,
2267        algorithm=None,
2268        timeSigned=None,
2269        fudge=5,
2270        MAC=None,
2271        originalID=0,
2272        error=OK,
2273        otherData=b"",
2274        ttl=0,
2275    ):
2276        # All of our init arguments have to have defaults, because of
2277        # the way IEncodable and Message.parseRecords() work, but for
2278        # some of our arguments there is no reasonable default; we use
2279        # invalid values here to prevent a user of this class from
2280        # relying on what's really an internal implementation detail.
2281        self.algorithm = None if algorithm is None else Name(algorithm)
2282        self.timeSigned = timeSigned
2283        self.fudge = str2time(fudge)
2284        self.MAC = MAC
2285        self.originalID = originalID
2286        self.error = error
2287        self.otherData = otherData
2288        self.ttl = ttl
2289
2290    def encode(self, strio, compDict=None):
2291        self.algorithm.encode(strio, compDict)
2292        strio.write(struct.pack("!Q", self.timeSigned)[2:])  # 48-bit number
2293        strio.write(struct.pack("!HH", self.fudge, len(self.MAC)))
2294        strio.write(self.MAC)
2295        strio.write(
2296            struct.pack("!HHH", self.originalID, self.error, len(self.otherData))
2297        )
2298        strio.write(self.otherData)
2299
2300    def decode(self, strio, length=None):
2301        algorithm = Name()
2302        algorithm.decode(strio)
2303        self.algorithm = algorithm
2304        fields = struct.unpack("!QHH", b"\x00\x00" + readPrecisely(strio, 10))
2305        self.timeSigned, self.fudge, macLength = fields
2306        self.MAC = readPrecisely(strio, macLength)
2307        fields = struct.unpack("!HHH", readPrecisely(strio, 6))
2308        self.originalID, self.error, otherLength = fields
2309        self.otherData = readPrecisely(strio, otherLength)
2310
2311    def __hash__(self):
2312        return hash((self.algorithm, self.timeSigned, self.MAC, self.originalID))
2313
2314
2315def _responseFromMessage(responseConstructor, message, **kwargs):
2316    """
2317    Generate a L{Message} like instance suitable for use as the response to
2318    C{message}.
2319
2320    The C{queries}, C{id} attributes will be copied from C{message} and the
2321    C{answer} flag will be set to L{True}.
2322
2323    @param responseConstructor: A response message constructor with an
2324         initializer signature matching L{dns.Message.__init__}.
2325    @type responseConstructor: C{callable}
2326
2327    @param message: A request message.
2328    @type message: L{Message}
2329
2330    @param kwargs: Keyword arguments which will be passed to the initialiser
2331        of the response message.
2332    @type kwargs: L{dict}
2333
2334    @return: A L{Message} like response instance.
2335    @rtype: C{responseConstructor}
2336    """
2337    response = responseConstructor(id=message.id, answer=True, **kwargs)
2338    response.queries = message.queries[:]
2339    return response
2340
2341
2342def _getDisplayableArguments(obj, alwaysShow, fieldNames):
2343    """
2344    Inspect the function signature of C{obj}'s constructor,
2345    and get a list of which arguments should be displayed.
2346    This is a helper function for C{_compactRepr}.
2347
2348    @param obj: The instance whose repr is being generated.
2349    @param alwaysShow: A L{list} of field names which should always be shown.
2350    @param fieldNames: A L{list} of field attribute names which should be shown
2351        if they have non-default values.
2352    @return: A L{list} of displayable arguments.
2353    """
2354    displayableArgs = []
2355    # Get the argument names and values from the constructor.
2356    signature = inspect.signature(obj.__class__.__init__)
2357    for name in fieldNames:
2358        defaultValue = signature.parameters[name].default
2359        fieldValue = getattr(obj, name, defaultValue)
2360        if (name in alwaysShow) or (fieldValue != defaultValue):
2361            displayableArgs.append(f" {name}={fieldValue!r}")
2362
2363    return displayableArgs
2364
2365
2366def _compactRepr(
2367    obj, alwaysShow=None, flagNames=None, fieldNames=None, sectionNames=None
2368):
2369    """
2370    Return a L{str} representation of C{obj} which only shows fields with
2371    non-default values, flags which are True and sections which have been
2372    explicitly set.
2373
2374    @param obj: The instance whose repr is being generated.
2375    @param alwaysShow: A L{list} of field names which should always be shown.
2376    @param flagNames: A L{list} of flag attribute names which should be shown if
2377        they are L{True}.
2378    @param fieldNames: A L{list} of field attribute names which should be shown
2379        if they have non-default values.
2380    @param sectionNames: A L{list} of section attribute names which should be
2381        shown if they have been assigned a value.
2382
2383    @return: A L{str} representation of C{obj}.
2384    """
2385    if alwaysShow is None:
2386        alwaysShow = []
2387
2388    if flagNames is None:
2389        flagNames = []
2390
2391    if fieldNames is None:
2392        fieldNames = []
2393
2394    if sectionNames is None:
2395        sectionNames = []
2396
2397    setFlags = []
2398    for name in flagNames:
2399        if name in alwaysShow or getattr(obj, name, False) == True:
2400            setFlags.append(name)
2401
2402    displayableArgs = _getDisplayableArguments(obj, alwaysShow, fieldNames)
2403    out = ["<", obj.__class__.__name__] + displayableArgs
2404
2405    if setFlags:
2406        out.append(" flags={}".format(",".join(setFlags)))
2407
2408    for name in sectionNames:
2409        section = getattr(obj, name, [])
2410        if section:
2411            out.append(f" {name}={section!r}")
2412
2413    out.append(">")
2414
2415    return "".join(out)
2416
2417
2418class Message(tputil.FancyEqMixin):
2419    """
2420    L{Message} contains all the information represented by a single
2421    DNS request or response.
2422
2423    @ivar id: See L{__init__}
2424    @ivar answer: See L{__init__}
2425    @ivar opCode: See L{__init__}
2426    @ivar recDes: See L{__init__}
2427    @ivar recAv: See L{__init__}
2428    @ivar auth: See L{__init__}
2429    @ivar rCode: See L{__init__}
2430    @ivar trunc: See L{__init__}
2431    @ivar maxSize: See L{__init__}
2432    @ivar authenticData: See L{__init__}
2433    @ivar checkingDisabled: See L{__init__}
2434
2435    @ivar queries: The queries which are being asked of or answered by
2436        DNS server.
2437    @type queries: L{list} of L{Query}
2438
2439    @ivar answers: Records containing the answers to C{queries} if
2440        this is a response message.
2441    @type answers: L{list} of L{RRHeader}
2442
2443    @ivar authority: Records containing information about the
2444        authoritative DNS servers for the names in C{queries}.
2445    @type authority: L{list} of L{RRHeader}
2446
2447    @ivar additional: Records containing IP addresses of host names
2448        in C{answers} and C{authority}.
2449    @type additional: L{list} of L{RRHeader}
2450
2451    @ivar _flagNames: The names of attributes representing the flag header
2452        fields.
2453    @ivar _fieldNames: The names of attributes representing non-flag fixed
2454        header fields.
2455    @ivar _sectionNames: The names of attributes representing the record
2456        sections of this message.
2457    """
2458
2459    compareAttributes = (
2460        "id",
2461        "answer",
2462        "opCode",
2463        "recDes",
2464        "recAv",
2465        "auth",
2466        "rCode",
2467        "trunc",
2468        "maxSize",
2469        "authenticData",
2470        "checkingDisabled",
2471        "queries",
2472        "answers",
2473        "authority",
2474        "additional",
2475    )
2476
2477    headerFmt = "!H2B4H"
2478    headerSize = struct.calcsize(headerFmt)
2479
2480    # Question, answer, additional, and nameserver lists
2481    queries = answers = add = ns = None
2482
2483    def __init__(
2484        self,
2485        id=0,
2486        answer=0,
2487        opCode=0,
2488        recDes=0,
2489        recAv=0,
2490        auth=0,
2491        rCode=OK,
2492        trunc=0,
2493        maxSize=512,
2494        authenticData=0,
2495        checkingDisabled=0,
2496    ):
2497        """
2498        @param id: A 16 bit identifier assigned by the program that
2499            generates any kind of query.  This identifier is copied to
2500            the corresponding reply and can be used by the requester
2501            to match up replies to outstanding queries.
2502        @type id: L{int}
2503
2504        @param answer: A one bit field that specifies whether this
2505            message is a query (0), or a response (1).
2506        @type answer: L{int}
2507
2508        @param opCode: A four bit field that specifies kind of query in
2509            this message.  This value is set by the originator of a query
2510            and copied into the response.
2511        @type opCode: L{int}
2512
2513        @param recDes: Recursion Desired - this bit may be set in a
2514            query and is copied into the response.  If RD is set, it
2515            directs the name server to pursue the query recursively.
2516            Recursive query support is optional.
2517        @type recDes: L{int}
2518
2519        @param recAv: Recursion Available - this bit is set or cleared
2520            in a response and denotes whether recursive query support
2521            is available in the name server.
2522        @type recAv: L{int}
2523
2524        @param auth: Authoritative Answer - this bit is valid in
2525            responses and specifies that the responding name server
2526            is an authority for the domain name in question section.
2527        @type auth: L{int}
2528
2529        @ivar rCode: A response code, used to indicate success or failure in a
2530            message which is a response from a server to a client request.
2531        @type rCode: C{0 <= int < 16}
2532
2533        @param trunc: A flag indicating that this message was
2534            truncated due to length greater than that permitted on the
2535            transmission channel.
2536        @type trunc: L{int}
2537
2538        @param maxSize: The requestor's UDP payload size is the number
2539            of octets of the largest UDP payload that can be
2540            reassembled and delivered in the requestor's network
2541            stack.
2542        @type maxSize: L{int}
2543
2544        @param authenticData: A flag indicating in a response that all
2545            the data included in the answer and authority portion of
2546            the response has been authenticated by the server
2547            according to the policies of that server.
2548            See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
2549        @type authenticData: L{int}
2550
2551        @param checkingDisabled: A flag indicating in a query that
2552            pending (non-authenticated) data is acceptable to the
2553            resolver sending the query.
2554            See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
2555        @type authenticData: L{int}
2556        """
2557        self.maxSize = maxSize
2558        self.id = id
2559        self.answer = answer
2560        self.opCode = opCode
2561        self.auth = auth
2562        self.trunc = trunc
2563        self.recDes = recDes
2564        self.recAv = recAv
2565        self.rCode = rCode
2566        self.authenticData = authenticData
2567        self.checkingDisabled = checkingDisabled
2568
2569        self.queries = []
2570        self.answers = []
2571        self.authority = []
2572        self.additional = []
2573
2574    def __repr__(self) -> str:
2575        """
2576        Generate a repr of this L{Message}.
2577
2578        Only includes the non-default fields and sections and only includes
2579        flags which are set. The C{id} is always shown.
2580
2581        @return: The native string repr.
2582        """
2583        return _compactRepr(
2584            self,
2585            flagNames=(
2586                "answer",
2587                "auth",
2588                "trunc",
2589                "recDes",
2590                "recAv",
2591                "authenticData",
2592                "checkingDisabled",
2593            ),
2594            fieldNames=("id", "opCode", "rCode", "maxSize"),
2595            sectionNames=("queries", "answers", "authority", "additional"),
2596            alwaysShow=("id",),
2597        )
2598
2599    def addQuery(self, name, type=ALL_RECORDS, cls=IN):
2600        """
2601        Add another query to this Message.
2602
2603        @type name: L{bytes}
2604        @param name: The name to query.
2605
2606        @type type: L{int}
2607        @param type: Query type
2608
2609        @type cls: L{int}
2610        @param cls: Query class
2611        """
2612        self.queries.append(Query(name, type, cls))
2613
2614    def encode(self, strio):
2615        compDict = {}
2616        body_tmp = BytesIO()
2617        for q in self.queries:
2618            q.encode(body_tmp, compDict)
2619        for q in self.answers:
2620            q.encode(body_tmp, compDict)
2621        for q in self.authority:
2622            q.encode(body_tmp, compDict)
2623        for q in self.additional:
2624            q.encode(body_tmp, compDict)
2625        body = body_tmp.getvalue()
2626        size = len(body) + self.headerSize
2627        if self.maxSize and size > self.maxSize:
2628            self.trunc = 1
2629            body = body[: self.maxSize - self.headerSize]
2630        byte3 = (
2631            ((self.answer & 1) << 7)
2632            | ((self.opCode & 0xF) << 3)
2633            | ((self.auth & 1) << 2)
2634            | ((self.trunc & 1) << 1)
2635            | (self.recDes & 1)
2636        )
2637        byte4 = (
2638            ((self.recAv & 1) << 7)
2639            | ((self.authenticData & 1) << 5)
2640            | ((self.checkingDisabled & 1) << 4)
2641            | (self.rCode & 0xF)
2642        )
2643
2644        strio.write(
2645            struct.pack(
2646                self.headerFmt,
2647                self.id,
2648                byte3,
2649                byte4,
2650                len(self.queries),
2651                len(self.answers),
2652                len(self.authority),
2653                len(self.additional),
2654            )
2655        )
2656        strio.write(body)
2657
2658    def decode(self, strio, length=None):
2659        self.maxSize = 0
2660        header = readPrecisely(strio, self.headerSize)
2661        r = struct.unpack(self.headerFmt, header)
2662        self.id, byte3, byte4, nqueries, nans, nns, nadd = r
2663        self.answer = (byte3 >> 7) & 1
2664        self.opCode = (byte3 >> 3) & 0xF
2665        self.auth = (byte3 >> 2) & 1
2666        self.trunc = (byte3 >> 1) & 1
2667        self.recDes = byte3 & 1
2668        self.recAv = (byte4 >> 7) & 1
2669        self.authenticData = (byte4 >> 5) & 1
2670        self.checkingDisabled = (byte4 >> 4) & 1
2671        self.rCode = byte4 & 0xF
2672
2673        self.queries = []
2674        for i in range(nqueries):
2675            q = Query()
2676            try:
2677                q.decode(strio)
2678            except EOFError:
2679                return
2680            self.queries.append(q)
2681
2682        items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
2683
2684        for (l, n) in items:
2685            self.parseRecords(l, n, strio)
2686
2687    def parseRecords(self, list, num, strio):
2688        for i in range(num):
2689            header = RRHeader(auth=self.auth)
2690            try:
2691                header.decode(strio)
2692            except EOFError:
2693                return
2694            t = self.lookupRecordType(header.type)
2695            if not t:
2696                continue
2697            header.payload = t(ttl=header.ttl)
2698            try:
2699                header.payload.decode(strio, header.rdlength)
2700            except EOFError:
2701                return
2702            list.append(header)
2703
2704    # Create a mapping from record types to their corresponding Record_*
2705    # classes.  This relies on the global state which has been created so
2706    # far in initializing this module (so don't define Record classes after
2707    # this).
2708    _recordTypes = {}
2709    for name in globals():
2710        if name.startswith("Record_"):
2711            _recordTypes[globals()[name].TYPE] = globals()[name]
2712
2713    # Clear the iteration variable out of the class namespace so it
2714    # doesn't become an attribute.
2715    del name
2716
2717    def lookupRecordType(self, type):
2718        """
2719        Retrieve the L{IRecord} implementation for the given record type.
2720
2721        @param type: A record type, such as C{A} or L{NS}.
2722        @type type: L{int}
2723
2724        @return: An object which implements L{IRecord} or L{None} if none
2725            can be found for the given type.
2726        @rtype: C{Type[IRecord]}
2727        """
2728        return self._recordTypes.get(type, UnknownRecord)
2729
2730    def toStr(self):
2731        """
2732        Encode this L{Message} into a byte string in the format described by RFC
2733        1035.
2734
2735        @rtype: L{bytes}
2736        """
2737        strio = BytesIO()
2738        self.encode(strio)
2739        return strio.getvalue()
2740
2741    def fromStr(self, str):
2742        """
2743        Decode a byte string in the format described by RFC 1035 into this
2744        L{Message}.
2745
2746        @param str: L{bytes}
2747        """
2748        strio = BytesIO(str)
2749        self.decode(strio)
2750
2751
2752class _EDNSMessage(tputil.FancyEqMixin):
2753    """
2754    An I{EDNS} message.
2755
2756    Designed for compatibility with L{Message} but with a narrower public
2757    interface.
2758
2759    Most importantly, L{_EDNSMessage.fromStr} will interpret and remove I{OPT}
2760    records that are present in the additional records section.
2761
2762    The I{OPT} records are used to populate certain I{EDNS} specific attributes.
2763
2764    L{_EDNSMessage.toStr} will add suitable I{OPT} records to the additional
2765    section to represent the extended EDNS information.
2766
2767    @see: U{https://tools.ietf.org/html/rfc6891}
2768
2769    @ivar id: See L{__init__}
2770    @ivar answer: See L{__init__}
2771    @ivar opCode: See L{__init__}
2772    @ivar auth: See L{__init__}
2773    @ivar trunc: See L{__init__}
2774    @ivar recDes: See L{__init__}
2775    @ivar recAv: See L{__init__}
2776    @ivar rCode: See L{__init__}
2777    @ivar ednsVersion: See L{__init__}
2778    @ivar dnssecOK: See L{__init__}
2779    @ivar authenticData: See L{__init__}
2780    @ivar checkingDisabled: See L{__init__}
2781    @ivar maxSize: See L{__init__}
2782
2783    @ivar queries: See L{__init__}
2784    @ivar answers: See L{__init__}
2785    @ivar authority: See L{__init__}
2786    @ivar additional: See L{__init__}
2787
2788    @ivar _messageFactory: A constructor of L{Message} instances. Called by
2789        C{_toMessage} and C{_fromMessage}.
2790    """
2791
2792    compareAttributes = (
2793        "id",
2794        "answer",
2795        "opCode",
2796        "auth",
2797        "trunc",
2798        "recDes",
2799        "recAv",
2800        "rCode",
2801        "ednsVersion",
2802        "dnssecOK",
2803        "authenticData",
2804        "checkingDisabled",
2805        "maxSize",
2806        "queries",
2807        "answers",
2808        "authority",
2809        "additional",
2810    )
2811
2812    _messageFactory = Message
2813
2814    def __init__(
2815        self,
2816        id=0,
2817        answer=False,
2818        opCode=OP_QUERY,
2819        auth=False,
2820        trunc=False,
2821        recDes=False,
2822        recAv=False,
2823        rCode=0,
2824        ednsVersion=0,
2825        dnssecOK=False,
2826        authenticData=False,
2827        checkingDisabled=False,
2828        maxSize=512,
2829        queries=None,
2830        answers=None,
2831        authority=None,
2832        additional=None,
2833    ):
2834        """
2835        Construct a new L{_EDNSMessage}
2836
2837        @see: U{RFC1035 section-4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
2838        @see: U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}
2839        @see: U{RFC3225 section-3<https://tools.ietf.org/html/rfc3225#section-3>}
2840        @see: U{RFC6891 section-6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}
2841
2842        @param id: A 16 bit identifier assigned by the program that generates
2843            any kind of query.  This identifier is copied the corresponding
2844            reply and can be used by the requester to match up replies to
2845            outstanding queries.
2846        @type id: L{int}
2847
2848        @param answer: A one bit field that specifies whether this message is a
2849            query (0), or a response (1).
2850        @type answer: L{bool}
2851
2852        @param opCode: A four bit field that specifies kind of query in this
2853            message.  This value is set by the originator of a query and copied
2854            into the response.
2855        @type opCode: L{int}
2856
2857        @param auth: Authoritative Answer - this bit is valid in responses, and
2858            specifies that the responding name server is an authority for the
2859            domain name in question section.
2860        @type auth: L{bool}
2861
2862        @param trunc: Truncation - specifies that this message was truncated due
2863            to length greater than that permitted on the transmission channel.
2864        @type trunc: L{bool}
2865
2866        @param recDes: Recursion Desired - this bit may be set in a query and is
2867            copied into the response.  If set, it directs the name server to
2868            pursue the query recursively. Recursive query support is optional.
2869        @type recDes: L{bool}
2870
2871        @param recAv: Recursion Available - this bit is set or cleared in a
2872            response, and denotes whether recursive query support is available
2873            in the name server.
2874        @type recAv: L{bool}
2875
2876        @param rCode: Extended 12-bit RCODE. Derived from the 4 bits defined in
2877            U{RFC1035 4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
2878            and the upper 8bits defined in U{RFC6891
2879            6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}.
2880        @type rCode: L{int}
2881
2882        @param ednsVersion: Indicates the EDNS implementation level. Set to
2883            L{None} to prevent any EDNS attributes and options being added to
2884            the encoded byte string.
2885        @type ednsVersion: L{int} or L{None}
2886
2887        @param dnssecOK: DNSSEC OK bit as defined by
2888            U{RFC3225 3<https://tools.ietf.org/html/rfc3225#section-3>}.
2889        @type dnssecOK: L{bool}
2890
2891        @param authenticData: A flag indicating in a response that all the data
2892            included in the answer and authority portion of the response has
2893            been authenticated by the server according to the policies of that
2894            server.
2895            See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
2896        @type authenticData: L{bool}
2897
2898        @param checkingDisabled: A flag indicating in a query that pending
2899            (non-authenticated) data is acceptable to the resolver sending the
2900            query.
2901            See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
2902        @type authenticData: L{bool}
2903
2904        @param maxSize: The requestor's UDP payload size is the number of octets
2905            of the largest UDP payload that can be reassembled and delivered in
2906            the requestor's network stack.
2907        @type maxSize: L{int}
2908
2909        @param queries: The L{list} of L{Query} associated with this message.
2910        @type queries: L{list} of L{Query}
2911
2912        @param answers: The L{list} of answers associated with this message.
2913        @type answers: L{list} of L{RRHeader}
2914
2915        @param authority: The L{list} of authority records associated with this
2916            message.
2917        @type authority: L{list} of L{RRHeader}
2918
2919        @param additional: The L{list} of additional records associated with
2920            this message.
2921        @type additional: L{list} of L{RRHeader}
2922        """
2923        self.id = id
2924        self.answer = answer
2925        self.opCode = opCode
2926        self.auth = auth
2927        self.trunc = trunc
2928        self.recDes = recDes
2929        self.recAv = recAv
2930        self.rCode = rCode
2931        self.ednsVersion = ednsVersion
2932        self.dnssecOK = dnssecOK
2933        self.authenticData = authenticData
2934        self.checkingDisabled = checkingDisabled
2935        self.maxSize = maxSize
2936
2937        if queries is None:
2938            queries = []
2939        self.queries = queries
2940
2941        if answers is None:
2942            answers = []
2943        self.answers = answers
2944
2945        if authority is None:
2946            authority = []
2947        self.authority = authority
2948
2949        if additional is None:
2950            additional = []
2951        self.additional = additional
2952
2953    def __repr__(self) -> str:
2954        return _compactRepr(
2955            self,
2956            flagNames=(
2957                "answer",
2958                "auth",
2959                "trunc",
2960                "recDes",
2961                "recAv",
2962                "authenticData",
2963                "checkingDisabled",
2964                "dnssecOK",
2965            ),
2966            fieldNames=("id", "opCode", "rCode", "maxSize", "ednsVersion"),
2967            sectionNames=("queries", "answers", "authority", "additional"),
2968            alwaysShow=("id",),
2969        )
2970
2971    def _toMessage(self):
2972        """
2973        Convert to a standard L{dns.Message}.
2974
2975        If C{ednsVersion} is not None, an L{_OPTHeader} instance containing all
2976        the I{EDNS} specific attributes and options will be appended to the list
2977        of C{additional} records.
2978
2979        @return: A L{dns.Message}
2980        @rtype: L{dns.Message}
2981        """
2982        m = self._messageFactory(
2983            id=self.id,
2984            answer=self.answer,
2985            opCode=self.opCode,
2986            auth=self.auth,
2987            trunc=self.trunc,
2988            recDes=self.recDes,
2989            recAv=self.recAv,
2990            # Assign the lower 4 bits to the message
2991            rCode=self.rCode & 0xF,
2992            authenticData=self.authenticData,
2993            checkingDisabled=self.checkingDisabled,
2994        )
2995
2996        m.queries = self.queries[:]
2997        m.answers = self.answers[:]
2998        m.authority = self.authority[:]
2999        m.additional = self.additional[:]
3000
3001        if self.ednsVersion is not None:
3002            o = _OPTHeader(
3003                version=self.ednsVersion,
3004                dnssecOK=self.dnssecOK,
3005                udpPayloadSize=self.maxSize,
3006                # Assign the upper 8 bits to the OPT record
3007                extendedRCODE=self.rCode >> 4,
3008            )
3009            m.additional.append(o)
3010
3011        return m
3012
3013    def toStr(self):
3014        """
3015        Encode to wire format by first converting to a standard L{dns.Message}.
3016
3017        @return: A L{bytes} string.
3018        """
3019        return self._toMessage().toStr()
3020
3021    @classmethod
3022    def _fromMessage(cls, message):
3023        """
3024        Construct and return a new L{_EDNSMessage} whose attributes and records
3025        are derived from the attributes and records of C{message} (a L{Message}
3026        instance).
3027
3028        If present, an C{OPT} record will be extracted from the C{additional}
3029        section and its attributes and options will be used to set the EDNS
3030        specific attributes C{extendedRCODE}, C{ednsVersion}, C{dnssecOK},
3031        C{ednsOptions}.
3032
3033        The C{extendedRCODE} will be combined with C{message.rCode} and assigned
3034        to C{self.rCode}.
3035
3036        @param message: The source L{Message}.
3037        @type message: L{Message}
3038
3039        @return: A new L{_EDNSMessage}
3040        @rtype: L{_EDNSMessage}
3041        """
3042        additional = []
3043        optRecords = []
3044        for r in message.additional:
3045            if r.type == OPT:
3046                optRecords.append(_OPTHeader.fromRRHeader(r))
3047            else:
3048                additional.append(r)
3049
3050        newMessage = cls(
3051            id=message.id,
3052            answer=message.answer,
3053            opCode=message.opCode,
3054            auth=message.auth,
3055            trunc=message.trunc,
3056            recDes=message.recDes,
3057            recAv=message.recAv,
3058            rCode=message.rCode,
3059            authenticData=message.authenticData,
3060            checkingDisabled=message.checkingDisabled,
3061            # Default to None, it will be updated later when the OPT records are
3062            # parsed.
3063            ednsVersion=None,
3064            dnssecOK=False,
3065            queries=message.queries[:],
3066            answers=message.answers[:],
3067            authority=message.authority[:],
3068            additional=additional,
3069        )
3070
3071        if len(optRecords) == 1:
3072            # XXX: If multiple OPT records are received, an EDNS server should
3073            # respond with FORMERR. See ticket:5669#comment:1.
3074            opt = optRecords[0]
3075            newMessage.ednsVersion = opt.version
3076            newMessage.dnssecOK = opt.dnssecOK
3077            newMessage.maxSize = opt.udpPayloadSize
3078            newMessage.rCode = opt.extendedRCODE << 4 | message.rCode
3079
3080        return newMessage
3081
3082    def fromStr(self, bytes):
3083        """
3084        Decode from wire format, saving flags, values and records to this
3085        L{_EDNSMessage} instance in place.
3086
3087        @param bytes: The full byte string to be decoded.
3088        @type bytes: L{bytes}
3089        """
3090        m = self._messageFactory()
3091        m.fromStr(bytes)
3092
3093        ednsMessage = self._fromMessage(m)
3094        for attrName in self.compareAttributes:
3095            setattr(self, attrName, getattr(ednsMessage, attrName))
3096
3097
3098class DNSMixin:
3099    """
3100    DNS protocol mixin shared by UDP and TCP implementations.
3101
3102    @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
3103        be used to issue DNS queries and manage request timeouts.
3104    """
3105
3106    id = None
3107    liveMessages = None
3108
3109    def __init__(self, controller, reactor=None):
3110        self.controller = controller
3111        self.id = random.randrange(2 ** 10, 2 ** 15)
3112        if reactor is None:
3113            from twisted.internet import reactor
3114        self._reactor = reactor
3115
3116    def pickID(self):
3117        """
3118        Return a unique ID for queries.
3119        """
3120        while True:
3121            id = randomSource()
3122            if id not in self.liveMessages:
3123                return id
3124
3125    def callLater(self, period, func, *args):
3126        """
3127        Wrapper around reactor.callLater, mainly for test purpose.
3128        """
3129        return self._reactor.callLater(period, func, *args)
3130
3131    def _query(self, queries, timeout, id, writeMessage):
3132        """
3133        Send out a message with the given queries.
3134
3135        @type queries: L{list} of C{Query} instances
3136        @param queries: The queries to transmit
3137
3138        @type timeout: L{int} or C{float}
3139        @param timeout: How long to wait before giving up
3140
3141        @type id: L{int}
3142        @param id: Unique key for this request
3143
3144        @type writeMessage: C{callable}
3145        @param writeMessage: One-parameter callback which writes the message
3146
3147        @rtype: C{Deferred}
3148        @return: a C{Deferred} which will be fired with the result of the
3149            query, or errbacked with any errors that could happen (exceptions
3150            during writing of the query, timeout errors, ...).
3151        """
3152        m = Message(id, recDes=1)
3153        m.queries = queries
3154
3155        try:
3156            writeMessage(m)
3157        except BaseException:
3158            return defer.fail()
3159
3160        resultDeferred = defer.Deferred()
3161        cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
3162        self.liveMessages[id] = (resultDeferred, cancelCall)
3163
3164        return resultDeferred
3165
3166    def _clearFailed(self, deferred, id):
3167        """
3168        Clean the Deferred after a timeout.
3169        """
3170        try:
3171            del self.liveMessages[id]
3172        except KeyError:
3173            pass
3174        deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
3175
3176
3177class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
3178    """
3179    DNS protocol over UDP.
3180    """
3181
3182    resends = None
3183
3184    def stopProtocol(self):
3185        """
3186        Stop protocol: reset state variables.
3187        """
3188        self.liveMessages = {}
3189        self.resends = {}
3190        self.transport = None
3191
3192    def startProtocol(self):
3193        """
3194        Upon start, reset internal state.
3195        """
3196        self.liveMessages = {}
3197        self.resends = {}
3198
3199    def writeMessage(self, message, address):
3200        """
3201        Send a message holding DNS queries.
3202
3203        @type message: L{Message}
3204        """
3205        self.transport.write(message.toStr(), address)
3206
3207    def startListening(self):
3208        self._reactor.listenUDP(0, self, maxPacketSize=512)
3209
3210    def datagramReceived(self, data, addr):
3211        """
3212        Read a datagram, extract the message in it and trigger the associated
3213        Deferred.
3214        """
3215        m = Message()
3216        try:
3217            m.fromStr(data)
3218        except EOFError:
3219            log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
3220            return
3221        except BaseException:
3222            # Nothing should trigger this, but since we're potentially
3223            # invoking a lot of different decoding methods, we might as well
3224            # be extra cautious.  Anything that triggers this is itself
3225            # buggy.
3226            log.err(failure.Failure(), "Unexpected decoding error")
3227            return
3228
3229        if m.id in self.liveMessages:
3230            d, canceller = self.liveMessages[m.id]
3231            del self.liveMessages[m.id]
3232            canceller.cancel()
3233            # XXX we shouldn't need this hack of catching exception on callback()
3234            try:
3235                d.callback(m)
3236            except BaseException:
3237                log.err()
3238        else:
3239            if m.id not in self.resends:
3240                self.controller.messageReceived(m, self, addr)
3241
3242    def removeResend(self, id):
3243        """
3244        Mark message ID as no longer having duplication suppression.
3245        """
3246        try:
3247            del self.resends[id]
3248        except KeyError:
3249            pass
3250
3251    def query(self, address, queries, timeout=10, id=None):
3252        """
3253        Send out a message with the given queries.
3254
3255        @type address: L{tuple} of L{str} and L{int}
3256        @param address: The address to which to send the query
3257
3258        @type queries: L{list} of C{Query} instances
3259        @param queries: The queries to transmit
3260
3261        @rtype: C{Deferred}
3262        """
3263        if not self.transport:
3264            # XXX transport might not get created automatically, use callLater?
3265            try:
3266                self.startListening()
3267            except CannotListenError:
3268                return defer.fail()
3269
3270        if id is None:
3271            id = self.pickID()
3272        else:
3273            self.resends[id] = 1
3274
3275        def writeMessage(m):
3276            self.writeMessage(m, address)
3277
3278        return self._query(queries, timeout, id, writeMessage)
3279
3280
3281class DNSProtocol(DNSMixin, protocol.Protocol):
3282    """
3283    DNS protocol over TCP.
3284    """
3285
3286    length = None
3287    buffer = b""
3288
3289    def writeMessage(self, message):
3290        """
3291        Send a message holding DNS queries.
3292
3293        @type message: L{Message}
3294        """
3295        s = message.toStr()
3296        self.transport.write(struct.pack("!H", len(s)) + s)
3297
3298    def connectionMade(self):
3299        """
3300        Connection is made: reset internal state, and notify the controller.
3301        """
3302        self.liveMessages = {}
3303        self.controller.connectionMade(self)
3304
3305    def connectionLost(self, reason):
3306        """
3307        Notify the controller that this protocol is no longer
3308        connected.
3309        """
3310        self.controller.connectionLost(self)
3311
3312    def dataReceived(self, data):
3313        self.buffer += data
3314
3315        while self.buffer:
3316            if self.length is None and len(self.buffer) >= 2:
3317                self.length = struct.unpack("!H", self.buffer[:2])[0]
3318                self.buffer = self.buffer[2:]
3319
3320            if len(self.buffer) >= self.length:
3321                myChunk = self.buffer[: self.length]
3322                m = Message()
3323                m.fromStr(myChunk)
3324
3325                try:
3326                    d, canceller = self.liveMessages[m.id]
3327                except KeyError:
3328                    self.controller.messageReceived(m, self)
3329                else:
3330                    del self.liveMessages[m.id]
3331                    canceller.cancel()
3332                    # XXX we shouldn't need this hack
3333                    try:
3334                        d.callback(m)
3335                    except BaseException:
3336                        log.err()
3337
3338                self.buffer = self.buffer[self.length :]
3339                self.length = None
3340            else:
3341                break
3342
3343    def query(self, queries, timeout=60):
3344        """
3345        Send out a message with the given queries.
3346
3347        @type queries: L{list} of C{Query} instances
3348        @param queries: The queries to transmit
3349
3350        @rtype: C{Deferred}
3351        """
3352        id = self.pickID()
3353        return self._query(queries, timeout, id, self.writeMessage)
3354