aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/names/dns.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/names/dns.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/names/dns.py')
-rw-r--r--contrib/python/Twisted/py2/twisted/names/dns.py3214
1 files changed, 3214 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/names/dns.py b/contrib/python/Twisted/py2/twisted/names/dns.py
new file mode 100644
index 0000000000..89509a644e
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/names/dns.py
@@ -0,0 +1,3214 @@
+# -*- test-case-name: twisted.names.test.test_dns -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+DNS protocol implementation.
+
+Future Plans:
+ - Get rid of some toplevels, maybe.
+"""
+
+from __future__ import division, absolute_import
+
+__all__ = [
+ 'IEncodable', 'IRecord',
+
+ 'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'HINFO',
+ 'MAILA', 'MAILB', 'MB', 'MD', 'MF', 'MG', 'MINFO', 'MR', 'MX',
+ 'NAPTR', 'NS', 'NULL', 'OPT', 'PTR', 'RP', 'SOA', 'SPF', 'SRV', 'TXT',
+ 'SSHFP', 'TSIG', 'WKS',
+
+ 'ANY', 'CH', 'CS', 'HS', 'IN',
+
+ 'ALL_RECORDS', 'AXFR', 'IXFR',
+
+ 'EFORMAT', 'ENAME', 'ENOTIMP', 'EREFUSED', 'ESERVER', 'EBADVERSION',
+ 'EBADSIG', 'EBADKEY', 'EBADTIME',
+
+ 'Record_A', 'Record_A6', 'Record_AAAA', 'Record_AFSDB', 'Record_CNAME',
+ 'Record_DNAME', 'Record_HINFO', 'Record_MB', 'Record_MD', 'Record_MF',
+ 'Record_MG', 'Record_MINFO', 'Record_MR', 'Record_MX', 'Record_NAPTR',
+ 'Record_NS', 'Record_NULL', 'Record_PTR', 'Record_RP', 'Record_SOA',
+ 'Record_SPF', 'Record_SRV', 'Record_SSHFP', 'Record_TSIG', 'Record_TXT',
+ 'Record_WKS',
+ 'UnknownRecord',
+
+ 'QUERY_CLASSES', 'QUERY_TYPES', 'REV_CLASSES', 'REV_TYPES', 'EXT_QUERIES',
+
+ 'Charstr', 'Message', 'Name', 'Query', 'RRHeader', 'SimpleRecord',
+ 'DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol',
+
+ 'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE',
+ 'PORT',
+
+ 'AuthoritativeDomainError', 'DNSQueryTimeoutError', 'DomainError',
+ ]
+
+
+# System imports
+import inspect, struct, random, socket
+from itertools import chain
+
+from io import BytesIO
+
+AF_INET6 = socket.AF_INET6
+
+from zope.interface import implementer, Interface, Attribute
+
+
+# Twisted imports
+from twisted.internet import protocol, defer
+from twisted.internet.error import CannotListenError
+from twisted.python import log, failure
+from twisted.python import util as tputil
+from twisted.python import randbytes
+from twisted.python.compat import _PY3, unicode, comparable, cmp, nativeString
+
+
+if _PY3:
+ def _ord2bytes(ordinal):
+ """
+ Construct a bytes object representing a single byte with the given
+ ordinal value.
+
+ @type ordinal: L{int}
+ @rtype: L{bytes}
+ """
+ return bytes([ordinal])
+
+
+ def _nicebytes(bytes):
+ """
+ Represent a mostly textful bytes object in a way suitable for
+ presentation to an end user.
+
+ @param bytes: The bytes to represent.
+ @rtype: L{str}
+ """
+ return repr(bytes)[1:]
+
+
+ def _nicebyteslist(list):
+ """
+ Represent a list of mostly textful bytes objects in a way suitable for
+ presentation to an end user.
+
+ @param list: The list of bytes to represent.
+ @rtype: L{str}
+ """
+ return '[%s]' % (
+ ', '.join([_nicebytes(b) for b in list]),)
+else:
+ _ord2bytes = chr
+ _nicebytes = _nicebyteslist = repr
+
+
+
+def randomSource():
+ """
+ Wrapper around L{twisted.python.randbytes.RandomFactory.secureRandom} to
+ return 2 random bytes.
+
+ @rtype: L{bytes}
+ """
+ return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
+
+
+PORT = 53
+
+(A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
+ RP, AFSDB) = range(1, 19)
+AAAA = 28
+SRV = 33
+NAPTR = 35
+A6 = 38
+DNAME = 39
+OPT = 41
+SSHFP = 44
+SPF = 99
+
+# These record types do not exist in zones, but are transferred in
+# messages the same way normal RRs are.
+TKEY = 249
+TSIG = 250
+
+QUERY_TYPES = {
+ A: 'A',
+ NS: 'NS',
+ MD: 'MD',
+ MF: 'MF',
+ CNAME: 'CNAME',
+ SOA: 'SOA',
+ MB: 'MB',
+ MG: 'MG',
+ MR: 'MR',
+ NULL: 'NULL',
+ WKS: 'WKS',
+ PTR: 'PTR',
+ HINFO: 'HINFO',
+ MINFO: 'MINFO',
+ MX: 'MX',
+ TXT: 'TXT',
+ RP: 'RP',
+ AFSDB: 'AFSDB',
+
+ # 19 through 27? Eh, I'll get to 'em.
+
+ AAAA: 'AAAA',
+ SRV: 'SRV',
+ NAPTR: 'NAPTR',
+ A6: 'A6',
+ DNAME: 'DNAME',
+ OPT: 'OPT',
+ SSHFP: 'SSHFP',
+ SPF: 'SPF',
+
+ TKEY: 'TKEY',
+ TSIG: 'TSIG',
+}
+
+IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
+
+# "Extended" queries (Hey, half of these are deprecated, good job)
+EXT_QUERIES = {
+ IXFR: 'IXFR',
+ AXFR: 'AXFR',
+ MAILB: 'MAILB',
+ MAILA: 'MAILA',
+ ALL_RECORDS: 'ALL_RECORDS'
+}
+
+REV_TYPES = dict([
+ (v, k) for (k, v) in chain(QUERY_TYPES.items(), EXT_QUERIES.items())
+])
+
+IN, CS, CH, HS = range(1, 5)
+ANY = 255
+
+QUERY_CLASSES = {
+ IN: 'IN',
+ CS: 'CS',
+ CH: 'CH',
+ HS: 'HS',
+ ANY: 'ANY'
+}
+REV_CLASSES = dict([
+ (v, k) for (k, v) in QUERY_CLASSES.items()
+])
+
+
+# Opcodes
+OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
+OP_NOTIFY = 4 # RFC 1996
+OP_UPDATE = 5 # RFC 2136
+
+
+# Response Codes
+OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
+# https://tools.ietf.org/html/rfc6891#section-9
+EBADVERSION = 16
+# RFC 2845
+EBADSIG, EBADKEY, EBADTIME = range(16, 19)
+
+
+
+class IRecord(Interface):
+ """
+ A single entry in a zone of authority.
+ """
+
+ TYPE = Attribute("An indicator of what kind of record this is.")
+
+
+# Backwards compatibility aliases - these should be deprecated or something I
+# suppose. -exarkun
+from twisted.names.error import DomainError, AuthoritativeDomainError
+from twisted.names.error import DNSQueryTimeoutError
+
+
+
+def _nameToLabels(name):
+ """
+ Split a domain name into its constituent labels.
+
+ @type name: L{bytes}
+ @param name: A fully qualified domain name (with or without a
+ trailing dot).
+
+ @return: A L{list} of labels ending with an empty label
+ representing the DNS root zone.
+ @rtype: L{list} of L{bytes}
+ """
+ if name in (b'', b'.'):
+ return [b'']
+ labels = name.split(b'.')
+ if labels[-1] != b'':
+ labels.append(b'')
+ return labels
+
+
+
+def domainString(domain):
+ """
+ Coerce a domain name string to bytes.
+
+ L{twisted.names} represents domain names as L{bytes}, but many interfaces
+ accept L{bytes} or a text string (L{unicode} on Python 2, L{str} on Python
+ 3). This function coerces text strings using IDNA encoding --- see
+ L{encodings.idna}.
+
+ Note that DNS is I{case insensitive} but I{case preserving}. This function
+ doesn't normalize case, so you'll still need to do that whenever comparing
+ the strings it returns.
+
+ @param domain: A domain name. If passed as a text string it will be
+ C{idna} encoded.
+ @type domain: L{bytes} or L{str}
+
+ @returns: L{bytes} suitable for network transmission.
+ @rtype: L{bytes}
+
+ @since: Twisted 20.3.0
+ """
+ if isinstance(domain, unicode):
+ domain = domain.encode('idna')
+ if not isinstance(domain, bytes):
+ raise TypeError('Expected {} or {} but found {!r} of type {}'.format(
+ type(b'').__name__, type(u'').__name__,
+ domain, type(domain)))
+ return domain
+
+
+
+def _isSubdomainOf(descendantName, ancestorName):
+ """
+ Test whether C{descendantName} is equal to or is a I{subdomain} of
+ C{ancestorName}.
+
+ The names are compared case-insensitively.
+
+ The names are treated as byte strings containing one or more
+ DNS labels separated by B{.}.
+
+ C{descendantName} is considered equal if its sequence of labels
+ exactly matches the labels of C{ancestorName}.
+
+ C{descendantName} is considered a I{subdomain} if its sequence of
+ labels ends with the labels of C{ancestorName}.
+
+ @type descendantName: L{bytes}
+ @param descendantName: The DNS subdomain name.
+
+ @type ancestorName: L{bytes}
+ @param ancestorName: The DNS parent or ancestor domain name.
+
+ @return: C{True} if C{descendantName} is equal to or if it is a
+ subdomain of C{ancestorName}. Otherwise returns C{False}.
+ """
+ descendantLabels = _nameToLabels(descendantName.lower())
+ ancestorLabels = _nameToLabels(ancestorName.lower())
+ return descendantLabels[-len(ancestorLabels):] == ancestorLabels
+
+
+
+def str2time(s):
+ """
+ Parse a string description of an interval into an integer number of seconds.
+
+ @param s: An interval definition constructed as an interval duration
+ followed by an interval unit. An interval duration is a base ten
+ representation of an integer. An interval unit is one of the following
+ letters: S (seconds), M (minutes), H (hours), D (days), W (weeks), or Y
+ (years). For example: C{"3S"} indicates an interval of three seconds;
+ C{"5D"} indicates an interval of five days. Alternatively, C{s} may be
+ any non-string and it will be returned unmodified.
+ @type s: text string (L{bytes} or L{unicode}) for parsing; anything else
+ for passthrough.
+
+ @return: an L{int} giving the interval represented by the string C{s}, or
+ whatever C{s} is if it is not a string.
+ """
+ suffixes = (
+ ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
+ ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
+ )
+ if _PY3 and isinstance(s, bytes):
+ s = s.decode('ascii')
+
+ if isinstance(s, str):
+ s = s.upper().strip()
+ for (suff, mult) in suffixes:
+ if s.endswith(suff):
+ return int(float(s[:-1]) * mult)
+ try:
+ s = int(s)
+ except ValueError:
+ raise ValueError("Invalid time interval specifier: " + s)
+ return s
+
+
+
+def readPrecisely(file, l):
+ buff = file.read(l)
+ if len(buff) < l:
+ raise EOFError
+ return buff
+
+
+
+class IEncodable(Interface):
+ """
+ Interface for something which can be encoded to and decoded
+ to the DNS wire format.
+
+ A binary-mode file object (such as L{io.BytesIO}) is used as a buffer when
+ encoding or decoding.
+ """
+
+ def encode(strio, compDict=None):
+ """
+ Write a representation of this object to the given
+ file object.
+
+ @type strio: File-like object
+ @param strio: The buffer to write to. It must have a C{tell()} method.
+
+ @type compDict: L{dict} of L{bytes} to L{int} r L{None}
+ @param compDict: A mapping of names to byte offsets that have already
+ been written to the buffer, which may be used for compression (see RFC
+ 1035 section 4.1.4). When L{None}, encode without compression.
+ """
+
+
+ def decode(strio, length=None):
+ """
+ Reconstruct an object from data read from the given
+ file object.
+
+ @type strio: File-like object
+ @param strio: A seekable buffer from which bytes may be read.
+
+ @type length: L{int} or L{None}
+ @param length: The number of bytes in this RDATA field. Most
+ implementations can ignore this value. Only in the case of
+ records similar to TXT where the total length is in no way
+ encoded in the data is it necessary.
+ """
+
+
+
+@implementer(IEncodable)
+class Charstr(object):
+
+ def __init__(self, string=b''):
+ if not isinstance(string, bytes):
+ raise ValueError("%r is not a byte string" % (string,))
+ self.string = string
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this Character string into the appropriate byte format.
+
+ @type strio: file
+ @param strio: The byte representation of this Charstr will be written
+ to this file.
+ """
+ string = self.string
+ ind = len(string)
+ strio.write(_ord2bytes(ind))
+ strio.write(string)
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode a byte string into this Charstr.
+
+ @type strio: file
+ @param strio: Bytes will be read from this file until the full string
+ is decoded.
+
+ @raise EOFError: Raised when there are not enough bytes available from
+ C{strio}.
+ """
+ self.string = b''
+ l = ord(readPrecisely(strio, 1))
+ self.string = readPrecisely(strio, l)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Charstr):
+ return self.string == other.string
+ return NotImplemented
+
+
+ def __ne__(self, other):
+ if isinstance(other, Charstr):
+ return self.string != other.string
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash(self.string)
+
+
+ def __str__(self):
+ """
+ Represent this L{Charstr} instance by its string value.
+ """
+ return nativeString(self.string)
+
+
+
+@implementer(IEncodable)
+class Name:
+ """
+ A name in the domain name system, made up of multiple labels. For example,
+ I{twistedmatrix.com}.
+
+ @ivar name: A byte string giving the name.
+ @type name: L{bytes}
+ """
+ def __init__(self, name=b''):
+ """
+ @param name: A name.
+ @type name: L{bytes} or L{str}
+ """
+ self.name = domainString(name)
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this Name into the appropriate byte format.
+
+ @type strio: file
+ @param strio: The byte representation of this Name will be written to
+ this file.
+
+ @type compDict: dict
+ @param compDict: dictionary of Names that have already been encoded
+ and whose addresses may be backreferenced by this Name (for the purpose
+ of reducing the message size).
+ """
+ name = self.name
+ while name:
+ if compDict is not None:
+ if name in compDict:
+ strio.write(
+ struct.pack("!H", 0xc000 | compDict[name]))
+ return
+ else:
+ compDict[name] = strio.tell() + Message.headerSize
+ ind = name.find(b'.')
+ if ind > 0:
+ label, name = name[:ind], name[ind + 1:]
+ else:
+ # This is the last label, end the loop after handling it.
+ label = name
+ name = None
+ ind = len(label)
+ strio.write(_ord2bytes(ind))
+ strio.write(label)
+ strio.write(b'\x00')
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode a byte string into this Name.
+
+ @type strio: file
+ @param strio: Bytes will be read from this file until the full Name
+ is decoded.
+
+ @raise EOFError: Raised when there are not enough bytes available
+ from C{strio}.
+
+ @raise ValueError: Raised when the name cannot be decoded (for example,
+ because it contains a loop).
+ """
+ visited = set()
+ self.name = b''
+ off = 0
+ while 1:
+ l = ord(readPrecisely(strio, 1))
+ if l == 0:
+ if off > 0:
+ strio.seek(off)
+ return
+ if (l >> 6) == 3:
+ new_off = ((l&63) << 8
+ | ord(readPrecisely(strio, 1)))
+ if new_off in visited:
+ raise ValueError("Compression loop in encoded name")
+ visited.add(new_off)
+ if off == 0:
+ off = strio.tell()
+ strio.seek(new_off)
+ continue
+ label = readPrecisely(strio, l)
+ if self.name == b'':
+ self.name = label
+ else:
+ self.name = self.name + b'.' + label
+
+ def __eq__(self, other):
+ if isinstance(other, Name):
+ return self.name.lower() == other.name.lower()
+ return NotImplemented
+
+
+ def __ne__(self, other):
+ if isinstance(other, Name):
+ return self.name.lower() != other.name.lower()
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash(self.name)
+
+
+ def __str__(self):
+ """
+ Represent this L{Name} instance by its string name.
+ """
+ return nativeString(self.name)
+
+
+
+@comparable
+@implementer(IEncodable)
+class Query:
+ """
+ Represent a single DNS query.
+
+ @ivar name: The name about which this query is requesting information.
+ @type name: L{Name}
+
+ @ivar type: The query type.
+ @type type: L{int}
+
+ @ivar cls: The query class.
+ @type cls: L{int}
+ """
+ name = None
+ type = None
+ cls = None
+
+ def __init__(self, name=b'', type=A, cls=IN):
+ """
+ @type name: L{bytes} or L{unicode}
+ @param name: See L{Query.name}
+
+ @type type: L{int}
+ @param type: The query type.
+
+ @type cls: L{int}
+ @param cls: The query class.
+ """
+ self.name = Name(name)
+ self.type = type
+ self.cls = cls
+
+
+ def encode(self, strio, compDict=None):
+ self.name.encode(strio, compDict)
+ strio.write(struct.pack("!HH", self.type, self.cls))
+
+
+ def decode(self, strio, length = None):
+ self.name.decode(strio)
+ buff = readPrecisely(strio, 4)
+ self.type, self.cls = struct.unpack("!HH", buff)
+
+
+ def __hash__(self):
+ return hash((self.name.name.lower(), self.type, self.cls))
+
+
+ def __cmp__(self, other):
+ if isinstance(other, Query):
+ return cmp(
+ (self.name.name.lower(), self.type, self.cls),
+ (other.name.name.lower(), other.type, other.cls))
+ return NotImplemented
+
+
+ def __str__(self):
+ t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
+ c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
+ return '<Query %s %s %s>' % (self.name, t, c)
+
+
+ def __repr__(self):
+ return 'Query(%r, %r, %r)' % (self.name.name, self.type, self.cls)
+
+
+
+@implementer(IEncodable)
+class _OPTHeader(tputil.FancyStrMixin, tputil.FancyEqMixin, object):
+ """
+ An OPT record header.
+
+ @ivar name: The DNS name associated with this record. Since this
+ is a pseudo record, the name is always an L{Name} instance
+ with value b'', which represents the DNS root zone. This
+ attribute is a readonly property.
+
+ @ivar type: The DNS record type. This is a fixed value of 41
+ C{dns.OPT} for OPT Record. This attribute is a readonly
+ property.
+
+ @see: L{_OPTHeader.__init__} for documentation of other public
+ instance attributes.
+
+ @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
+
+ @since: 13.2
+ """
+ showAttributes = (
+ ('name', lambda n: nativeString(n.name)), 'type', 'udpPayloadSize',
+ 'extendedRCODE', 'version', 'dnssecOK', 'options')
+
+ compareAttributes = (
+ 'name', 'type', 'udpPayloadSize', 'extendedRCODE', 'version',
+ 'dnssecOK', 'options')
+
+ def __init__(self, udpPayloadSize=4096, extendedRCODE=0, version=0,
+ dnssecOK=False, options=None):
+ """
+ @type udpPayloadSize: L{int}
+ @param payload: The number of octets of the largest UDP
+ payload that can be reassembled and delivered in the
+ requestor's network stack.
+
+ @type extendedRCODE: L{int}
+ @param extendedRCODE: Forms the upper 8 bits of extended
+ 12-bit RCODE (together with the 4 bits defined in
+ [RFC1035]. Note that EXTENDED-RCODE value 0 indicates
+ that an unextended RCODE is in use (values 0 through 15).
+
+ @type version: L{int}
+ @param version: Indicates the implementation level of the
+ setter. Full conformance with this specification is
+ indicated by version C{0}.
+
+ @type dnssecOK: L{bool}
+ @param dnssecOK: DNSSEC OK bit as defined by [RFC3225].
+
+ @type options: L{list}
+ @param options: A L{list} of 0 or more L{_OPTVariableOption}
+ instances.
+ """
+ self.udpPayloadSize = udpPayloadSize
+ self.extendedRCODE = extendedRCODE
+ self.version = version
+ self.dnssecOK = dnssecOK
+
+ if options is None:
+ options = []
+ self.options = options
+
+
+ @property
+ def name(self):
+ """
+ A readonly property for accessing the C{name} attribute of
+ this record.
+
+ @return: The DNS name associated with this record. Since this
+ is a pseudo record, the name is always an L{Name} instance
+ with value b'', which represents the DNS root zone.
+ """
+ return Name(b'')
+
+
+ @property
+ def type(self):
+ """
+ A readonly property for accessing the C{type} attribute of
+ this record.
+
+ @return: The DNS record type. This is a fixed value of 41
+ (C{dns.OPT} for OPT Record.
+ """
+ return OPT
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this L{_OPTHeader} instance to bytes.
+
+ @type strio: L{file}
+ @param strio: the byte representation of this L{_OPTHeader}
+ will be written to this file.
+
+ @type compDict: L{dict} or L{None}
+ @param compDict: A dictionary of backreference addresses that
+ have already been written to this stream and that may
+ be used for DNS name compression.
+ """
+ b = BytesIO()
+ for o in self.options:
+ o.encode(b)
+ optionBytes = b.getvalue()
+
+ RRHeader(
+ name=self.name.name,
+ type=self.type,
+ cls=self.udpPayloadSize,
+ ttl=(
+ self.extendedRCODE << 24
+ | self.version << 16
+ | self.dnssecOK << 15),
+ payload=UnknownRecord(optionBytes)
+ ).encode(strio, compDict)
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode bytes into an L{_OPTHeader} instance.
+
+ @type strio: L{file}
+ @param strio: Bytes will be read from this file until the full
+ L{_OPTHeader} is decoded.
+
+ @type length: L{int} or L{None}
+ @param length: Not used.
+ """
+
+ h = RRHeader()
+ h.decode(strio, length)
+ h.payload = UnknownRecord(readPrecisely(strio, h.rdlength))
+
+ newOptHeader = self.fromRRHeader(h)
+
+ for attrName in self.compareAttributes:
+ if attrName not in ('name', 'type'):
+ setattr(self, attrName, getattr(newOptHeader, attrName))
+
+
+ @classmethod
+ def fromRRHeader(cls, rrHeader):
+ """
+ A classmethod for constructing a new L{_OPTHeader} from the
+ attributes and payload of an existing L{RRHeader} instance.
+
+ @type rrHeader: L{RRHeader}
+ @param rrHeader: An L{RRHeader} instance containing an
+ L{UnknownRecord} payload.
+
+ @return: An instance of L{_OPTHeader}.
+ @rtype: L{_OPTHeader}
+ """
+ options = None
+ if rrHeader.payload is not None:
+ options = []
+ optionsBytes = BytesIO(rrHeader.payload.data)
+ optionsBytesLength = len(rrHeader.payload.data)
+ while optionsBytes.tell() < optionsBytesLength:
+ o = _OPTVariableOption()
+ o.decode(optionsBytes)
+ options.append(o)
+
+ # Decode variable options if present
+ return cls(
+ udpPayloadSize=rrHeader.cls,
+ extendedRCODE=rrHeader.ttl >> 24,
+ version=rrHeader.ttl >> 16 & 0xff,
+ dnssecOK=(rrHeader.ttl & 0xffff) >> 15,
+ options=options
+ )
+
+
+
+@implementer(IEncodable)
+class _OPTVariableOption(tputil.FancyStrMixin, tputil.FancyEqMixin, object):
+ """
+ A class to represent OPT record variable options.
+
+ @see: L{_OPTVariableOption.__init__} for documentation of public
+ instance attributes.
+
+ @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
+
+ @since: 13.2
+ """
+ showAttributes = ('code', ('data', nativeString))
+ compareAttributes = ('code', 'data')
+
+ _fmt = '!HH'
+
+ def __init__(self, code=0, data=b''):
+ """
+ @type code: L{int}
+ @param code: The option code
+
+ @type data: L{bytes}
+ @param data: The option data
+ """
+ self.code = code
+ self.data = data
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this L{_OPTVariableOption} to bytes.
+
+ @type strio: L{file}
+ @param strio: the byte representation of this
+ L{_OPTVariableOption} will be written to this file.
+
+ @type compDict: L{dict} or L{None}
+ @param compDict: A dictionary of backreference addresses that
+ have already been written to this stream and that may
+ be used for DNS name compression.
+ """
+ strio.write(
+ struct.pack(self._fmt, self.code, len(self.data)) + self.data)
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode bytes into an L{_OPTVariableOption} instance.
+
+ @type strio: L{file}
+ @param strio: Bytes will be read from this file until the full
+ L{_OPTVariableOption} is decoded.
+
+ @type length: L{int} or L{None}
+ @param length: Not used.
+ """
+ l = struct.calcsize(self._fmt)
+ buff = readPrecisely(strio, l)
+ self.code, length = struct.unpack(self._fmt, buff)
+ self.data = readPrecisely(strio, length)
+
+
+
+@implementer(IEncodable)
+class RRHeader(tputil.FancyEqMixin):
+ """
+ A resource record header.
+
+ @cvar fmt: L{str} specifying the byte format of an RR.
+
+ @ivar name: The name about which this reply contains information.
+ @type name: L{Name}
+
+ @ivar type: The query type of the original request.
+ @type type: L{int}
+
+ @ivar cls: The query class of the original request.
+
+ @ivar ttl: The time-to-live for this record.
+ @type ttl: L{int}
+
+ @ivar payload: An object that implements the L{IEncodable} interface
+
+ @ivar auth: A L{bool} indicating whether this C{RRHeader} was parsed from
+ an authoritative message.
+ """
+ compareAttributes = ('name', 'type', 'cls', 'ttl', 'payload', 'auth')
+
+ fmt = "!HHIH"
+
+ name = None
+ type = None
+ cls = None
+ ttl = None
+ payload = None
+ rdlength = None
+
+ cachedResponse = None
+
+ def __init__(self, name=b'', type=A, cls=IN, ttl=0, payload=None,
+ auth=False):
+ """
+ @type name: L{bytes} or L{str}
+ @param name: See L{RRHeader.name}
+
+ @type type: L{int}
+ @param type: The query type.
+
+ @type cls: L{int}
+ @param cls: The query class.
+
+ @type ttl: L{int}
+ @param ttl: Time to live for this record. This will be
+ converted to an L{int}.
+
+ @type payload: An object implementing C{IEncodable}
+ @param payload: A Query Type specific data object.
+
+ @raises TypeError: if the ttl cannot be converted to an L{int}.
+ @raises ValueError: if the ttl is negative.
+ """
+ assert (payload is None) or isinstance(payload, UnknownRecord) or (payload.TYPE == type)
+
+ integralTTL = int(ttl)
+
+ if integralTTL < 0:
+ raise ValueError("TTL cannot be negative")
+
+ self.name = Name(name)
+ self.type = type
+ self.cls = cls
+ self.ttl = integralTTL
+ self.payload = payload
+ self.auth = auth
+
+
+ def encode(self, strio, compDict=None):
+ self.name.encode(strio, compDict)
+ strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
+ if self.payload:
+ prefix = strio.tell()
+ self.payload.encode(strio, compDict)
+ aft = strio.tell()
+ strio.seek(prefix - 2, 0)
+ strio.write(struct.pack('!H', aft - prefix))
+ strio.seek(aft, 0)
+
+
+ def decode(self, strio, length = None):
+ self.name.decode(strio)
+ l = struct.calcsize(self.fmt)
+ buff = readPrecisely(strio, l)
+ r = struct.unpack(self.fmt, buff)
+ self.type, self.cls, self.ttl, self.rdlength = r
+
+
+ def isAuthoritative(self):
+ return self.auth
+
+
+ def __str__(self):
+ t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
+ c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
+ return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
+
+
+ __repr__ = __str__
+
+
+
+@implementer(IEncodable, IRecord)
+class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ A Resource Record which consists of a single RFC 1035 domain-name.
+
+ @type name: L{Name}
+ @ivar name: The name associated with this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ showAttributes = (('name', 'name', '%s'), 'ttl')
+ compareAttributes = ('name', 'ttl')
+
+ TYPE = None
+ name = None
+
+ def __init__(self, name=b'', ttl=None):
+ """
+ @param name: See L{SimpleRecord.name}
+ @type name: L{bytes} or L{str}
+ """
+ self.name = Name(name)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.name.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.name = Name()
+ self.name.decode(strio)
+
+
+ def __hash__(self):
+ return hash(self.name)
+
+
+# Kinds of RRs - oh my!
+class Record_NS(SimpleRecord):
+ """
+ An authoritative nameserver.
+ """
+ TYPE = NS
+ fancybasename = 'NS'
+
+
+
+class Record_MD(SimpleRecord):
+ """
+ A mail destination.
+
+ This record type is obsolete.
+
+ @see: L{Record_MX}
+ """
+ TYPE = MD
+ fancybasename = 'MD'
+
+
+
+class Record_MF(SimpleRecord):
+ """
+ A mail forwarder.
+
+ This record type is obsolete.
+
+ @see: L{Record_MX}
+ """
+ TYPE = MF
+ fancybasename = 'MF'
+
+
+
+class Record_CNAME(SimpleRecord):
+ """
+ The canonical name for an alias.
+ """
+ TYPE = CNAME
+ fancybasename = 'CNAME'
+
+
+
+class Record_MB(SimpleRecord):
+ """
+ A mailbox domain name.
+
+ This is an experimental record type.
+ """
+ TYPE = MB
+ fancybasename = 'MB'
+
+
+
+class Record_MG(SimpleRecord):
+ """
+ A mail group member.
+
+ This is an experimental record type.
+ """
+ TYPE = MG
+ fancybasename = 'MG'
+
+
+
+class Record_MR(SimpleRecord):
+ """
+ A mail rename domain name.
+
+ This is an experimental record type.
+ """
+ TYPE = MR
+ fancybasename = 'MR'
+
+
+
+class Record_PTR(SimpleRecord):
+ """
+ A domain name pointer.
+ """
+ TYPE = PTR
+ fancybasename = 'PTR'
+
+
+
+class Record_DNAME(SimpleRecord):
+ """
+ A non-terminal DNS name redirection.
+
+ This record type provides the capability to map an entire subtree of the
+ DNS name space to another domain. It differs from the CNAME record which
+ maps a single node of the name space.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2672.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3363.html}
+ """
+ TYPE = DNAME
+ fancybasename = 'DNAME'
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_A(tputil.FancyEqMixin):
+ """
+ An IPv4 host address.
+
+ @type address: L{bytes}
+ @ivar address: The packed network-order representation of the IPv4 address
+ associated with this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ compareAttributes = ('address', 'ttl')
+
+ TYPE = A
+ address = None
+
+ def __init__(self, address='0.0.0.0', ttl=None):
+ """
+ @type address: L{bytes} or L{unicode}
+ @param address: The IPv4 address associated with this record, in
+ quad-dotted notation.
+ """
+ if _PY3 and isinstance(address, bytes):
+ address = address.decode('ascii')
+
+ address = socket.inet_aton(address)
+ self.address = address
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+
+
+ def __hash__(self):
+ return hash(self.address)
+
+
+ def __str__(self):
+ return '<A address=%s ttl=%s>' % (self.dottedQuad(), self.ttl)
+ __repr__ = __str__
+
+
+ def dottedQuad(self):
+ return socket.inet_ntoa(self.address)
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Marks the start of a zone of authority.
+
+ This record describes parameters which are shared by all records within a
+ particular zone.
+
+ @type mname: L{Name}
+ @ivar mname: The domain-name of the name server that was the original or
+ primary source of data for this zone.
+
+ @type rname: L{Name}
+ @ivar rname: A domain-name which specifies the mailbox of the person
+ responsible for this zone.
+
+ @type serial: L{int}
+ @ivar serial: The unsigned 32 bit version number of the original copy of
+ the zone. Zone transfers preserve this value. This value wraps and
+ should be compared using sequence space arithmetic.
+
+ @type refresh: L{int}
+ @ivar refresh: A 32 bit time interval before the zone should be refreshed.
+
+ @type minimum: L{int}
+ @ivar minimum: The unsigned 32 bit minimum TTL field that should be
+ exported with any RR from this zone.
+
+ @type expire: L{int}
+ @ivar expire: A 32 bit time value that specifies the upper limit on the
+ time interval that can elapse before the zone is no longer
+ authoritative.
+
+ @type retry: L{int}
+ @ivar retry: A 32 bit time interval that should elapse before a failed
+ refresh should be retried.
+
+ @type ttl: L{int}
+ @ivar ttl: The default TTL to use for records served from this zone.
+ """
+ fancybasename = 'SOA'
+ compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'minimum', 'ttl')
+ showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
+
+ TYPE = SOA
+
+ def __init__(self, mname=b'', rname=b'', serial=0, refresh=0, retry=0,
+ expire=0, minimum=0, ttl=None):
+ """
+ @param mname: See L{Record_SOA.mname}
+ @type mname: L{bytes} or L{unicode}
+
+ @param rname: See L{Record_SOA.rname}
+ @type rname: L{bytes} or L{unicode}
+ """
+ self.mname, self.rname = Name(mname), Name(rname)
+ self.serial, self.refresh = str2time(serial), str2time(refresh)
+ self.minimum, self.expire = str2time(minimum), str2time(expire)
+ self.retry = str2time(retry)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.mname.encode(strio, compDict)
+ self.rname.encode(strio, compDict)
+ strio.write(
+ struct.pack(
+ '!LlllL',
+ self.serial, self.refresh, self.retry, self.expire,
+ self.minimum
+ )
+ )
+
+
+ def decode(self, strio, length = None):
+ self.mname, self.rname = Name(), Name()
+ self.mname.decode(strio)
+ self.rname.decode(strio)
+ r = struct.unpack('!LlllL', readPrecisely(strio, 20))
+ self.serial, self.refresh, self.retry, self.expire, self.minimum = r
+
+
+ def __hash__(self):
+ return hash((
+ self.serial, self.mname, self.rname,
+ self.refresh, self.expire, self.retry
+ ))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ A null record.
+
+ This is an experimental record type.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ fancybasename = 'NULL'
+ showAttributes = (('payload', _nicebytes), 'ttl')
+ compareAttributes = ('payload', 'ttl')
+
+ TYPE = NULL
+
+ def __init__(self, payload=None, ttl=None):
+ self.payload = payload
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.payload)
+
+
+ def decode(self, strio, length = None):
+ self.payload = readPrecisely(strio, length)
+
+
+ def __hash__(self):
+ return hash(self.payload)
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ A well known service description.
+
+ This record type is obsolete. See L{Record_SRV}.
+
+ @type address: L{bytes}
+ @ivar address: The packed network-order representation of the IPv4 address
+ associated with this record.
+
+ @type protocol: L{int}
+ @ivar protocol: The 8 bit IP protocol number for which this service map is
+ relevant.
+
+ @type map: L{bytes}
+ @ivar map: A bitvector indicating the services available at the specified
+ address.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ fancybasename = "WKS"
+ compareAttributes = ('address', 'protocol', 'map', 'ttl')
+ showAttributes = [('_address', 'address', '%s'), 'protocol', 'ttl']
+
+ TYPE = WKS
+
+ _address = property(lambda self: socket.inet_ntoa(self.address))
+
+ def __init__(self, address='0.0.0.0', protocol=0, map=b'', ttl=None):
+ """
+ @type address: L{bytes} or L{unicode}
+ @param address: The IPv4 address associated with this record, in
+ quad-dotted notation.
+ """
+ if _PY3 and isinstance(address, bytes):
+ address = address.decode('idna')
+
+ self.address = socket.inet_aton(address)
+ self.protocol, self.map = protocol, map
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+ strio.write(struct.pack('!B', self.protocol))
+ strio.write(self.map)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+ self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.map = readPrecisely(strio, length - 5)
+
+
+ def __hash__(self):
+ return hash((self.address, self.protocol, self.map))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ An IPv6 host address.
+
+ @type address: L{bytes}
+ @ivar address: The packed network-order representation of the IPv6 address
+ associated with this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1886.html}
+ """
+ TYPE = AAAA
+
+ fancybasename = 'AAAA'
+ showAttributes = (('_address', 'address', '%s'), 'ttl')
+ compareAttributes = ('address', 'ttl')
+
+ _address = property(lambda self: socket.inet_ntop(AF_INET6, self.address))
+
+ def __init__(self, address='::', ttl=None):
+ """
+ @type address: L{bytes} or L{unicode}
+ @param address: The IPv6 address for this host, in RFC 2373 format.
+ """
+ if _PY3 and isinstance(address, bytes):
+ address = address.decode('idna')
+
+ self.address = socket.inet_pton(AF_INET6, address)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 16)
+
+
+ def __hash__(self):
+ return hash(self.address)
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ An IPv6 address.
+
+ This is an experimental record type.
+
+ @type prefixLen: L{int}
+ @ivar prefixLen: The length of the suffix.
+
+ @type suffix: L{bytes}
+ @ivar suffix: An IPv6 address suffix in network order.
+
+ @type prefix: L{Name}
+ @ivar prefix: If specified, a name which will be used as a prefix for other
+ A6 records.
+
+ @type bytes: L{int}
+ @ivar bytes: The length of the prefix.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2874.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3363.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3364.html}
+ """
+ TYPE = A6
+
+ fancybasename = 'A6'
+ showAttributes = (('_suffix', 'suffix', '%s'), ('prefix', 'prefix', '%s'), 'ttl')
+ compareAttributes = ('prefixLen', 'prefix', 'suffix', 'ttl')
+
+ _suffix = property(lambda self: socket.inet_ntop(AF_INET6, self.suffix))
+
+ def __init__(self, prefixLen=0, suffix='::', prefix=b'', ttl=None):
+ """
+ @param suffix: An IPv6 address suffix in in RFC 2373 format.
+ @type suffix: L{bytes} or L{unicode}
+
+ @param prefix: An IPv6 address prefix for other A6 records.
+ @type prefix: L{bytes} or L{unicode}
+ """
+ if _PY3 and isinstance(suffix, bytes):
+ suffix = suffix.decode('idna')
+
+ self.prefixLen = prefixLen
+ self.suffix = socket.inet_pton(AF_INET6, suffix)
+ self.prefix = Name(prefix)
+ self.bytes = int((128 - self.prefixLen) / 8.0)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!B', self.prefixLen))
+ if self.bytes:
+ strio.write(self.suffix[-self.bytes:])
+ if self.prefixLen:
+ # This may not be compressed
+ self.prefix.encode(strio, None)
+
+
+ def decode(self, strio, length = None):
+ self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.bytes = int((128 - self.prefixLen) / 8.0)
+ if self.bytes:
+ self.suffix = b'\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
+ if self.prefixLen:
+ self.prefix.decode(strio)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Record_A6):
+ return (self.prefixLen == other.prefixLen and
+ self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
+ self.prefix == other.prefix and
+ self.ttl == other.ttl)
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
+
+
+ def __str__(self):
+ return '<A6 %s %s (%d) ttl=%s>' % (
+ self.prefix,
+ socket.inet_ntop(AF_INET6, self.suffix),
+ self.prefixLen, self.ttl
+ )
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The location of the server(s) for a specific protocol and domain.
+
+ This is an experimental record type.
+
+ @type priority: L{int}
+ @ivar priority: The priority of this target host. A client MUST attempt to
+ contact the target host with the lowest-numbered priority it can reach;
+ target hosts with the same priority SHOULD be tried in an order defined
+ by the weight field.
+
+ @type weight: L{int}
+ @ivar weight: Specifies a relative weight for entries with the same
+ priority. Larger weights SHOULD be given a proportionately higher
+ probability of being selected.
+
+ @type port: L{int}
+ @ivar port: The port on this target host of this service.
+
+ @type target: L{Name}
+ @ivar target: The domain name of the target host. There MUST be one or
+ more address records for this name, the name MUST NOT be an alias (in
+ the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
+ required, to return the address record(s) in the Additional Data
+ section. Unless and until permitted by future standards action, name
+ compression is not to be used for this field.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2782.html}
+ """
+ TYPE = SRV
+
+ fancybasename = 'SRV'
+ compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
+ showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
+
+ def __init__(self, priority=0, weight=0, port=0, target=b'', ttl=None):
+ """
+ @param target: See L{Record_SRV.target}
+ @type target: L{bytes} or L{unicode}
+ """
+ self.priority = int(priority)
+ self.weight = int(weight)
+ self.port = int(port)
+ self.target = Name(target)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
+ # This can't be compressed
+ self.target.encode(strio, None)
+
+
+ def decode(self, strio, length = None):
+ r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
+ self.priority, self.weight, self.port = r
+ self.target = Name()
+ self.target.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.priority, self.weight, self.port, self.target))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The location of the server(s) for a specific protocol and domain.
+
+ @type order: L{int}
+ @ivar order: An integer specifying the order in which the NAPTR records
+ MUST be processed to ensure the correct ordering of rules. Low numbers
+ are processed before high numbers.
+
+ @type preference: L{int}
+ @ivar preference: An integer that specifies the order in which NAPTR
+ records with equal "order" values SHOULD be processed, low numbers
+ being processed before high numbers.
+
+ @type flag: L{Charstr}
+ @ivar flag: A <character-string> containing flags to control aspects of the
+ rewriting and interpretation of the fields in the record. Flags
+ are single characters from the set [A-Z0-9]. The case of the alphabetic
+ characters is not significant.
+
+ At this time only four flags, "S", "A", "U", and "P", are defined.
+
+ @type service: L{Charstr}
+ @ivar service: Specifies the service(s) available down this rewrite path.
+ It may also specify the particular protocol that is used to talk with a
+ service. A protocol MUST be specified if the flags field states that
+ the NAPTR is terminal.
+
+ @type regexp: L{Charstr}
+ @ivar regexp: A STRING containing a substitution expression that is applied
+ to the original string held by the client in order to construct the
+ next domain name to lookup.
+
+ @type replacement: L{Name}
+ @ivar replacement: The next NAME to query for NAPTR, SRV, or address
+ records depending on the value of the flags field. This MUST be a
+ fully qualified domain-name.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2915.html}
+ """
+ TYPE = NAPTR
+
+ compareAttributes = ('order', 'preference', 'flags', 'service', 'regexp',
+ 'replacement')
+ fancybasename = 'NAPTR'
+
+ showAttributes = ('order', 'preference', ('flags', 'flags', '%s'),
+ ('service', 'service', '%s'), ('regexp', 'regexp', '%s'),
+ ('replacement', 'replacement', '%s'), 'ttl')
+
+ def __init__(self, order=0, preference=0, flags=b'', service=b'',
+ regexp=b'', replacement=b'', ttl=None):
+ """
+ @param replacement: See L{Record_NAPTR.replacement}
+ @type replacement: L{bytes} or L{unicode}
+ """
+ self.order = int(order)
+ self.preference = int(preference)
+ self.flags = Charstr(flags)
+ self.service = Charstr(service)
+ self.regexp = Charstr(regexp)
+ self.replacement = Name(replacement)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict=None):
+ strio.write(struct.pack('!HH', self.order, self.preference))
+ # This can't be compressed
+ self.flags.encode(strio, None)
+ self.service.encode(strio, None)
+ self.regexp.encode(strio, None)
+ self.replacement.encode(strio, None)
+
+
+ def decode(self, strio, length=None):
+ r = struct.unpack('!HH', readPrecisely(strio, struct.calcsize('!HH')))
+ self.order, self.preference = r
+ self.flags = Charstr()
+ self.service = Charstr()
+ self.regexp = Charstr()
+ self.replacement = Name()
+ self.flags.decode(strio)
+ self.service.decode(strio)
+ self.regexp.decode(strio)
+ self.replacement.decode(strio)
+
+
+ def __hash__(self):
+ return hash((
+ self.order, self.preference, self.flags,
+ self.service, self.regexp, self.replacement))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Map from a domain name to the name of an AFS cell database server.
+
+ @type subtype: L{int}
+ @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
+ Volume Location Server for the named AFS cell. In the case of subtype
+ 2, the host has an authenticated name server holding the cell-root
+ directory node for the named DCE/NCA cell.
+
+ @type hostname: L{Name}
+ @ivar hostname: The domain name of a host that has a server for the cell
+ named by this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1183.html}
+ """
+ TYPE = AFSDB
+
+ fancybasename = 'AFSDB'
+ compareAttributes = ('subtype', 'hostname', 'ttl')
+ showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
+
+ def __init__(self, subtype=0, hostname=b'', ttl=None):
+ """
+ @param hostname: See L{Record_AFSDB.hostname}
+ @type hostname: L{bytes} or L{unicode}
+ """
+ self.subtype = int(subtype)
+ self.hostname = Name(hostname)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!H', self.subtype))
+ self.hostname.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
+ self.subtype, = r
+ self.hostname.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.subtype, self.hostname))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The responsible person for a domain.
+
+ @type mbox: L{Name}
+ @ivar mbox: A domain name that specifies the mailbox for the responsible
+ person.
+
+ @type txt: L{Name}
+ @ivar txt: A domain name for which TXT RR's exist (indirection through
+ which allows information sharing about the contents of this RP record).
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1183.html}
+ """
+ TYPE = RP
+
+ fancybasename = 'RP'
+ compareAttributes = ('mbox', 'txt', 'ttl')
+ showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
+
+ def __init__(self, mbox=b'', txt=b'', ttl=None):
+ """
+ @param mbox: See L{Record_RP.mbox}.
+ @type mbox: L{bytes} or L{unicode}
+
+ @param txt: See L{Record_RP.txt}
+ @type txt: L{bytes} or L{unicode}
+ """
+ self.mbox = Name(mbox)
+ self.txt = Name(txt)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.mbox.encode(strio, compDict)
+ self.txt.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.mbox = Name()
+ self.txt = Name()
+ self.mbox.decode(strio)
+ self.txt.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.mbox, self.txt))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Host information.
+
+ @type cpu: L{bytes}
+ @ivar cpu: Specifies the CPU type.
+
+ @type os: L{bytes}
+ @ivar os: Specifies the OS.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ TYPE = HINFO
+
+ fancybasename = 'HINFO'
+ showAttributes = (('cpu', _nicebytes), ('os', _nicebytes), 'ttl')
+ compareAttributes = ('cpu', 'os', 'ttl')
+
+ def __init__(self, cpu=b'', os=b'', ttl=None):
+ self.cpu, self.os = cpu, os
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
+ strio.write(struct.pack('!B', len(self.os)) + self.os)
+
+
+ def decode(self, strio, length = None):
+ cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.cpu = readPrecisely(strio, cpu)
+ os = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.os = readPrecisely(strio, os)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Record_HINFO):
+ return (self.os.lower() == other.os.lower() and
+ self.cpu.lower() == other.cpu.lower() and
+ self.ttl == other.ttl)
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash((self.os.lower(), self.cpu.lower()))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Mailbox or mail list information.
+
+ This is an experimental record type.
+
+ @type rmailbx: L{Name}
+ @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
+ for the mailing list or mailbox. If this domain name names the root,
+ the owner of the MINFO RR is responsible for itself.
+
+ @type emailbx: L{Name}
+ @ivar emailbx: A domain-name which specifies a mailbox which is to receive
+ error messages related to the mailing list or mailbox specified by the
+ owner of the MINFO record. If this domain name names the root, errors
+ should be returned to the sender of the message.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ TYPE = MINFO
+
+ rmailbx = None
+ emailbx = None
+
+ fancybasename = 'MINFO'
+ compareAttributes = ('rmailbx', 'emailbx', 'ttl')
+ showAttributes = (('rmailbx', 'responsibility', '%s'),
+ ('emailbx', 'errors', '%s'),
+ 'ttl')
+
+ def __init__(self, rmailbx=b'', emailbx=b'', ttl=None):
+ """
+ @param rmailbx: See L{Record_MINFO.rmailbx}.
+ @type rmailbx: L{bytes} or L{unicode}
+
+ @param emailbx: See L{Record_MINFO.rmailbx}.
+ @type emailbx: L{bytes} or L{unicode}
+ """
+ self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.rmailbx.encode(strio, compDict)
+ self.emailbx.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.rmailbx, self.emailbx = Name(), Name()
+ self.rmailbx.decode(strio)
+ self.emailbx.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.rmailbx, self.emailbx))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Mail exchange.
+
+ @type preference: L{int}
+ @ivar preference: Specifies the preference given to this RR among others at
+ the same owner. Lower values are preferred.
+
+ @type name: L{Name}
+ @ivar name: A domain-name which specifies a host willing to act as a mail
+ exchange.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ TYPE = MX
+
+ fancybasename = 'MX'
+ compareAttributes = ('preference', 'name', 'ttl')
+ showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
+
+ def __init__(self, preference=0, name=b'', ttl=None, **kwargs):
+ """
+ @param name: See L{Record_MX.name}.
+ @type name: L{bytes} or L{unicode}
+ """
+ self.preference = int(preference)
+ self.name = Name(kwargs.get('exchange', name))
+ self.ttl = str2time(ttl)
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!H', self.preference))
+ self.name.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
+ self.name = Name()
+ self.name.decode(strio)
+
+ def __hash__(self):
+ return hash((self.preference, self.name))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_SSHFP(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ A record containing the fingerprint of an SSH key.
+
+ @type algorithm: L{int}
+ @ivar algorithm: The SSH key's algorithm, such as L{ALGORITHM_RSA}.
+ Note that the numbering used for SSH key algorithms is specific
+ to the SSHFP record, and is not the same as the numbering
+ used for KEY or SIG records.
+
+ @type fingerprintType: L{int}
+ @ivar fingerprintType: The fingerprint type,
+ such as L{FINGERPRINT_TYPE_SHA256}.
+
+ @type fingerprint: L{bytes}
+ @ivar fingerprint: The key's fingerprint, e.g. a 32-byte SHA-256 digest.
+
+ @cvar ALGORITHM_RSA: The algorithm value for C{ssh-rsa} keys.
+ @cvar ALGORITHM_DSS: The algorithm value for C{ssh-dss} keys.
+ @cvar ALGORITHM_ECDSA: The algorithm value for C{ecdsa-sha2-*} keys.
+ @cvar ALGORITHM_Ed25519: The algorithm value for C{ed25519} keys.
+
+ @cvar FINGERPRINT_TYPE_SHA1: The type for SHA-1 fingerprints.
+ @cvar FINGERPRINT_TYPE_SHA256: The type for SHA-256 fingerprints.
+
+ @see: U{RFC 4255 <https://tools.ietf.org/html/rfc4255>}
+ and
+ U{RFC 6594 <https://tools.ietf.org/html/rfc6594>}
+ """
+ fancybasename = "SSHFP"
+ compareAttributes = ('algorithm', 'fingerprintType', 'fingerprint', 'ttl')
+ showAttributes = ('algorithm', 'fingerprintType', 'fingerprint')
+
+ TYPE = SSHFP
+
+ ALGORITHM_RSA = 1
+ ALGORITHM_DSS = 2
+ ALGORITHM_ECDSA = 3
+ ALGORITHM_Ed25519 = 4
+
+ FINGERPRINT_TYPE_SHA1 = 1
+ FINGERPRINT_TYPE_SHA256 = 2
+
+ def __init__(self, algorithm=0, fingerprintType=0, fingerprint=b'', ttl=0):
+ self.algorithm = algorithm
+ self.fingerprintType = fingerprintType
+ self.fingerprint = fingerprint
+ self.ttl = ttl
+
+
+ def encode(self, strio, compDict=None):
+ strio.write(struct.pack('!BB',
+ self.algorithm, self.fingerprintType))
+ strio.write(self.fingerprint)
+
+
+ def decode(self, strio, length=None):
+ r = struct.unpack('!BB', readPrecisely(strio, 2))
+ (self.algorithm, self.fingerprintType) = r
+ self.fingerprint = readPrecisely(strio, length - 2)
+
+
+ def __hash__(self):
+ return hash((self.algorithm, self.fingerprintType, self.fingerprint))
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Freeform text.
+
+ @type data: L{list} of L{bytes}
+ @ivar data: Freeform text which makes up this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be cached.
+ """
+ TYPE = TXT
+
+ fancybasename = 'TXT'
+ showAttributes = (('data', _nicebyteslist), 'ttl')
+ compareAttributes = ('data', 'ttl')
+
+ def __init__(self, *data, **kw):
+ self.data = list(data)
+ # arg man python sucks so bad
+ self.ttl = str2time(kw.get('ttl', None))
+
+
+ def encode(self, strio, compDict=None):
+ for d in self.data:
+ strio.write(struct.pack('!B', len(d)) + d)
+
+
+ def decode(self, strio, length=None):
+ soFar = 0
+ self.data = []
+ while soFar < length:
+ L = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.data.append(readPrecisely(strio, L))
+ soFar += L + 1
+ if soFar != length:
+ log.msg(
+ "Decoded %d bytes in %s record, but rdlength is %d" % (
+ soFar, self.fancybasename, length
+ )
+ )
+
+
+ def __hash__(self):
+ return hash(tuple(self.data))
+
+
+
+@implementer(IEncodable, IRecord)
+class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin, object):
+ """
+ Encapsulate the wire data for unknown record types so that they can
+ pass through the system unchanged.
+
+ @type data: L{bytes}
+ @ivar data: Wire data which makes up this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds which this record should be cached.
+
+ @since: 11.1
+ """
+ fancybasename = 'UNKNOWN'
+ compareAttributes = ('data', 'ttl')
+ showAttributes = (('data', _nicebytes), 'ttl')
+
+ def __init__(self, data=b'', ttl=None):
+ self.data = data
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Write the raw bytes corresponding to this record's payload to the
+ stream.
+ """
+ strio.write(self.data)
+
+
+ def decode(self, strio, length=None):
+ """
+ Load the bytes which are part of this record from the stream and store
+ them unparsed and unmodified.
+ """
+ if length is None:
+ raise Exception('must know length for unknown record types')
+ self.data = readPrecisely(strio, length)
+
+
+ def __hash__(self):
+ return hash((self.data, self.ttl))
+
+
+
+class Record_SPF(Record_TXT):
+ """
+ Structurally, freeform text. Semantically, a policy definition, formatted
+ as defined in U{rfc 4408<http://www.faqs.org/rfcs/rfc4408.html>}.
+
+ @type data: L{list} of L{bytes}
+ @ivar data: Freeform text which makes up this record.
+
+ @type ttl: L{int}
+ @ivar ttl: The maximum number of seconds
+ which this record should be cached.
+ """
+ TYPE = SPF
+ fancybasename = 'SPF'
+
+
+
+@implementer(IEncodable, IRecord)
+class Record_TSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ A transaction signature, encapsulated in a RR, as described
+ in U{RFC 2845 <https://tools.ietf.org/html/rfc2845>}.
+
+ @type algorithm: L{Name}
+ @ivar algorithm: The name of the signature or MAC algorithm.
+
+ @type timeSigned: L{int}
+ @ivar timeSigned: Signing time, as seconds from the POSIX epoch.
+
+ @type fudge: L{int}
+ @ivar fudge: Allowable time skew, in seconds.
+
+ @type MAC: L{bytes}
+ @ivar MAC: The message digest or signature.
+
+ @type originalID: L{int}
+ @ivar originalID: A message ID.
+
+ @type error: L{int}
+ @ivar error: An error code (extended C{RCODE}) carried
+ in exceptional cases.
+
+ @type otherData: L{bytes}
+ @ivar otherData: Other data carried in exceptional cases.
+
+ """
+ fancybasename = "TSIG"
+ compareAttributes = ('algorithm', 'timeSigned', 'fudge',
+ 'MAC', 'originalID', 'error', 'otherData',
+ 'ttl')
+ showAttributes = ['algorithm', 'timeSigned', 'MAC', 'error', 'otherData']
+
+ TYPE = TSIG
+
+ def __init__(self, algorithm=None, timeSigned=None,
+ fudge=5, MAC=None, originalID=0,
+ error=OK, otherData=b'', ttl=0):
+ # All of our init arguments have to have defaults, because of
+ # the way IEncodable and Message.parseRecords() work, but for
+ # some of our arguments there is no reasonable default; we use
+ # invalid values here to prevent a user of this class from
+ # relying on what's really an internal implementation detail.
+ self.algorithm = None if algorithm is None else Name(algorithm)
+ self.timeSigned = timeSigned
+ self.fudge = str2time(fudge)
+ self.MAC = MAC
+ self.originalID = originalID
+ self.error = error
+ self.otherData = otherData
+ self.ttl = ttl
+
+
+ def encode(self, strio, compDict=None):
+ self.algorithm.encode(strio, compDict)
+ strio.write(struct.pack('!Q', self.timeSigned)[2:]) # 48-bit number
+ strio.write(struct.pack('!HH', self.fudge, len(self.MAC)))
+ strio.write(self.MAC)
+ strio.write(struct.pack('!HHH',
+ self.originalID, self.error,
+ len(self.otherData)))
+ strio.write(self.otherData)
+
+
+ def decode(self, strio, length=None):
+ algorithm = Name()
+ algorithm.decode(strio)
+ self.algorithm = algorithm
+ fields = struct.unpack('!QHH', b'\x00\x00' + readPrecisely(strio, 10))
+ self.timeSigned, self.fudge, macLength = fields
+ self.MAC = readPrecisely(strio, macLength)
+ fields = struct.unpack('!HHH', readPrecisely(strio, 6))
+ self.originalID, self.error, otherLength = fields
+ self.otherData = readPrecisely(strio, otherLength)
+
+
+ def __hash__(self):
+ return hash((self.algorithm, self.timeSigned,
+ self.MAC, self.originalID))
+
+
+
+def _responseFromMessage(responseConstructor, message, **kwargs):
+ """
+ Generate a L{Message} like instance suitable for use as the response to
+ C{message}.
+
+ The C{queries}, C{id} attributes will be copied from C{message} and the
+ C{answer} flag will be set to L{True}.
+
+ @param responseConstructor: A response message constructor with an
+ initializer signature matching L{dns.Message.__init__}.
+ @type responseConstructor: C{callable}
+
+ @param message: A request message.
+ @type message: L{Message}
+
+ @param kwargs: Keyword arguments which will be passed to the initialiser
+ of the response message.
+ @type kwargs: L{dict}
+
+ @return: A L{Message} like response instance.
+ @rtype: C{responseConstructor}
+ """
+ response = responseConstructor(id=message.id, answer=True, **kwargs)
+ response.queries = message.queries[:]
+ return response
+
+
+
+def _getDisplayableArguments(obj, alwaysShow, fieldNames):
+ """
+ Inspect the function signature of C{obj}'s constructor,
+ and get a list of which arguments should be displayed.
+ This is a helper function for C{_compactRepr}.
+
+ @param obj: The instance whose repr is being generated.
+ @param alwaysShow: A L{list} of field names which should always be shown.
+ @param fieldNames: A L{list} of field attribute names which should be shown
+ if they have non-default values.
+ @return: A L{list} of displayable arguments.
+ """
+ displayableArgs = []
+ if _PY3:
+ # Get the argument names and values from the constructor.
+ signature = inspect.signature(obj.__class__.__init__)
+ for name in fieldNames:
+ defaultValue = signature.parameters[name].default
+ fieldValue = getattr(obj, name, defaultValue)
+ if (name in alwaysShow) or (fieldValue != defaultValue):
+ displayableArgs.append(' %s=%r' % (name, fieldValue))
+ else:
+ # Get the argument names and values from the constructor.
+ argspec = inspect.getargspec(obj.__class__.__init__)
+ # Reverse the args and defaults to avoid mapping positional arguments
+ # which don't have a default.
+ defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))
+ for name in fieldNames:
+ defaultValue = defaults.get(name)
+ fieldValue = getattr(obj, name, defaultValue)
+ if (name in alwaysShow) or (fieldValue != defaultValue):
+ displayableArgs.append(' %s=%r' % (name, fieldValue))
+
+ return displayableArgs
+
+
+
+def _compactRepr(obj, alwaysShow=None, flagNames=None, fieldNames=None,
+ sectionNames=None):
+ """
+ Return a L{str} representation of C{obj} which only shows fields with
+ non-default values, flags which are True and sections which have been
+ explicitly set.
+
+ @param obj: The instance whose repr is being generated.
+ @param alwaysShow: A L{list} of field names which should always be shown.
+ @param flagNames: A L{list} of flag attribute names which should be shown if
+ they are L{True}.
+ @param fieldNames: A L{list} of field attribute names which should be shown
+ if they have non-default values.
+ @param sectionNames: A L{list} of section attribute names which should be
+ shown if they have been assigned a value.
+
+ @return: A L{str} representation of C{obj}.
+ """
+ if alwaysShow is None:
+ alwaysShow = []
+
+ if flagNames is None:
+ flagNames = []
+
+ if fieldNames is None:
+ fieldNames = []
+
+ if sectionNames is None:
+ sectionNames = []
+
+ setFlags = []
+ for name in flagNames:
+ if name in alwaysShow or getattr(obj, name, False) == True:
+ setFlags.append(name)
+
+ displayableArgs = _getDisplayableArguments(obj, alwaysShow, fieldNames)
+ out = ['<', obj.__class__.__name__] + displayableArgs
+
+ if setFlags:
+ out.append(' flags=%s' % (','.join(setFlags),))
+
+ for name in sectionNames:
+ section = getattr(obj, name, [])
+ if section:
+ out.append(' %s=%r' % (name, section))
+
+ out.append('>')
+
+ return ''.join(out)
+
+
+
+class Message(tputil.FancyEqMixin):
+ """
+ L{Message} contains all the information represented by a single
+ DNS request or response.
+
+ @ivar id: See L{__init__}
+ @ivar answer: See L{__init__}
+ @ivar opCode: See L{__init__}
+ @ivar recDes: See L{__init__}
+ @ivar recAv: See L{__init__}
+ @ivar auth: See L{__init__}
+ @ivar rCode: See L{__init__}
+ @ivar trunc: See L{__init__}
+ @ivar maxSize: See L{__init__}
+ @ivar authenticData: See L{__init__}
+ @ivar checkingDisabled: See L{__init__}
+
+ @ivar queries: The queries which are being asked of or answered by
+ DNS server.
+ @type queries: L{list} of L{Query}
+
+ @ivar answers: Records containing the answers to C{queries} if
+ this is a response message.
+ @type answers: L{list} of L{RRHeader}
+
+ @ivar authority: Records containing information about the
+ authoritative DNS servers for the names in C{queries}.
+ @type authority: L{list} of L{RRHeader}
+
+ @ivar additional: Records containing IP addresses of host names
+ in C{answers} and C{authority}.
+ @type additional: L{list} of L{RRHeader}
+
+ @ivar _flagNames: The names of attributes representing the flag header
+ fields.
+ @ivar _fieldNames: The names of attributes representing non-flag fixed
+ header fields.
+ @ivar _sectionNames: The names of attributes representing the record
+ sections of this message.
+ """
+ compareAttributes = (
+ 'id', 'answer', 'opCode', 'recDes', 'recAv',
+ 'auth', 'rCode', 'trunc', 'maxSize',
+ 'authenticData', 'checkingDisabled',
+ 'queries', 'answers', 'authority', 'additional'
+ )
+
+ headerFmt = "!H2B4H"
+ headerSize = struct.calcsize(headerFmt)
+
+ # Question, answer, additional, and nameserver lists
+ queries = answers = add = ns = None
+
+ def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
+ auth=0, rCode=OK, trunc=0, maxSize=512,
+ authenticData=0, checkingDisabled=0):
+ """
+ @param id: A 16 bit identifier assigned by the program that
+ generates any kind of query. This identifier is copied to
+ the corresponding reply and can be used by the requester
+ to match up replies to outstanding queries.
+ @type id: L{int}
+
+ @param answer: A one bit field that specifies whether this
+ message is a query (0), or a response (1).
+ @type answer: L{int}
+
+ @param opCode: A four bit field that specifies kind of query in
+ this message. This value is set by the originator of a query
+ and copied into the response.
+ @type opCode: L{int}
+
+ @param recDes: Recursion Desired - this bit may be set in a
+ query and is copied into the response. If RD is set, it
+ directs the name server to pursue the query recursively.
+ Recursive query support is optional.
+ @type recDes: L{int}
+
+ @param recAv: Recursion Available - this bit is set or cleared
+ in a response and denotes whether recursive query support
+ is available in the name server.
+ @type recAv: L{int}
+
+ @param auth: Authoritative Answer - this bit is valid in
+ responses and specifies that the responding name server
+ is an authority for the domain name in question section.
+ @type auth: L{int}
+
+ @ivar rCode: A response code, used to indicate success or failure in a
+ message which is a response from a server to a client request.
+ @type rCode: C{0 <= int < 16}
+
+ @param trunc: A flag indicating that this message was
+ truncated due to length greater than that permitted on the
+ transmission channel.
+ @type trunc: L{int}
+
+ @param maxSize: The requestor's UDP payload size is the number
+ of octets of the largest UDP payload that can be
+ reassembled and delivered in the requestor's network
+ stack.
+ @type maxSize: L{int}
+
+ @param authenticData: A flag indicating in a response that all
+ the data included in the answer and authority portion of
+ the response has been authenticated by the server
+ according to the policies of that server.
+ See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
+ @type authenticData: L{int}
+
+ @param checkingDisabled: A flag indicating in a query that
+ pending (non-authenticated) data is acceptable to the
+ resolver sending the query.
+ See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
+ @type authenticData: L{int}
+ """
+ self.maxSize = maxSize
+ self.id = id
+ self.answer = answer
+ self.opCode = opCode
+ self.auth = auth
+ self.trunc = trunc
+ self.recDes = recDes
+ self.recAv = recAv
+ self.rCode = rCode
+ self.authenticData = authenticData
+ self.checkingDisabled = checkingDisabled
+
+ self.queries = []
+ self.answers = []
+ self.authority = []
+ self.additional = []
+
+
+ def __repr__(self):
+ """
+ Generate a repr of this L{Message}.
+
+ Only includes the non-default fields and sections and only includes
+ flags which are set. The C{id} is always shown.
+
+ @return: The native string repr.
+ """
+ return _compactRepr(
+ self,
+ flagNames=('answer', 'auth', 'trunc', 'recDes', 'recAv',
+ 'authenticData', 'checkingDisabled'),
+ fieldNames=('id', 'opCode', 'rCode', 'maxSize'),
+ sectionNames=('queries', 'answers', 'authority', 'additional'),
+ alwaysShow=('id',)
+ )
+
+
+ def addQuery(self, name, type=ALL_RECORDS, cls=IN):
+ """
+ Add another query to this Message.
+
+ @type name: L{bytes}
+ @param name: The name to query.
+
+ @type type: L{int}
+ @param type: Query type
+
+ @type cls: L{int}
+ @param cls: Query class
+ """
+ self.queries.append(Query(name, type, cls))
+
+
+ def encode(self, strio):
+ compDict = {}
+ body_tmp = BytesIO()
+ for q in self.queries:
+ q.encode(body_tmp, compDict)
+ for q in self.answers:
+ q.encode(body_tmp, compDict)
+ for q in self.authority:
+ q.encode(body_tmp, compDict)
+ for q in self.additional:
+ q.encode(body_tmp, compDict)
+ body = body_tmp.getvalue()
+ size = len(body) + self.headerSize
+ if self.maxSize and size > self.maxSize:
+ self.trunc = 1
+ body = body[:self.maxSize - self.headerSize]
+ byte3 = (( ( self.answer & 1 ) << 7 )
+ | ((self.opCode & 0xf ) << 3 )
+ | ((self.auth & 1 ) << 2 )
+ | ((self.trunc & 1 ) << 1 )
+ | ( self.recDes & 1 ) )
+ byte4 = ( ( (self.recAv & 1 ) << 7 )
+ | ((self.authenticData & 1) << 5)
+ | ((self.checkingDisabled & 1) << 4)
+ | (self.rCode & 0xf ) )
+
+ strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
+ len(self.queries), len(self.answers),
+ len(self.authority), len(self.additional)))
+ strio.write(body)
+
+
+ def decode(self, strio, length=None):
+ self.maxSize = 0
+ header = readPrecisely(strio, self.headerSize)
+ r = struct.unpack(self.headerFmt, header)
+ self.id, byte3, byte4, nqueries, nans, nns, nadd = r
+ self.answer = ( byte3 >> 7 ) & 1
+ self.opCode = ( byte3 >> 3 ) & 0xf
+ self.auth = ( byte3 >> 2 ) & 1
+ self.trunc = ( byte3 >> 1 ) & 1
+ self.recDes = byte3 & 1
+ self.recAv = ( byte4 >> 7 ) & 1
+ self.authenticData = ( byte4 >> 5 ) & 1
+ self.checkingDisabled = ( byte4 >> 4 ) & 1
+ self.rCode = byte4 & 0xf
+
+ self.queries = []
+ for i in range(nqueries):
+ q = Query()
+ try:
+ q.decode(strio)
+ except EOFError:
+ return
+ self.queries.append(q)
+
+ items = (
+ (self.answers, nans),
+ (self.authority, nns),
+ (self.additional, nadd))
+
+ for (l, n) in items:
+ self.parseRecords(l, n, strio)
+
+
+ def parseRecords(self, list, num, strio):
+ for i in range(num):
+ header = RRHeader(auth=self.auth)
+ try:
+ header.decode(strio)
+ except EOFError:
+ return
+ t = self.lookupRecordType(header.type)
+ if not t:
+ continue
+ header.payload = t(ttl=header.ttl)
+ try:
+ header.payload.decode(strio, header.rdlength)
+ except EOFError:
+ return
+ list.append(header)
+
+
+ # Create a mapping from record types to their corresponding Record_*
+ # classes. This relies on the global state which has been created so
+ # far in initializing this module (so don't define Record classes after
+ # this).
+ _recordTypes = {}
+ for name in globals():
+ if name.startswith('Record_'):
+ _recordTypes[globals()[name].TYPE] = globals()[name]
+
+ # Clear the iteration variable out of the class namespace so it
+ # doesn't become an attribute.
+ del name
+
+
+ def lookupRecordType(self, type):
+ """
+ Retrieve the L{IRecord} implementation for the given record type.
+
+ @param type: A record type, such as C{A} or L{NS}.
+ @type type: L{int}
+
+ @return: An object which implements L{IRecord} or L{None} if none
+ can be found for the given type.
+ @rtype: L{types.ClassType}
+ """
+ return self._recordTypes.get(type, UnknownRecord)
+
+
+ def toStr(self):
+ """
+ Encode this L{Message} into a byte string in the format described by RFC
+ 1035.
+
+ @rtype: L{bytes}
+ """
+ strio = BytesIO()
+ self.encode(strio)
+ return strio.getvalue()
+
+
+ def fromStr(self, str):
+ """
+ Decode a byte string in the format described by RFC 1035 into this
+ L{Message}.
+
+ @param str: L{bytes}
+ """
+ strio = BytesIO(str)
+ self.decode(strio)
+
+
+
+class _EDNSMessage(tputil.FancyEqMixin, object):
+ """
+ An I{EDNS} message.
+
+ Designed for compatibility with L{Message} but with a narrower public
+ interface.
+
+ Most importantly, L{_EDNSMessage.fromStr} will interpret and remove I{OPT}
+ records that are present in the additional records section.
+
+ The I{OPT} records are used to populate certain I{EDNS} specific attributes.
+
+ L{_EDNSMessage.toStr} will add suitable I{OPT} records to the additional
+ section to represent the extended EDNS information.
+
+ @see: U{https://tools.ietf.org/html/rfc6891}
+
+ @ivar id: See L{__init__}
+ @ivar answer: See L{__init__}
+ @ivar opCode: See L{__init__}
+ @ivar auth: See L{__init__}
+ @ivar trunc: See L{__init__}
+ @ivar recDes: See L{__init__}
+ @ivar recAv: See L{__init__}
+ @ivar rCode: See L{__init__}
+ @ivar ednsVersion: See L{__init__}
+ @ivar dnssecOK: See L{__init__}
+ @ivar authenticData: See L{__init__}
+ @ivar checkingDisabled: See L{__init__}
+ @ivar maxSize: See L{__init__}
+
+ @ivar queries: See L{__init__}
+ @ivar answers: See L{__init__}
+ @ivar authority: See L{__init__}
+ @ivar additional: See L{__init__}
+
+ @ivar _messageFactory: A constructor of L{Message} instances. Called by
+ C{_toMessage} and C{_fromMessage}.
+ """
+
+ compareAttributes = (
+ 'id', 'answer', 'opCode', 'auth', 'trunc',
+ 'recDes', 'recAv', 'rCode', 'ednsVersion', 'dnssecOK',
+ 'authenticData', 'checkingDisabled', 'maxSize',
+ 'queries', 'answers', 'authority', 'additional')
+
+ _messageFactory = Message
+
+ def __init__(self, id=0, answer=False, opCode=OP_QUERY, auth=False,
+ trunc=False, recDes=False, recAv=False, rCode=0,
+ ednsVersion=0, dnssecOK=False, authenticData=False,
+ checkingDisabled=False, maxSize=512,
+ queries=None, answers=None, authority=None, additional=None):
+ """
+ Construct a new L{_EDNSMessage}
+
+ @see: U{RFC1035 section-4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
+ @see: U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}
+ @see: U{RFC3225 section-3<https://tools.ietf.org/html/rfc3225#section-3>}
+ @see: U{RFC6891 section-6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}
+
+ @param id: A 16 bit identifier assigned by the program that generates
+ any kind of query. This identifier is copied the corresponding
+ reply and can be used by the requester to match up replies to
+ outstanding queries.
+ @type id: L{int}
+
+ @param answer: A one bit field that specifies whether this message is a
+ query (0), or a response (1).
+ @type answer: L{bool}
+
+ @param opCode: A four bit field that specifies kind of query in this
+ message. This value is set by the originator of a query and copied
+ into the response.
+ @type opCode: L{int}
+
+ @param auth: Authoritative Answer - this bit is valid in responses, and
+ specifies that the responding name server is an authority for the
+ domain name in question section.
+ @type auth: L{bool}
+
+ @param trunc: Truncation - specifies that this message was truncated due
+ to length greater than that permitted on the transmission channel.
+ @type trunc: L{bool}
+
+ @param recDes: Recursion Desired - this bit may be set in a query and is
+ copied into the response. If set, it directs the name server to
+ pursue the query recursively. Recursive query support is optional.
+ @type recDes: L{bool}
+
+ @param recAv: Recursion Available - this bit is set or cleared in a
+ response, and denotes whether recursive query support is available
+ in the name server.
+ @type recAv: L{bool}
+
+ @param rCode: Extended 12-bit RCODE. Derived from the 4 bits defined in
+ U{RFC1035 4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
+ and the upper 8bits defined in U{RFC6891
+ 6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}.
+ @type rCode: L{int}
+
+ @param ednsVersion: Indicates the EDNS implementation level. Set to
+ L{None} to prevent any EDNS attributes and options being added to
+ the encoded byte string.
+ @type ednsVersion: L{int} or L{None}
+
+ @param dnssecOK: DNSSEC OK bit as defined by
+ U{RFC3225 3<https://tools.ietf.org/html/rfc3225#section-3>}.
+ @type dnssecOK: L{bool}
+
+ @param authenticData: A flag indicating in a response that all the data
+ included in the answer and authority portion of the response has
+ been authenticated by the server according to the policies of that
+ server.
+ See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
+ @type authenticData: L{bool}
+
+ @param checkingDisabled: A flag indicating in a query that pending
+ (non-authenticated) data is acceptable to the resolver sending the
+ query.
+ See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
+ @type authenticData: L{bool}
+
+ @param maxSize: The requestor's UDP payload size is the number of octets
+ of the largest UDP payload that can be reassembled and delivered in
+ the requestor's network stack.
+ @type maxSize: L{int}
+
+ @param queries: The L{list} of L{Query} associated with this message.
+ @type queries: L{list} of L{Query}
+
+ @param answers: The L{list} of answers associated with this message.
+ @type answers: L{list} of L{RRHeader}
+
+ @param authority: The L{list} of authority records associated with this
+ message.
+ @type authority: L{list} of L{RRHeader}
+
+ @param additional: The L{list} of additional records associated with
+ this message.
+ @type additional: L{list} of L{RRHeader}
+ """
+ self.id = id
+ self.answer = answer
+ self.opCode = opCode
+ self.auth = auth
+ self.trunc = trunc
+ self.recDes = recDes
+ self.recAv = recAv
+ self.rCode = rCode
+ self.ednsVersion = ednsVersion
+ self.dnssecOK = dnssecOK
+ self.authenticData = authenticData
+ self.checkingDisabled = checkingDisabled
+ self.maxSize = maxSize
+
+ if queries is None:
+ queries = []
+ self.queries = queries
+
+ if answers is None:
+ answers = []
+ self.answers = answers
+
+ if authority is None:
+ authority = []
+ self.authority = authority
+
+ if additional is None:
+ additional = []
+ self.additional = additional
+
+
+ def __repr__(self):
+ return _compactRepr(
+ self,
+ flagNames=('answer', 'auth', 'trunc', 'recDes', 'recAv',
+ 'authenticData', 'checkingDisabled', 'dnssecOK'),
+ fieldNames=('id', 'opCode', 'rCode', 'maxSize', 'ednsVersion'),
+ sectionNames=('queries', 'answers', 'authority', 'additional'),
+ alwaysShow=('id',)
+ )
+
+
+ def _toMessage(self):
+ """
+ Convert to a standard L{dns.Message}.
+
+ If C{ednsVersion} is not None, an L{_OPTHeader} instance containing all
+ the I{EDNS} specific attributes and options will be appended to the list
+ of C{additional} records.
+
+ @return: A L{dns.Message}
+ @rtype: L{dns.Message}
+ """
+ m = self._messageFactory(
+ id=self.id,
+ answer=self.answer,
+ opCode=self.opCode,
+ auth=self.auth,
+ trunc=self.trunc,
+ recDes=self.recDes,
+ recAv=self.recAv,
+ # Assign the lower 4 bits to the message
+ rCode=self.rCode & 0xf,
+ authenticData=self.authenticData,
+ checkingDisabled=self.checkingDisabled)
+
+ m.queries = self.queries[:]
+ m.answers = self.answers[:]
+ m.authority = self.authority[:]
+ m.additional = self.additional[:]
+
+ if self.ednsVersion is not None:
+ o = _OPTHeader(version=self.ednsVersion,
+ dnssecOK=self.dnssecOK,
+ udpPayloadSize=self.maxSize,
+ # Assign the upper 8 bits to the OPT record
+ extendedRCODE=self.rCode >> 4)
+ m.additional.append(o)
+
+ return m
+
+
+ def toStr(self):
+ """
+ Encode to wire format by first converting to a standard L{dns.Message}.
+
+ @return: A L{bytes} string.
+ """
+ return self._toMessage().toStr()
+
+
+ @classmethod
+ def _fromMessage(cls, message):
+ """
+ Construct and return a new L{_EDNSMessage} whose attributes and records
+ are derived from the attributes and records of C{message} (a L{Message}
+ instance).
+
+ If present, an C{OPT} record will be extracted from the C{additional}
+ section and its attributes and options will be used to set the EDNS
+ specific attributes C{extendedRCODE}, C{ednsVersion}, C{dnssecOK},
+ C{ednsOptions}.
+
+ The C{extendedRCODE} will be combined with C{message.rCode} and assigned
+ to C{self.rCode}.
+
+ @param message: The source L{Message}.
+ @type message: L{Message}
+
+ @return: A new L{_EDNSMessage}
+ @rtype: L{_EDNSMessage}
+ """
+ additional = []
+ optRecords = []
+ for r in message.additional:
+ if r.type == OPT:
+ optRecords.append(_OPTHeader.fromRRHeader(r))
+ else:
+ additional.append(r)
+
+ newMessage = cls(
+ id=message.id,
+ answer=message.answer,
+ opCode=message.opCode,
+ auth=message.auth,
+ trunc=message.trunc,
+ recDes=message.recDes,
+ recAv=message.recAv,
+ rCode=message.rCode,
+ authenticData=message.authenticData,
+ checkingDisabled=message.checkingDisabled,
+ # Default to None, it will be updated later when the OPT records are
+ # parsed.
+ ednsVersion=None,
+ dnssecOK=False,
+ queries=message.queries[:],
+ answers=message.answers[:],
+ authority=message.authority[:],
+ additional=additional,
+ )
+
+ if len(optRecords) == 1:
+ # XXX: If multiple OPT records are received, an EDNS server should
+ # respond with FORMERR. See ticket:5669#comment:1.
+ opt = optRecords[0]
+ newMessage.ednsVersion = opt.version
+ newMessage.dnssecOK = opt.dnssecOK
+ newMessage.maxSize = opt.udpPayloadSize
+ newMessage.rCode = opt.extendedRCODE << 4 | message.rCode
+
+ return newMessage
+
+
+ def fromStr(self, bytes):
+ """
+ Decode from wire format, saving flags, values and records to this
+ L{_EDNSMessage} instance in place.
+
+ @param bytes: The full byte string to be decoded.
+ @type bytes: L{bytes}
+ """
+ m = self._messageFactory()
+ m.fromStr(bytes)
+
+ ednsMessage = self._fromMessage(m)
+ for attrName in self.compareAttributes:
+ setattr(self, attrName, getattr(ednsMessage, attrName))
+
+
+
+class DNSMixin(object):
+ """
+ DNS protocol mixin shared by UDP and TCP implementations.
+
+ @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
+ be used to issue DNS queries and manage request timeouts.
+ """
+ id = None
+ liveMessages = None
+
+ def __init__(self, controller, reactor=None):
+ self.controller = controller
+ self.id = random.randrange(2 ** 10, 2 ** 15)
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+
+ def pickID(self):
+ """
+ Return a unique ID for queries.
+ """
+ while True:
+ id = randomSource()
+ if id not in self.liveMessages:
+ return id
+
+
+ def callLater(self, period, func, *args):
+ """
+ Wrapper around reactor.callLater, mainly for test purpose.
+ """
+ return self._reactor.callLater(period, func, *args)
+
+
+ def _query(self, queries, timeout, id, writeMessage):
+ """
+ Send out a message with the given queries.
+
+ @type queries: L{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @type timeout: L{int} or C{float}
+ @param timeout: How long to wait before giving up
+
+ @type id: L{int}
+ @param id: Unique key for this request
+
+ @type writeMessage: C{callable}
+ @param writeMessage: One-parameter callback which writes the message
+
+ @rtype: C{Deferred}
+ @return: a C{Deferred} which will be fired with the result of the
+ query, or errbacked with any errors that could happen (exceptions
+ during writing of the query, timeout errors, ...).
+ """
+ m = Message(id, recDes=1)
+ m.queries = queries
+
+ try:
+ writeMessage(m)
+ except:
+ return defer.fail()
+
+ resultDeferred = defer.Deferred()
+ cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
+ self.liveMessages[id] = (resultDeferred, cancelCall)
+
+ return resultDeferred
+
+ def _clearFailed(self, deferred, id):
+ """
+ Clean the Deferred after a timeout.
+ """
+ try:
+ del self.liveMessages[id]
+ except KeyError:
+ pass
+ deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
+
+
+class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
+ """
+ DNS protocol over UDP.
+ """
+ resends = None
+
+ def stopProtocol(self):
+ """
+ Stop protocol: reset state variables.
+ """
+ self.liveMessages = {}
+ self.resends = {}
+ self.transport = None
+
+ def startProtocol(self):
+ """
+ Upon start, reset internal state.
+ """
+ self.liveMessages = {}
+ self.resends = {}
+
+ def writeMessage(self, message, address):
+ """
+ Send a message holding DNS queries.
+
+ @type message: L{Message}
+ """
+ self.transport.write(message.toStr(), address)
+
+ def startListening(self):
+ self._reactor.listenUDP(0, self, maxPacketSize=512)
+
+ def datagramReceived(self, data, addr):
+ """
+ Read a datagram, extract the message in it and trigger the associated
+ Deferred.
+ """
+ m = Message()
+ try:
+ m.fromStr(data)
+ except EOFError:
+ log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
+ return
+ except:
+ # Nothing should trigger this, but since we're potentially
+ # invoking a lot of different decoding methods, we might as well
+ # be extra cautious. Anything that triggers this is itself
+ # buggy.
+ log.err(failure.Failure(), "Unexpected decoding error")
+ return
+
+ if m.id in self.liveMessages:
+ d, canceller = self.liveMessages[m.id]
+ del self.liveMessages[m.id]
+ canceller.cancel()
+ # XXX we shouldn't need this hack of catching exception on callback()
+ try:
+ d.callback(m)
+ except:
+ log.err()
+ else:
+ if m.id not in self.resends:
+ self.controller.messageReceived(m, self, addr)
+
+
+ def removeResend(self, id):
+ """
+ Mark message ID as no longer having duplication suppression.
+ """
+ try:
+ del self.resends[id]
+ except KeyError:
+ pass
+
+ def query(self, address, queries, timeout=10, id=None):
+ """
+ Send out a message with the given queries.
+
+ @type address: L{tuple} of L{str} and L{int}
+ @param address: The address to which to send the query
+
+ @type queries: L{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @rtype: C{Deferred}
+ """
+ if not self.transport:
+ # XXX transport might not get created automatically, use callLater?
+ try:
+ self.startListening()
+ except CannotListenError:
+ return defer.fail()
+
+ if id is None:
+ id = self.pickID()
+ else:
+ self.resends[id] = 1
+
+ def writeMessage(m):
+ self.writeMessage(m, address)
+
+ return self._query(queries, timeout, id, writeMessage)
+
+
+class DNSProtocol(DNSMixin, protocol.Protocol):
+ """
+ DNS protocol over TCP.
+ """
+ length = None
+ buffer = b''
+
+ def writeMessage(self, message):
+ """
+ Send a message holding DNS queries.
+
+ @type message: L{Message}
+ """
+ s = message.toStr()
+ self.transport.write(struct.pack('!H', len(s)) + s)
+
+ def connectionMade(self):
+ """
+ Connection is made: reset internal state, and notify the controller.
+ """
+ self.liveMessages = {}
+ self.controller.connectionMade(self)
+
+
+ def connectionLost(self, reason):
+ """
+ Notify the controller that this protocol is no longer
+ connected.
+ """
+ self.controller.connectionLost(self)
+
+
+ def dataReceived(self, data):
+ self.buffer += data
+
+ while self.buffer:
+ if self.length is None and len(self.buffer) >= 2:
+ self.length = struct.unpack('!H', self.buffer[:2])[0]
+ self.buffer = self.buffer[2:]
+
+ if len(self.buffer) >= self.length:
+ myChunk = self.buffer[:self.length]
+ m = Message()
+ m.fromStr(myChunk)
+
+ try:
+ d, canceller = self.liveMessages[m.id]
+ except KeyError:
+ self.controller.messageReceived(m, self)
+ else:
+ del self.liveMessages[m.id]
+ canceller.cancel()
+ # XXX we shouldn't need this hack
+ try:
+ d.callback(m)
+ except:
+ log.err()
+
+ self.buffer = self.buffer[self.length:]
+ self.length = None
+ else:
+ break
+
+
+ def query(self, queries, timeout=60):
+ """
+ Send out a message with the given queries.
+
+ @type queries: L{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @rtype: C{Deferred}
+ """
+ id = self.pickID()
+ return self._query(queries, timeout, id, self.writeMessage)