aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/names
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/py3/twisted/names
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/names')
-rw-r--r--contrib/python/Twisted/py3/twisted/names/__init__.py6
-rw-r--r--contrib/python/Twisted/py3/twisted/names/_rfc1982.py261
-rw-r--r--contrib/python/Twisted/py3/twisted/names/authority.py503
-rw-r--r--contrib/python/Twisted/py3/twisted/names/cache.py131
-rw-r--r--contrib/python/Twisted/py3/twisted/names/client.py734
-rw-r--r--contrib/python/Twisted/py3/twisted/names/common.py263
-rw-r--r--contrib/python/Twisted/py3/twisted/names/dns.py3390
-rw-r--r--contrib/python/Twisted/py3/twisted/names/error.py94
-rw-r--r--contrib/python/Twisted/py3/twisted/names/hosts.py151
-rw-r--r--contrib/python/Twisted/py3/twisted/names/newsfragments/.gitignore1
-rw-r--r--contrib/python/Twisted/py3/twisted/names/resolve.py91
-rw-r--r--contrib/python/Twisted/py3/twisted/names/root.py331
-rw-r--r--contrib/python/Twisted/py3/twisted/names/secondary.py216
-rw-r--r--contrib/python/Twisted/py3/twisted/names/server.py569
-rw-r--r--contrib/python/Twisted/py3/twisted/names/srvconnect.py271
-rw-r--r--contrib/python/Twisted/py3/twisted/names/tap.py149
16 files changed, 7161 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/names/__init__.py b/contrib/python/Twisted/py3/twisted/names/__init__.py
new file mode 100644
index 0000000000..ccdf8ba331
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Names: DNS server and client implementations.
+"""
diff --git a/contrib/python/Twisted/py3/twisted/names/_rfc1982.py b/contrib/python/Twisted/py3/twisted/names/_rfc1982.py
new file mode 100644
index 0000000000..61d43c009b
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/_rfc1982.py
@@ -0,0 +1,261 @@
+# -*- test-case-name: twisted.names.test.test_rfc1982 -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Utilities for handling RFC1982 Serial Number Arithmetic.
+
+@see: U{http://tools.ietf.org/html/rfc1982}
+
+@var RFC4034_TIME_FORMAT: RRSIG Time field presentation format. The Signature
+ Expiration Time and Inception Time field values MUST be represented either
+ as an unsigned decimal integer indicating seconds since 1 January 1970
+ 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in UTC. See U{RRSIG Presentation
+ Format<https://tools.ietf.org/html/rfc4034#section-3.2>}
+"""
+
+
+import calendar
+from datetime import datetime, timedelta
+
+from twisted.python.compat import nativeString
+from twisted.python.util import FancyStrMixin
+
+RFC4034_TIME_FORMAT = "%Y%m%d%H%M%S"
+
+
+class SerialNumber(FancyStrMixin):
+ """
+ An RFC1982 Serial Number.
+
+ This class implements RFC1982 DNS Serial Number Arithmetic.
+
+ SNA is used in DNS and specifically in DNSSEC as defined in RFC4034 in the
+ DNSSEC Signature Expiration and Inception Fields.
+
+ @see: U{https://tools.ietf.org/html/rfc1982}
+ @see: U{https://tools.ietf.org/html/rfc4034}
+
+ @ivar _serialBits: See C{serialBits} of L{__init__}.
+ @ivar _number: See C{number} of L{__init__}.
+ @ivar _modulo: The value at which wrapping will occur.
+ @ivar _halfRing: Half C{_modulo}. If another L{SerialNumber} value is larger
+ than this, it would lead to a wrapped value which is larger than the
+ first and comparisons are therefore ambiguous.
+ @ivar _maxAdd: Half C{_modulo} plus 1. If another L{SerialNumber} value is
+ larger than this, it would lead to a wrapped value which is larger than
+ the first. Comparisons with the original value would therefore be
+ ambiguous.
+ """
+
+ showAttributes = (
+ ("_number", "number", "%d"),
+ ("_serialBits", "serialBits", "%d"),
+ )
+
+ def __init__(self, number: int, serialBits: int = 32):
+ """
+ Construct an L{SerialNumber} instance.
+
+ @param number: An L{int} which will be stored as the modulo
+ C{number % 2 ^ serialBits}
+ @type number: L{int}
+
+ @param serialBits: The size of the serial number space. The power of two
+ which results in one larger than the largest integer corresponding
+ to a serial number value.
+ @type serialBits: L{int}
+ """
+ self._serialBits = serialBits
+ self._modulo = 2**serialBits
+ self._halfRing: int = 2 ** (serialBits - 1)
+ self._maxAdd = 2 ** (serialBits - 1) - 1
+ self._number: int = int(number) % self._modulo
+
+ def _convertOther(self, other: object) -> "SerialNumber":
+ """
+ Check that a foreign object is suitable for use in the comparison or
+ arithmetic magic methods of this L{SerialNumber} instance. Raise
+ L{TypeError} if not.
+
+ @param other: The foreign L{object} to be checked.
+ @return: C{other} after compatibility checks and possible coercion.
+ @raise TypeError: If C{other} is not compatible.
+ """
+ if not isinstance(other, SerialNumber):
+ raise TypeError(f"cannot compare or combine {self!r} and {other!r}")
+
+ if self._serialBits != other._serialBits:
+ raise TypeError(
+ "cannot compare or combine SerialNumber instances with "
+ "different serialBits. %r and %r" % (self, other)
+ )
+
+ return other
+
+ def __str__(self) -> str:
+ """
+ Return a string representation of this L{SerialNumber} instance.
+
+ @rtype: L{nativeString}
+ """
+ return nativeString("%d" % (self._number,))
+
+ def __int__(self):
+ """
+ @return: The integer value of this L{SerialNumber} instance.
+ @rtype: L{int}
+ """
+ return self._number
+
+ def __eq__(self, other: object) -> bool:
+ """
+ Allow rich equality comparison with another L{SerialNumber} instance.
+ """
+ try:
+ other = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ return other._number == self._number
+
+ def __lt__(self, other: object) -> bool:
+ """
+ Allow I{less than} comparison with another L{SerialNumber} instance.
+ """
+ try:
+ other = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ return (
+ self._number < other._number
+ and (other._number - self._number) < self._halfRing
+ ) or (
+ self._number > other._number
+ and (self._number - other._number) > self._halfRing
+ )
+
+ def __gt__(self, other: object) -> bool:
+ """
+ Allow I{greater than} comparison with another L{SerialNumber} instance.
+ """
+ try:
+ other_sn = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ return (
+ self._number < other_sn._number
+ and (other_sn._number - self._number) > self._halfRing
+ ) or (
+ self._number > other_sn._number
+ and (self._number - other_sn._number) < self._halfRing
+ )
+
+ def __le__(self, other: object) -> bool:
+ """
+ Allow I{less than or equal} comparison with another L{SerialNumber}
+ instance.
+ """
+ try:
+ other = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ return self == other or self < other
+
+ def __ge__(self, other: object) -> bool:
+ """
+ Allow I{greater than or equal} comparison with another L{SerialNumber}
+ instance.
+ """
+ try:
+ other = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ return self == other or self > other
+
+ def __add__(self, other: object) -> "SerialNumber":
+ """
+ Allow I{addition} with another L{SerialNumber} instance.
+
+ Serial numbers may be incremented by the addition of a positive
+ integer n, where n is taken from the range of integers
+ [0 .. (2^(SERIAL_BITS - 1) - 1)]. For a sequence number s, the
+ result of such an addition, s', is defined as
+
+ s' = (s + n) modulo (2 ^ SERIAL_BITS)
+
+ where the addition and modulus operations here act upon values that are
+ non-negative values of unbounded size in the usual ways of integer
+ arithmetic.
+
+ Addition of a value outside the range
+ [0 .. (2^(SERIAL_BITS - 1) - 1)] is undefined.
+
+ @see: U{http://tools.ietf.org/html/rfc1982#section-3.1}
+
+ @raise ArithmeticError: If C{other} is more than C{_maxAdd}
+ ie more than half the maximum value of this serial number.
+ """
+ try:
+ other = self._convertOther(other)
+ except TypeError:
+ return NotImplemented
+ if other._number <= self._maxAdd:
+ return SerialNumber(
+ (self._number + other._number) % self._modulo,
+ serialBits=self._serialBits,
+ )
+ else:
+ raise ArithmeticError(
+ "value %r outside the range 0 .. %r"
+ % (
+ other._number,
+ self._maxAdd,
+ )
+ )
+
+ def __hash__(self):
+ """
+ Allow L{SerialNumber} instances to be hashed for use as L{dict} keys.
+
+ @rtype: L{int}
+ """
+ return hash(self._number)
+
+ @classmethod
+ def fromRFC4034DateString(cls, utcDateString):
+ """
+ Create an L{SerialNumber} instance from a date string in format
+ 'YYYYMMDDHHMMSS' described in U{RFC4034
+ 3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.
+
+ The L{SerialNumber} instance stores the date as a 32bit UNIX timestamp.
+
+ @see: U{https://tools.ietf.org/html/rfc4034#section-3.1.5}
+
+ @param utcDateString: A UTC date/time string of format I{YYMMDDhhmmss}
+ which will be converted to seconds since the UNIX epoch.
+ @type utcDateString: L{unicode}
+
+ @return: An L{SerialNumber} instance containing the supplied date as a
+ 32bit UNIX timestamp.
+ """
+ parsedDate = datetime.strptime(utcDateString, RFC4034_TIME_FORMAT)
+ secondsSinceEpoch = calendar.timegm(parsedDate.utctimetuple())
+ return cls(secondsSinceEpoch, serialBits=32)
+
+ def toRFC4034DateString(self):
+ """
+ Calculate a date by treating the current L{SerialNumber} value as a UNIX
+ timestamp and return a date string in the format described in
+ U{RFC4034 3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.
+
+ @return: The date string.
+ """
+ # Can't use datetime.utcfromtimestamp, because it seems to overflow the
+ # signed 32bit int used in the underlying C library. SNA is unsigned
+ # and capable of handling all timestamps up to 2**32.
+ d = datetime(1970, 1, 1) + timedelta(seconds=self._number)
+ return nativeString(d.strftime(RFC4034_TIME_FORMAT))
+
+
+__all__ = ["SerialNumber"]
diff --git a/contrib/python/Twisted/py3/twisted/names/authority.py b/contrib/python/Twisted/py3/twisted/names/authority.py
new file mode 100644
index 0000000000..33df6c0068
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/authority.py
@@ -0,0 +1,503 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Authoritative resolvers.
+"""
+
+
+import os
+import time
+
+from twisted.internet import defer
+from twisted.names import common, dns, error
+from twisted.python import failure
+from twisted.python.compat import execfile, nativeString
+from twisted.python.filepath import FilePath
+
+
+def getSerial(filename="/tmp/twisted-names.serial"):
+ """
+ Return a monotonically increasing (across program runs) integer.
+
+ State is stored in the given file. If it does not exist, it is
+ created with rw-/---/--- permissions.
+
+ This manipulates process-global state by calling C{os.umask()}, so it isn't
+ thread-safe.
+
+ @param filename: Path to a file that is used to store the state across
+ program runs.
+ @type filename: L{str}
+
+ @return: a monotonically increasing number
+ @rtype: L{str}
+ """
+ serial = time.strftime("%Y%m%d")
+
+ o = os.umask(0o177)
+ try:
+ if not os.path.exists(filename):
+ with open(filename, "w") as f:
+ f.write(serial + " 0")
+ finally:
+ os.umask(o)
+
+ with open(filename) as serialFile:
+ lastSerial, zoneID = serialFile.readline().split()
+
+ zoneID = (lastSerial == serial) and (int(zoneID) + 1) or 0
+
+ with open(filename, "w") as serialFile:
+ serialFile.write("%s %d" % (serial, zoneID))
+
+ serial = serial + ("%02d" % (zoneID,))
+ return serial
+
+
+class FileAuthority(common.ResolverBase):
+ """
+ An Authority that is loaded from a file.
+
+ This is an abstract class that implements record search logic. To create
+ a functional resolver, subclass it and override the L{loadFile} method.
+
+ @ivar _ADDITIONAL_PROCESSING_TYPES: Record types for which additional
+ processing will be done.
+
+ @ivar _ADDRESS_TYPES: Record types which are useful for inclusion in the
+ additional section generated during additional processing.
+
+ @ivar soa: A 2-tuple containing the SOA domain name as a L{bytes} and a
+ L{dns.Record_SOA}.
+
+ @ivar records: A mapping of domains (as lowercased L{bytes}) to records.
+ @type records: L{dict} with L{bytes} keys
+ """
+
+ # See https://twistedmatrix.com/trac/ticket/6650
+ _ADDITIONAL_PROCESSING_TYPES = (dns.CNAME, dns.MX, dns.NS)
+ _ADDRESS_TYPES = (dns.A, dns.AAAA)
+
+ soa = None
+ records = None
+
+ def __init__(self, filename):
+ common.ResolverBase.__init__(self)
+ self.loadFile(filename)
+ self._cache = {}
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+ def loadFile(self, filename):
+ """
+ Load DNS records from a file.
+
+ This method populates the I{soa} and I{records} attributes. It must be
+ overridden in a subclass. It is called once from the initializer.
+
+ @param filename: The I{filename} parameter that was passed to the
+ initilizer.
+
+ @returns: L{None} -- the return value is ignored
+ """
+
+ def _additionalRecords(self, answer, authority, ttl):
+ """
+ Find locally known information that could be useful to the consumer of
+ the response and construct appropriate records to include in the
+ I{additional} section of that response.
+
+ Essentially, implement RFC 1034 section 4.3.2 step 6.
+
+ @param answer: A L{list} of the records which will be included in the
+ I{answer} section of the response.
+
+ @param authority: A L{list} of the records which will be included in
+ the I{authority} section of the response.
+
+ @param ttl: The default TTL for records for which this is not otherwise
+ specified.
+
+ @return: A generator of L{dns.RRHeader} instances for inclusion in the
+ I{additional} section. These instances represent extra information
+ about the records in C{answer} and C{authority}.
+ """
+ for record in answer + authority:
+ if record.type in self._ADDITIONAL_PROCESSING_TYPES:
+ name = record.payload.name.name
+ for rec in self.records.get(name.lower(), ()):
+ if rec.TYPE in self._ADDRESS_TYPES:
+ yield dns.RRHeader(
+ name, rec.TYPE, dns.IN, rec.ttl or ttl, rec, auth=True
+ )
+
+ def _lookup(self, name, cls, type, timeout=None):
+ """
+ Determine a response to a particular DNS query.
+
+ @param name: The name which is being queried and for which to lookup a
+ response.
+ @type name: L{bytes}
+
+ @param cls: The class which is being queried. Only I{IN} is
+ implemented here and this value is presently disregarded.
+ @type cls: L{int}
+
+ @param type: The type of records being queried. See the types defined
+ in L{twisted.names.dns}.
+ @type type: L{int}
+
+ @param timeout: All processing is done locally and a result is
+ available immediately, so the timeout value is ignored.
+
+ @return: A L{Deferred} that fires with a L{tuple} of three sets of
+ response records (to comprise the I{answer}, I{authority}, and
+ I{additional} sections of a DNS response) or with a L{Failure} if
+ there is a problem processing the query.
+ """
+ cnames = []
+ results = []
+ authority = []
+ additional = []
+ default_ttl = max(self.soa[1].minimum, self.soa[1].expire)
+
+ domain_records = self.records.get(name.lower())
+
+ if domain_records:
+ for record in domain_records:
+ if record.ttl is not None:
+ ttl = record.ttl
+ else:
+ ttl = default_ttl
+
+ if record.TYPE == dns.NS and name.lower() != self.soa[0].lower():
+ # NS record belong to a child zone: this is a referral. As
+ # NS records are authoritative in the child zone, ours here
+ # are not. RFC 2181, section 6.1.
+ authority.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=False)
+ )
+ elif record.TYPE == type or type == dns.ALL_RECORDS:
+ results.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True)
+ )
+ if record.TYPE == dns.CNAME:
+ cnames.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True)
+ )
+ if not results:
+ results = cnames
+
+ # Sort of https://tools.ietf.org/html/rfc1034#section-4.3.2 .
+ # See https://twistedmatrix.com/trac/ticket/6732
+ additionalInformation = self._additionalRecords(
+ results, authority, default_ttl
+ )
+ if cnames:
+ results.extend(additionalInformation)
+ else:
+ additional.extend(additionalInformation)
+
+ if not results and not authority:
+ # Empty response. Include SOA record to allow clients to cache
+ # this response. RFC 1034, sections 3.7 and 4.3.4, and RFC 2181
+ # section 7.1.
+ authority.append(
+ dns.RRHeader(
+ self.soa[0], dns.SOA, dns.IN, ttl, self.soa[1], auth=True
+ )
+ )
+ return defer.succeed((results, authority, additional))
+ else:
+ if dns._isSubdomainOf(name, self.soa[0]):
+ # We may be the authority and we didn't find it.
+ # XXX: The QNAME may also be in a delegated child zone. See
+ # #6581 and #6580
+ return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
+ else:
+ # The QNAME is not a descendant of this zone. Fail with
+ # DomainError so that the next chained authority or
+ # resolver will be queried.
+ return defer.fail(failure.Failure(error.DomainError(name)))
+
+ def lookupZone(self, name, timeout=10):
+ name = dns.domainString(name)
+ if self.soa[0].lower() == name.lower():
+ # Wee hee hee hooo yea
+ default_ttl = max(self.soa[1].minimum, self.soa[1].expire)
+ if self.soa[1].ttl is not None:
+ soa_ttl = self.soa[1].ttl
+ else:
+ soa_ttl = default_ttl
+ results = [
+ dns.RRHeader(
+ self.soa[0], dns.SOA, dns.IN, soa_ttl, self.soa[1], auth=True
+ )
+ ]
+ for k, r in self.records.items():
+ for rec in r:
+ if rec.ttl is not None:
+ ttl = rec.ttl
+ else:
+ ttl = default_ttl
+ if rec.TYPE != dns.SOA:
+ results.append(
+ dns.RRHeader(k, rec.TYPE, dns.IN, ttl, rec, auth=True)
+ )
+ results.append(results[0])
+ return defer.succeed((results, (), ()))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+ def _cbAllRecords(self, results):
+ ans, auth, add = [], [], []
+ for res in results:
+ if res[0]:
+ ans.extend(res[1][0])
+ auth.extend(res[1][1])
+ add.extend(res[1][2])
+ return ans, auth, add
+
+
+class PySourceAuthority(FileAuthority):
+ """
+ A FileAuthority that is built up from Python source code.
+ """
+
+ def loadFile(self, filename):
+ g, l = self.setupConfigNamespace(), {}
+ execfile(filename, g, l)
+ if "zone" not in l:
+ raise ValueError("No zone defined in " + filename)
+
+ self.records = {}
+ for rr in l["zone"]:
+ if isinstance(rr[1], dns.Record_SOA):
+ self.soa = rr
+ self.records.setdefault(rr[0].lower(), []).append(rr[1])
+
+ def wrapRecord(self, type):
+ def wrapRecordFunc(name, *arg, **kw):
+ return (dns.domainString(name), type(*arg, **kw))
+
+ return wrapRecordFunc
+
+ def setupConfigNamespace(self):
+ r = {}
+ items = dns.__dict__.keys()
+ for record in [x for x in items if x.startswith("Record_")]:
+ type = getattr(dns, record)
+ f = self.wrapRecord(type)
+ r[record[len("Record_") :]] = f
+ return r
+
+
+class BindAuthority(FileAuthority):
+ """
+ An Authority that loads U{BIND zone files
+ <https://en.wikipedia.org/wiki/Zone_file>}.
+
+ Supports only C{$ORIGIN} and C{$TTL} directives.
+ """
+
+ def loadFile(self, filename):
+ """
+ Load records from C{filename}.
+
+ @param filename: file to read from
+ @type filename: L{bytes}
+ """
+ fp = FilePath(filename)
+ # Not the best way to set an origin. It can be set using $ORIGIN
+ # though.
+ self.origin = nativeString(fp.basename() + b".")
+
+ lines = fp.getContent().splitlines(True)
+ lines = self.stripComments(lines)
+ lines = self.collapseContinuations(lines)
+ self.parseLines(lines)
+
+ def stripComments(self, lines):
+ """
+ Strip comments from C{lines}.
+
+ @param lines: lines to work on
+ @type lines: iterable of L{bytes}
+
+ @return: C{lines} sans comments.
+ """
+ return (
+ a.find(b";") == -1 and a or a[: a.find(b";")]
+ for a in [b.strip() for b in lines]
+ )
+
+ def collapseContinuations(self, lines):
+ """
+ Transform multiline statements into single lines.
+
+ @param lines: lines to work on
+ @type lines: iterable of L{bytes}
+
+ @return: iterable of continuous lines
+ """
+ l = []
+ state = 0
+ for line in lines:
+ if state == 0:
+ if line.find(b"(") == -1:
+ l.append(line)
+ else:
+ l.append(line[: line.find(b"(")])
+ state = 1
+ else:
+ if line.find(b")") != -1:
+ l[-1] += b" " + line[: line.find(b")")]
+ state = 0
+ else:
+ l[-1] += b" " + line
+ return filter(None, (line.split() for line in l))
+
+ def parseLines(self, lines):
+ """
+ Parse C{lines}.
+
+ @param lines: lines to work on
+ @type lines: iterable of L{bytes}
+ """
+ ttl = 60 * 60 * 3
+ origin = self.origin
+
+ self.records = {}
+
+ for line in lines:
+ if line[0] == b"$TTL":
+ ttl = dns.str2time(line[1])
+ elif line[0] == b"$ORIGIN":
+ origin = line[1]
+ elif line[0] == b"$INCLUDE":
+ raise NotImplementedError("$INCLUDE directive not implemented")
+ elif line[0] == b"$GENERATE":
+ raise NotImplementedError("$GENERATE directive not implemented")
+ else:
+ self.parseRecordLine(origin, ttl, line)
+
+ # If the origin changed, reflect that within the instance.
+ self.origin = origin
+
+ def addRecord(self, owner, ttl, type, domain, cls, rdata):
+ """
+ Add a record to our authority. Expand domain with origin if necessary.
+
+ @param owner: origin?
+ @type owner: L{bytes}
+
+ @param ttl: time to live for the record
+ @type ttl: L{int}
+
+ @param domain: the domain for which the record is to be added
+ @type domain: L{bytes}
+
+ @param type: record type
+ @type type: L{str}
+
+ @param cls: record class
+ @type cls: L{str}
+
+ @param rdata: record data
+ @type rdata: L{list} of L{bytes}
+ """
+ if not domain.endswith(b"."):
+ domain = domain + b"." + owner[:-1]
+ else:
+ domain = domain[:-1]
+ f = getattr(self, f"class_{cls}", None)
+ if f:
+ f(ttl, type, domain, rdata)
+ else:
+ raise NotImplementedError(f"Record class {cls!r} not supported")
+
+ def class_IN(self, ttl, type, domain, rdata):
+ """
+ Simulate a class IN and recurse into the actual class.
+
+ @param ttl: time to live for the record
+ @type ttl: L{int}
+
+ @param type: record type
+ @type type: str
+
+ @param domain: the domain
+ @type domain: bytes
+
+ @param rdata:
+ @type rdata: bytes
+ """
+ record = getattr(dns, f"Record_{nativeString(type)}", None)
+ if record:
+ r = record(*rdata)
+ r.ttl = ttl
+ self.records.setdefault(domain.lower(), []).append(r)
+
+ if type == "SOA":
+ self.soa = (domain, r)
+ else:
+ raise NotImplementedError(
+ f"Record type {nativeString(type)!r} not supported"
+ )
+
+ def parseRecordLine(self, origin, ttl, line):
+ """
+ Parse a C{line} from a zone file respecting C{origin} and C{ttl}.
+
+ Add resulting records to authority.
+
+ @param origin: starting point for the zone
+ @type origin: L{bytes}
+
+ @param ttl: time to live for the record
+ @type ttl: L{int}
+
+ @param line: zone file line to parse; split by word
+ @type line: L{list} of L{bytes}
+ """
+ queryClasses = {qc.encode("ascii") for qc in dns.QUERY_CLASSES.values()}
+ queryTypes = {qt.encode("ascii") for qt in dns.QUERY_TYPES.values()}
+
+ markers = queryClasses | queryTypes
+
+ cls = b"IN"
+ owner = origin
+
+ if line[0] == b"@":
+ line = line[1:]
+ owner = origin
+ elif not line[0].isdigit() and line[0] not in markers:
+ owner = line[0]
+ line = line[1:]
+
+ if line[0].isdigit() or line[0] in markers:
+ domain = owner
+ owner = origin
+ else:
+ domain = line[0]
+ line = line[1:]
+
+ if line[0] in queryClasses:
+ cls = line[0]
+ line = line[1:]
+ if line[0].isdigit():
+ ttl = int(line[0])
+ line = line[1:]
+ elif line[0].isdigit():
+ ttl = int(line[0])
+ line = line[1:]
+ if line[0] in queryClasses:
+ cls = line[0]
+ line = line[1:]
+
+ type = line[0]
+ rdata = line[1:]
+
+ self.addRecord(owner, ttl, nativeString(type), domain, nativeString(cls), rdata)
diff --git a/contrib/python/Twisted/py3/twisted/names/cache.py b/contrib/python/Twisted/py3/twisted/names/cache.py
new file mode 100644
index 0000000000..a3833d7ab1
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/cache.py
@@ -0,0 +1,131 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An in-memory caching resolver.
+"""
+
+
+from twisted.internet import defer
+from twisted.names import common, dns
+from twisted.python import failure, log
+
+
+class CacheResolver(common.ResolverBase):
+ """
+ A resolver that serves records from a local, memory cache.
+
+ @ivar _reactor: A provider of L{interfaces.IReactorTime}.
+ """
+
+ cache = None
+
+ def __init__(self, cache=None, verbose=0, reactor=None):
+ common.ResolverBase.__init__(self)
+
+ self.cache = {}
+ self.verbose = verbose
+ self.cancel = {}
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+ if cache:
+ for query, (seconds, payload) in cache.items():
+ self.cacheResult(query, payload, seconds)
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+ now = self._reactor.seconds()
+ for k, (when, (ans, add, ns)) in self.cache.items():
+ diff = now - when
+ for rec in ans + add + ns:
+ if rec.ttl < diff:
+ del self.cache[k]
+ break
+
+ def __getstate__(self):
+ for c in self.cancel.values():
+ c.cancel()
+ self.cancel.clear()
+ return self.__dict__
+
+ def _lookup(self, name, cls, type, timeout):
+ now = self._reactor.seconds()
+ q = dns.Query(name, type, cls)
+ try:
+ when, (ans, auth, add) = self.cache[q]
+ except KeyError:
+ if self.verbose > 1:
+ log.msg("Cache miss for " + repr(name))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+ else:
+ if self.verbose:
+ log.msg("Cache hit for " + repr(name))
+ diff = now - when
+
+ try:
+ result = (
+ [
+ dns.RRHeader(
+ r.name.name, r.type, r.cls, r.ttl - diff, r.payload
+ )
+ for r in ans
+ ],
+ [
+ dns.RRHeader(
+ r.name.name, r.type, r.cls, r.ttl - diff, r.payload
+ )
+ for r in auth
+ ],
+ [
+ dns.RRHeader(
+ r.name.name, r.type, r.cls, r.ttl - diff, r.payload
+ )
+ for r in add
+ ],
+ )
+ except ValueError:
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+ else:
+ return defer.succeed(result)
+
+ def lookupAllRecords(self, name, timeout=None):
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+ def cacheResult(self, query, payload, cacheTime=None):
+ """
+ Cache a DNS entry.
+
+ @param query: a L{dns.Query} instance.
+
+ @param payload: a 3-tuple of lists of L{dns.RRHeader} records, the
+ matching result of the query (answers, authority and additional).
+
+ @param cacheTime: The time (seconds since epoch) at which the entry is
+ considered to have been added to the cache. If L{None} is given,
+ the current time is used.
+ """
+ if self.verbose > 1:
+ log.msg("Adding %r to cache" % query)
+
+ self.cache[query] = (cacheTime or self._reactor.seconds(), payload)
+
+ if query in self.cancel:
+ self.cancel[query].cancel()
+
+ s = list(payload[0]) + list(payload[1]) + list(payload[2])
+ if s:
+ m = s[0].ttl
+ for r in s:
+ m = min(m, r.ttl)
+ else:
+ m = 0
+
+ self.cancel[query] = self._reactor.callLater(m, self.clearEntry, query)
+
+ def clearEntry(self, query):
+ del self.cache[query]
+ del self.cancel[query]
diff --git a/contrib/python/Twisted/py3/twisted/names/client.py b/contrib/python/Twisted/py3/twisted/names/client.py
new file mode 100644
index 0000000000..4052936ab9
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/client.py
@@ -0,0 +1,734 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Asynchronous client DNS
+
+The functions exposed in this module can be used for asynchronous name
+resolution and dns queries.
+
+If you need to create a resolver with specific requirements, such as needing to
+do queries against a particular host, the L{createResolver} function will
+return an C{IResolver}.
+
+Future plans: Proper nameserver acquisition on Windows/MacOS,
+better caching, respect timeouts
+"""
+
+import errno
+import os
+import warnings
+
+from zope.interface import moduleProvides
+
+from twisted.internet import defer, error, interfaces, protocol
+from twisted.internet.abstract import isIPv6Address
+from twisted.names import cache, common, dns, hosts as hostsModule, resolve, root
+from twisted.python import failure, log
+
+# Twisted imports
+from twisted.python.compat import nativeString
+from twisted.python.filepath import FilePath
+from twisted.python.runtime import platform
+
+moduleProvides(interfaces.IResolver)
+
+
+class Resolver(common.ResolverBase):
+ """
+ @ivar _waiting: A C{dict} mapping tuple keys of query name/type/class to
+ Deferreds which will be called back with the result of those queries.
+ This is used to avoid issuing the same query more than once in
+ parallel. This is more efficient on the network and helps avoid a
+ "birthday paradox" attack by keeping the number of outstanding requests
+ for a particular query fixed at one instead of allowing the attacker to
+ raise it to an arbitrary number.
+
+ @ivar _reactor: A provider of L{IReactorTCP}, L{IReactorUDP}, and
+ L{IReactorTime} which will be used to set up network resources and
+ track timeouts.
+ """
+
+ index = 0
+ timeout = None
+
+ factory = None
+ servers = None
+ dynServers = ()
+ pending = None
+ connections = None
+
+ resolv = None
+ _lastResolvTime = None
+ _resolvReadInterval = 60
+
+ def __init__(self, resolv=None, servers=None, timeout=(1, 3, 11, 45), reactor=None):
+ """
+ Construct a resolver which will query domain name servers listed in
+ the C{resolv.conf(5)}-format file given by C{resolv} as well as
+ those in the given C{servers} list. Servers are queried in a
+ round-robin fashion. If given, C{resolv} is periodically checked
+ for modification and re-parsed if it is noticed to have changed.
+
+ @type servers: C{list} of C{(str, int)} or L{None}
+ @param servers: If not None, interpreted as a list of (host, port)
+ pairs specifying addresses of domain name servers to attempt to use
+ for this lookup. Host addresses should be in IPv4 dotted-quad
+ form. If specified, overrides C{resolv}.
+
+ @type resolv: C{str}
+ @param resolv: Filename to read and parse as a resolver(5)
+ configuration file.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Default number of seconds after which to reissue the
+ query. When the last timeout expires, the query is considered
+ failed.
+
+ @param reactor: A provider of L{IReactorTime}, L{IReactorUDP}, and
+ L{IReactorTCP} which will be used to establish connections, listen
+ for DNS datagrams, and enforce timeouts. If not provided, the
+ global reactor will be used.
+
+ @raise ValueError: Raised if no nameserver addresses can be found.
+ """
+ common.ResolverBase.__init__(self)
+
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+ self.timeout = timeout
+
+ if servers is None:
+ self.servers = []
+ else:
+ self.servers = servers
+
+ self.resolv = resolv
+
+ if not len(self.servers) and not resolv:
+ raise ValueError("No nameservers specified")
+
+ self.factory = DNSClientFactory(self, timeout)
+ self.factory.noisy = 0 # Be quiet by default
+
+ self.connections = []
+ self.pending = []
+
+ self._waiting = {}
+
+ self.maybeParseConfig()
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d["connections"] = []
+ d["_parseCall"] = None
+ return d
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ self.maybeParseConfig()
+
+ def _openFile(self, path):
+ """
+ Wrapper used for opening files in the class, exists primarily for unit
+ testing purposes.
+ """
+ return FilePath(path).open()
+
+ def maybeParseConfig(self):
+ if self.resolv is None:
+ # Don't try to parse it, don't set up a call loop
+ return
+
+ try:
+ resolvConf = self._openFile(self.resolv)
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ # Missing resolv.conf is treated the same as an empty resolv.conf
+ self.parseConfig(())
+ else:
+ raise
+ else:
+ with resolvConf:
+ mtime = os.fstat(resolvConf.fileno()).st_mtime
+ if mtime != self._lastResolvTime:
+ log.msg(f"{self.resolv} changed, reparsing")
+ self._lastResolvTime = mtime
+ self.parseConfig(resolvConf)
+
+ # Check again in a little while
+ self._parseCall = self._reactor.callLater(
+ self._resolvReadInterval, self.maybeParseConfig
+ )
+
+ def parseConfig(self, resolvConf):
+ servers = []
+ for L in resolvConf:
+ L = L.strip()
+ if L.startswith(b"nameserver"):
+ resolver = (nativeString(L.split()[1]), dns.PORT)
+ servers.append(resolver)
+ log.msg(f"Resolver added {resolver!r} to server list")
+ elif L.startswith(b"domain"):
+ try:
+ self.domain = L.split()[1]
+ except IndexError:
+ self.domain = b""
+ self.search = None
+ elif L.startswith(b"search"):
+ self.search = L.split()[1:]
+ self.domain = None
+ if not servers:
+ servers.append(("127.0.0.1", dns.PORT))
+ self.dynServers = servers
+
+ def pickServer(self):
+ """
+ Return the address of a nameserver.
+
+ TODO: Weight servers for response time so faster ones can be
+ preferred.
+ """
+ if not self.servers and not self.dynServers:
+ return None
+ serverL = len(self.servers)
+ dynL = len(self.dynServers)
+
+ self.index += 1
+ self.index %= serverL + dynL
+ if self.index < serverL:
+ return self.servers[self.index]
+ else:
+ return self.dynServers[self.index - serverL]
+
+ def _connectedProtocol(self, interface=""):
+ """
+ Return a new L{DNSDatagramProtocol} bound to a randomly selected port
+ number.
+ """
+ failures = 0
+ proto = dns.DNSDatagramProtocol(self, reactor=self._reactor)
+
+ while True:
+ try:
+ self._reactor.listenUDP(dns.randomSource(), proto, interface=interface)
+ except error.CannotListenError as e:
+ failures += 1
+
+ if (
+ hasattr(e.socketError, "errno")
+ and e.socketError.errno == errno.EMFILE
+ ):
+ # We've run out of file descriptors. Stop trying.
+ raise
+
+ if failures >= 1000:
+ # We've tried a thousand times and haven't found a port.
+ # This is almost impossible, and likely means something
+ # else weird is going on. Raise, as to not infinite loop.
+ raise
+ else:
+ return proto
+
+ def connectionMade(self, protocol):
+ """
+ Called by associated L{dns.DNSProtocol} instances when they connect.
+ """
+ self.connections.append(protocol)
+ for d, q, t in self.pending:
+ self.queryTCP(q, t).chainDeferred(d)
+ del self.pending[:]
+
+ def connectionLost(self, protocol):
+ """
+ Called by associated L{dns.DNSProtocol} instances when they disconnect.
+ """
+ if protocol in self.connections:
+ self.connections.remove(protocol)
+
+ def messageReceived(self, message, protocol, address=None):
+ log.msg("Unexpected message (%d) received from %r" % (message.id, address))
+
+ def _query(self, *args):
+ """
+ Get a new L{DNSDatagramProtocol} instance from L{_connectedProtocol},
+ issue a query to it using C{*args}, and arrange for it to be
+ disconnected from its transport after the query completes.
+
+ @param args: Positional arguments to be passed to
+ L{DNSDatagramProtocol.query}.
+
+ @return: A L{Deferred} which will be called back with the result of the
+ query.
+ """
+ if isIPv6Address(args[0][0]):
+ protocol = self._connectedProtocol(interface="::")
+ else:
+ protocol = self._connectedProtocol()
+ d = protocol.query(*args)
+
+ def cbQueried(result):
+ protocol.transport.stopListening()
+ return result
+
+ d.addBoth(cbQueried)
+ return d
+
+ def queryUDP(self, queries, timeout=None):
+ """
+ Make a number of DNS queries via UDP.
+
+ @type queries: A C{list} of C{dns.Query} instances
+ @param queries: The queries to make.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ @raise C{twisted.internet.defer.TimeoutError}: When the query times
+ out.
+ """
+ if timeout is None:
+ timeout = self.timeout
+
+ addresses = self.servers + list(self.dynServers)
+ if not addresses:
+ return defer.fail(IOError("No domain name servers available"))
+
+ # Make sure we go through servers in the list in the order they were
+ # specified.
+ addresses.reverse()
+
+ used = addresses.pop()
+ d = self._query(used, queries, timeout[0])
+ d.addErrback(self._reissue, addresses, [used], queries, timeout)
+ return d
+
+ def _reissue(self, reason, addressesLeft, addressesUsed, query, timeout):
+ reason.trap(dns.DNSQueryTimeoutError)
+
+ # If there are no servers left to be tried, adjust the timeout
+ # to the next longest timeout period and move all the
+ # "used" addresses back to the list of addresses to try.
+ if not addressesLeft:
+ addressesLeft = addressesUsed
+ addressesLeft.reverse()
+ addressesUsed = []
+ timeout = timeout[1:]
+
+ # If all timeout values have been used this query has failed. Tell the
+ # protocol we're giving up on it and return a terminal timeout failure
+ # to our caller.
+ if not timeout:
+ return failure.Failure(defer.TimeoutError(query))
+
+ # Get an address to try. Take it out of the list of addresses
+ # to try and put it ino the list of already tried addresses.
+ address = addressesLeft.pop()
+ addressesUsed.append(address)
+
+ # Issue a query to a server. Use the current timeout. Add this
+ # function as a timeout errback in case another retry is required.
+ d = self._query(address, query, timeout[0], reason.value.id)
+ d.addErrback(self._reissue, addressesLeft, addressesUsed, query, timeout)
+ return d
+
+ def queryTCP(self, queries, timeout=10):
+ """
+ Make a number of DNS queries via TCP.
+
+ @type queries: Any non-zero number of C{dns.Query} instances
+ @param queries: The queries to make.
+
+ @type timeout: C{int}
+ @param timeout: The number of seconds after which to fail.
+
+ @rtype: C{Deferred}
+ """
+ if not len(self.connections):
+ address = self.pickServer()
+ if address is None:
+ return defer.fail(IOError("No domain name servers available"))
+ host, port = address
+ self._reactor.connectTCP(host, port, self.factory)
+ self.pending.append((defer.Deferred(), queries, timeout))
+ return self.pending[-1][0]
+ else:
+ return self.connections[0].query(queries, timeout)
+
+ def filterAnswers(self, message):
+ """
+ Extract results from the given message.
+
+ If the message was truncated, re-attempt the query over TCP and return
+ a Deferred which will fire with the results of that query.
+
+ If the message's result code is not C{twisted.names.dns.OK}, return a
+ Failure indicating the type of error which occurred.
+
+ Otherwise, return a three-tuple of lists containing the results from
+ the answers section, the authority section, and the additional section.
+ """
+ if message.trunc:
+ return self.queryTCP(message.queries).addCallback(self.filterAnswers)
+ if message.rCode != dns.OK:
+ return failure.Failure(self.exceptionForCode(message.rCode)(message))
+ return (message.answers, message.authority, message.additional)
+
+ def _lookup(self, name, cls, type, timeout):
+ """
+ Build a L{dns.Query} for the given parameters and dispatch it via UDP.
+
+ If this query is already outstanding, it will not be re-issued.
+ Instead, when the outstanding query receives a response, that response
+ will be re-used for this query as well.
+
+ @type name: C{str}
+ @type type: C{int}
+ @type cls: C{int}
+
+ @return: A L{Deferred} which fires with a three-tuple giving the
+ answer, authority, and additional sections of the response or with
+ a L{Failure} if the response code is anything other than C{dns.OK}.
+ """
+ key = (name, type, cls)
+ waiting = self._waiting.get(key)
+ if waiting is None:
+ self._waiting[key] = []
+ d = self.queryUDP([dns.Query(name, type, cls)], timeout)
+
+ def cbResult(result):
+ for d in self._waiting.pop(key):
+ d.callback(result)
+ return result
+
+ d.addCallback(self.filterAnswers)
+ d.addBoth(cbResult)
+ else:
+ d = defer.Deferred()
+ waiting.append(d)
+ return d
+
+ # This one doesn't ever belong on UDP
+ def lookupZone(self, name, timeout=10):
+ address = self.pickServer()
+ if address is None:
+ return defer.fail(IOError("No domain name servers available"))
+ host, port = address
+ d = defer.Deferred()
+ controller = AXFRController(name, d)
+ factory = DNSClientFactory(controller, timeout)
+ factory.noisy = False # stfu
+
+ connector = self._reactor.connectTCP(host, port, factory)
+ controller.timeoutCall = self._reactor.callLater(
+ timeout or 10, self._timeoutZone, d, controller, connector, timeout or 10
+ )
+
+ def eliminateTimeout(failure):
+ controller.timeoutCall.cancel()
+ controller.timeoutCall = None
+ return failure
+
+ return d.addCallbacks(
+ self._cbLookupZone, eliminateTimeout, callbackArgs=(connector,)
+ )
+
+ def _timeoutZone(self, d, controller, connector, seconds):
+ connector.disconnect()
+ controller.timeoutCall = None
+ controller.deferred = None
+ d.errback(
+ error.TimeoutError("Zone lookup timed out after %d seconds" % (seconds,))
+ )
+
+ def _cbLookupZone(self, result, connector):
+ connector.disconnect()
+ return (result, [], [])
+
+
+class AXFRController:
+ timeoutCall = None
+
+ def __init__(self, name, deferred):
+ self.name = name
+ self.deferred = deferred
+ self.soa = None
+ self.records = []
+ self.pending = [(deferred,)]
+
+ def connectionMade(self, protocol):
+ # dig saids recursion-desired to 0, so I will too
+ message = dns.Message(protocol.pickID(), recDes=0)
+ message.queries = [dns.Query(self.name, dns.AXFR, dns.IN)]
+ protocol.writeMessage(message)
+
+ def connectionLost(self, protocol):
+ # XXX Do something here - see #3428
+ pass
+
+ def messageReceived(self, message, protocol):
+ # Caveat: We have to handle two cases: All records are in 1
+ # message, or all records are in N messages.
+
+ # According to http://cr.yp.to/djbdns/axfr-notes.html,
+ # 'authority' and 'additional' are always empty, and only
+ # 'answers' is present.
+ self.records.extend(message.answers)
+ if not self.records:
+ return
+ if not self.soa:
+ if self.records[0].type == dns.SOA:
+ # print "first SOA!"
+ self.soa = self.records[0]
+ if len(self.records) > 1 and self.records[-1].type == dns.SOA:
+ # print "It's the second SOA! We're done."
+ if self.timeoutCall is not None:
+ self.timeoutCall.cancel()
+ self.timeoutCall = None
+ if self.deferred is not None:
+ self.deferred.callback(self.records)
+ self.deferred = None
+
+
+from twisted.internet.base import ThreadedResolver as _ThreadedResolverImpl
+
+
+class ThreadedResolver(_ThreadedResolverImpl):
+ def __init__(self, reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+ _ThreadedResolverImpl.__init__(self, reactor)
+ warnings.warn(
+ "twisted.names.client.ThreadedResolver is deprecated since "
+ "Twisted 9.0, use twisted.internet.base.ThreadedResolver "
+ "instead.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+
+
+class DNSClientFactory(protocol.ClientFactory):
+ def __init__(self, controller, timeout=10):
+ self.controller = controller
+ self.timeout = timeout
+
+ def clientConnectionLost(self, connector, reason):
+ pass
+
+ def clientConnectionFailed(self, connector, reason):
+ """
+ Fail all pending TCP DNS queries if the TCP connection attempt
+ fails.
+
+ @see: L{twisted.internet.protocol.ClientFactory}
+
+ @param connector: Not used.
+ @type connector: L{twisted.internet.interfaces.IConnector}
+
+ @param reason: A C{Failure} containing information about the
+ cause of the connection failure. This will be passed as the
+ argument to C{errback} on every pending TCP query
+ C{deferred}.
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ # Copy the current pending deferreds then reset the master
+ # pending list. This prevents triggering new deferreds which
+ # may be added by callback or errback functions on the current
+ # deferreds.
+ pending = self.controller.pending[:]
+ del self.controller.pending[:]
+ for pendingState in pending:
+ d = pendingState[0]
+ d.errback(reason)
+
+ def buildProtocol(self, addr):
+ p = dns.DNSProtocol(self.controller)
+ p.factory = self
+ return p
+
+
+def createResolver(servers=None, resolvconf=None, hosts=None):
+ r"""
+ Create and return a Resolver.
+
+ @type servers: C{list} of C{(str, int)} or L{None}
+
+ @param servers: If not L{None}, interpreted as a list of domain name servers
+ to attempt to use. Each server is a tuple of address in C{str} dotted-quad
+ form and C{int} port number.
+
+ @type resolvconf: C{str} or L{None}
+ @param resolvconf: If not L{None}, on posix systems will be interpreted as
+ an alternate resolv.conf to use. Will do nothing on windows systems. If
+ L{None}, /etc/resolv.conf will be used.
+
+ @type hosts: C{str} or L{None}
+ @param hosts: If not L{None}, an alternate hosts file to use. If L{None}
+ on posix systems, /etc/hosts will be used. On windows, C:\windows\hosts
+ will be used.
+
+ @rtype: C{IResolver}
+ """
+ if platform.getType() == "posix":
+ if resolvconf is None:
+ resolvconf = b"/etc/resolv.conf"
+ if hosts is None:
+ hosts = b"/etc/hosts"
+ theResolver = Resolver(resolvconf, servers)
+ hostResolver = hostsModule.Resolver(hosts)
+ else:
+ if hosts is None:
+ hosts = r"c:\windows\hosts"
+ from twisted.internet import reactor
+
+ bootstrap = _ThreadedResolverImpl(reactor)
+ hostResolver = hostsModule.Resolver(hosts)
+ theResolver = root.bootstrap(bootstrap, resolverFactory=Resolver)
+
+ L = [hostResolver, cache.CacheResolver(), theResolver]
+ return resolve.ResolverChain(L)
+
+
+theResolver = None
+
+
+def getResolver():
+ """
+ Get a Resolver instance.
+
+ Create twisted.names.client.theResolver if it is L{None}, and then return
+ that value.
+
+ @rtype: C{IResolver}
+ """
+ global theResolver
+ if theResolver is None:
+ try:
+ theResolver = createResolver()
+ except ValueError:
+ theResolver = createResolver(servers=[("127.0.0.1", 53)])
+ return theResolver
+
+
+def getHostByName(name, timeout=None, effort=10):
+ """
+ Resolve a name to a valid ipv4 or ipv6 address.
+
+ Will errback with C{DNSQueryTimeoutError} on a timeout, C{DomainError} or
+ C{AuthoritativeDomainError} (or subclasses) on other errors.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @type effort: C{int}
+ @param effort: How many times CNAME and NS records to follow while
+ resolving this name.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().getHostByName(name, timeout, effort)
+
+
+def query(query, timeout=None):
+ return getResolver().query(query, timeout)
+
+
+def lookupAddress(name, timeout=None):
+ return getResolver().lookupAddress(name, timeout)
+
+
+def lookupIPV6Address(name, timeout=None):
+ return getResolver().lookupIPV6Address(name, timeout)
+
+
+def lookupAddress6(name, timeout=None):
+ return getResolver().lookupAddress6(name, timeout)
+
+
+def lookupMailExchange(name, timeout=None):
+ return getResolver().lookupMailExchange(name, timeout)
+
+
+def lookupNameservers(name, timeout=None):
+ return getResolver().lookupNameservers(name, timeout)
+
+
+def lookupCanonicalName(name, timeout=None):
+ return getResolver().lookupCanonicalName(name, timeout)
+
+
+def lookupMailBox(name, timeout=None):
+ return getResolver().lookupMailBox(name, timeout)
+
+
+def lookupMailGroup(name, timeout=None):
+ return getResolver().lookupMailGroup(name, timeout)
+
+
+def lookupMailRename(name, timeout=None):
+ return getResolver().lookupMailRename(name, timeout)
+
+
+def lookupPointer(name, timeout=None):
+ return getResolver().lookupPointer(name, timeout)
+
+
+def lookupAuthority(name, timeout=None):
+ return getResolver().lookupAuthority(name, timeout)
+
+
+def lookupNull(name, timeout=None):
+ return getResolver().lookupNull(name, timeout)
+
+
+def lookupWellKnownServices(name, timeout=None):
+ return getResolver().lookupWellKnownServices(name, timeout)
+
+
+def lookupService(name, timeout=None):
+ return getResolver().lookupService(name, timeout)
+
+
+def lookupHostInfo(name, timeout=None):
+ return getResolver().lookupHostInfo(name, timeout)
+
+
+def lookupMailboxInfo(name, timeout=None):
+ return getResolver().lookupMailboxInfo(name, timeout)
+
+
+def lookupText(name, timeout=None):
+ return getResolver().lookupText(name, timeout)
+
+
+def lookupSenderPolicy(name, timeout=None):
+ return getResolver().lookupSenderPolicy(name, timeout)
+
+
+def lookupResponsibility(name, timeout=None):
+ return getResolver().lookupResponsibility(name, timeout)
+
+
+def lookupAFSDatabase(name, timeout=None):
+ return getResolver().lookupAFSDatabase(name, timeout)
+
+
+def lookupZone(name, timeout=None):
+ return getResolver().lookupZone(name, timeout)
+
+
+def lookupAllRecords(name, timeout=None):
+ return getResolver().lookupAllRecords(name, timeout)
+
+
+def lookupNamingAuthorityPointer(name, timeout=None):
+ return getResolver().lookupNamingAuthorityPointer(name, timeout)
diff --git a/contrib/python/Twisted/py3/twisted/names/common.py b/contrib/python/Twisted/py3/twisted/names/common.py
new file mode 100644
index 0000000000..ee64b451f7
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/common.py
@@ -0,0 +1,263 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Base functionality useful to various parts of Twisted Names.
+"""
+
+
+import socket
+
+from zope.interface import implementer
+
+from twisted.internet import defer, error, interfaces
+from twisted.logger import Logger
+from twisted.names import dns
+from twisted.names.error import (
+ DNSFormatError,
+ DNSNameError,
+ DNSNotImplementedError,
+ DNSQueryRefusedError,
+ DNSServerError,
+ DNSUnknownError,
+)
+
+# Helpers for indexing the three-tuples that get thrown around by this code a
+# lot.
+_ANS, _AUTH, _ADD = range(3)
+
+EMPTY_RESULT = (), (), ()
+
+
+@implementer(interfaces.IResolver)
+class ResolverBase:
+ """
+ L{ResolverBase} is a base class for implementations of
+ L{interfaces.IResolver} which deals with a lot
+ of the boilerplate of implementing all of the lookup methods.
+
+ @cvar _errormap: A C{dict} mapping DNS protocol failure response codes
+ to exception classes which will be used to represent those failures.
+ """
+
+ _log = Logger()
+ _errormap = {
+ dns.EFORMAT: DNSFormatError,
+ dns.ESERVER: DNSServerError,
+ dns.ENAME: DNSNameError,
+ dns.ENOTIMP: DNSNotImplementedError,
+ dns.EREFUSED: DNSQueryRefusedError,
+ }
+
+ typeToMethod = None
+
+ def __init__(self):
+ self.typeToMethod = {}
+ for k, v in typeToMethod.items():
+ self.typeToMethod[k] = getattr(self, v)
+
+ def exceptionForCode(self, responseCode):
+ """
+ Convert a response code (one of the possible values of
+ L{dns.Message.rCode} to an exception instance representing it.
+
+ @since: 10.0
+ """
+ return self._errormap.get(responseCode, DNSUnknownError)
+
+ def query(self, query, timeout=None):
+ try:
+ method = self.typeToMethod[query.type]
+ except KeyError:
+ self._log.debug(
+ "Query of unknown type {query.type} for {query.name.name!r}",
+ query=query,
+ )
+ return defer.maybeDeferred(
+ self._lookup, query.name.name, dns.IN, query.type, timeout
+ )
+ else:
+ return defer.maybeDeferred(method, query.name.name, timeout)
+
+ def _lookup(self, name, cls, type, timeout):
+ return defer.fail(NotImplementedError("ResolverBase._lookup"))
+
+ def lookupAddress(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.A, timeout)
+
+ def lookupIPV6Address(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.AAAA, timeout)
+
+ def lookupAddress6(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.A6, timeout)
+
+ def lookupMailExchange(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.MX, timeout)
+
+ def lookupNameservers(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.NS, timeout)
+
+ def lookupCanonicalName(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.CNAME, timeout)
+
+ def lookupMailBox(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.MB, timeout)
+
+ def lookupMailGroup(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.MG, timeout)
+
+ def lookupMailRename(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.MR, timeout)
+
+ def lookupPointer(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.PTR, timeout)
+
+ def lookupAuthority(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.SOA, timeout)
+
+ def lookupNull(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.NULL, timeout)
+
+ def lookupWellKnownServices(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.WKS, timeout)
+
+ def lookupService(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.SRV, timeout)
+
+ def lookupHostInfo(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.HINFO, timeout)
+
+ def lookupMailboxInfo(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.MINFO, timeout)
+
+ def lookupText(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.TXT, timeout)
+
+ def lookupSenderPolicy(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.SPF, timeout)
+
+ def lookupResponsibility(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.RP, timeout)
+
+ def lookupAFSDatabase(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.AFSDB, timeout)
+
+ def lookupZone(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.AXFR, timeout)
+
+ def lookupNamingAuthorityPointer(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.NAPTR, timeout)
+
+ def lookupAllRecords(self, name, timeout=None):
+ return self._lookup(dns.domainString(name), dns.IN, dns.ALL_RECORDS, timeout)
+
+ # IResolverSimple
+ def getHostByName(self, name, timeout=None, effort=10):
+ name = dns.domainString(name)
+ # XXX - respect timeout
+ # XXX - this should do A and AAAA lookups, not ANY (see RFC 8482).
+ # https://twistedmatrix.com/trac/ticket/9691
+ d = self.lookupAllRecords(name, timeout)
+ d.addCallback(self._cbRecords, name, effort)
+ return d
+
+ def _cbRecords(self, records, name, effort):
+ (ans, auth, add) = records
+ result = extractRecord(self, dns.Name(name), ans + auth + add, effort)
+ if not result:
+ raise error.DNSLookupError(name)
+ return result
+
+
+def extractRecord(resolver, name, answers, level=10):
+ """
+ Resolve a name to an IP address, following I{CNAME} records and I{NS}
+ referrals recursively.
+
+ This is an implementation detail of L{ResolverBase.getHostByName}.
+
+ @param resolver: The resolver to use for the next query (unless handling
+ an I{NS} referral).
+ @type resolver: L{IResolver}
+
+ @param name: The name being looked up.
+ @type name: L{dns.Name}
+
+ @param answers: All of the records returned by the previous query (answers,
+ authority, and additional concatenated).
+ @type answers: L{list} of L{dns.RRHeader}
+
+ @param level: Remaining recursion budget. This is decremented at each
+ recursion. The query returns L{None} when it reaches 0.
+ @type level: L{int}
+
+ @returns: The first IPv4 or IPv6 address (as a dotted quad or colon
+ quibbles), or L{None} when no result is found.
+ @rtype: native L{str} or L{None}
+ """
+ if not level:
+ return None
+ # FIXME: twisted.python.compat monkeypatches this if missing, so this
+ # condition is always true. https://twistedmatrix.com/trac/ticket/9753
+ if hasattr(socket, "inet_ntop"):
+ for r in answers:
+ if r.name == name and r.type == dns.A6:
+ return socket.inet_ntop(socket.AF_INET6, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.AAAA:
+ return socket.inet_ntop(socket.AF_INET6, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.A:
+ return socket.inet_ntop(socket.AF_INET, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.CNAME:
+ result = extractRecord(resolver, r.payload.name, answers, level - 1)
+ if not result:
+ return resolver.getHostByName(r.payload.name.name, effort=level - 1)
+ return result
+ # No answers, but maybe there's a hint at who we should be asking about
+ # this
+ for r in answers:
+ if r.type != dns.NS:
+ continue
+ from twisted.names import client
+
+ nsResolver = client.Resolver(
+ servers=[
+ (r.payload.name.name.decode("ascii"), dns.PORT),
+ ]
+ )
+
+ def queryAgain(records):
+ (ans, auth, add) = records
+ return extractRecord(nsResolver, name, ans + auth + add, level - 1)
+
+ return nsResolver.lookupAddress(name.name).addCallback(queryAgain)
+
+
+typeToMethod = {
+ dns.A: "lookupAddress",
+ dns.AAAA: "lookupIPV6Address",
+ dns.A6: "lookupAddress6",
+ dns.NS: "lookupNameservers",
+ dns.CNAME: "lookupCanonicalName",
+ dns.SOA: "lookupAuthority",
+ dns.MB: "lookupMailBox",
+ dns.MG: "lookupMailGroup",
+ dns.MR: "lookupMailRename",
+ dns.NULL: "lookupNull",
+ dns.WKS: "lookupWellKnownServices",
+ dns.PTR: "lookupPointer",
+ dns.HINFO: "lookupHostInfo",
+ dns.MINFO: "lookupMailboxInfo",
+ dns.MX: "lookupMailExchange",
+ dns.TXT: "lookupText",
+ dns.SPF: "lookupSenderPolicy",
+ dns.RP: "lookupResponsibility",
+ dns.AFSDB: "lookupAFSDatabase",
+ dns.SRV: "lookupService",
+ dns.NAPTR: "lookupNamingAuthorityPointer",
+ dns.AXFR: "lookupZone",
+ dns.ALL_RECORDS: "lookupAllRecords",
+}
diff --git a/contrib/python/Twisted/py3/twisted/names/dns.py b/contrib/python/Twisted/py3/twisted/names/dns.py
new file mode 100644
index 0000000000..c7644ef50d
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/dns.py
@@ -0,0 +1,3390 @@
+# -*- 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 annotations
+
+# System imports
+import inspect
+import random
+import socket
+import struct
+from io import BytesIO
+from itertools import chain
+from typing import Optional, Sequence, SupportsInt, Union, overload
+
+from zope.interface import Attribute, Interface, implementer
+
+# Twisted imports
+from twisted.internet import defer, protocol
+from twisted.internet.error import CannotListenError
+from twisted.python import failure, log, randbytes, util as tputil
+from twisted.python.compat import cmp, comparable, nativeString
+
+__all__ = [
+ "IEncodable",
+ "IRecord",
+ "IEncodableRecord",
+ "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",
+]
+
+
+AF_INET6 = socket.AF_INET6
+
+
+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 "[{}]".format(", ".join([_nicebytes(b) for b in list]))
+
+
+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 = {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 = {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 (
+ AuthoritativeDomainError,
+ DNSQueryTimeoutError,
+ DomainError,
+)
+
+
+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: str | bytes) -> bytes:
+ """
+ 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, str):
+ domain = domain.encode("idna")
+ if not isinstance(domain, bytes):
+ raise TypeError(
+ "Expected {} or {} but found {!r} of type {}".format(
+ bytes.__name__, str.__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: str) -> int:
+ """
+ mypy doesn't like type-punning str | bytes | int | None into a str so we have this helper function.
+ """
+ suffixes = (
+ ("S", 1),
+ ("M", 60),
+ ("H", 60 * 60),
+ ("D", 60 * 60 * 24),
+ ("W", 60 * 60 * 24 * 7),
+ ("Y", 60 * 60 * 24 * 365),
+ )
+ s = s.upper().strip()
+ for suff, mult in suffixes:
+ if s.endswith(suff):
+ return int(float(s[:-1]) * mult)
+ try:
+ return int(s)
+ except ValueError:
+ raise ValueError("Invalid time interval specifier: " + s)
+
+
+@overload
+def str2time(s: Union[str, bytes, int]) -> int:
+ ...
+
+
+@overload
+def str2time(s: None) -> None:
+ ...
+
+
+def str2time(s: Union[str, bytes, int, None]) -> Union[int, None]:
+ """
+ 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{str}) 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.
+ """
+ if isinstance(s, bytes):
+ return _str2time(s.decode("ascii"))
+
+ if isinstance(s, str):
+ return _str2time(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.
+ """
+
+
+class IEncodableRecord(IEncodable, IRecord):
+ """
+ Interface for DNS records that can be encoded and decoded.
+
+ @since: Twisted 21.2.0
+ """
+
+
+@implementer(IEncodable)
+class Charstr:
+ def __init__(self, string: bytes = b""):
+ if not isinstance(string, bytes):
+ raise ValueError(f"{string!r} is not a byte 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: object) -> bool:
+ if isinstance(other, Charstr):
+ return self.string == other.string
+ return NotImplemented
+
+ def __hash__(self):
+ return hash(self.string)
+
+ def __str__(self) -> str:
+ """
+ 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: bytes | str = 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: object) -> bool:
+ if isinstance(other, Name):
+ return self.name.lower() == other.name.lower()
+ return NotImplemented
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def __str__(self) -> str:
+ """
+ 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}
+ """
+
+ def __init__(self, name: Union[bytes, str] = b"", type: int = A, cls: int = IN):
+ """
+ @type name: L{bytes} or L{str}
+ @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) -> str:
+ 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 f"<Query {self.name} {t} {c}>"
+
+ def __repr__(self) -> str:
+ return f"Query({self.name.name!r}, {self.type!r}, {self.cls!r})"
+
+
+@implementer(IEncodable)
+class _OPTHeader(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ 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 udpPayloadSize: 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: 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: 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):
+ """
+ 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: 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: 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: The record described by this header.
+ @type payload: L{IEncodableRecord} or L{None}
+
+ @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"
+
+ rdlength = None
+
+ cachedResponse = None
+
+ def __init__(
+ self,
+ name: Union[bytes, str] = b"",
+ type: int = A,
+ cls: int = IN,
+ ttl: SupportsInt = 0,
+ payload: Optional[IEncodableRecord] = None,
+ auth: bool = 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: L{IEncodableRecord} or L{None}
+ @param payload: An optional Query Type specific data object.
+
+ @raises TypeError: if the ttl cannot be converted to an L{int}.
+ @raises ValueError: if the ttl is negative.
+ @raises ValueError: if the payload type is not equal to the C{type}
+ argument.
+ """
+ payloadType = None if payload is None else payload.TYPE
+ if payloadType is not None and payloadType != type:
+ raise ValueError(
+ "Payload type (%s) does not match given type (%s)"
+ % (
+ QUERY_TYPES.get(payloadType, payloadType),
+ QUERY_TYPES.get(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) -> str:
+ 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(IEncodableRecord)
+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: Optional[int] = 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(IEncodableRecord)
+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{str}
+ @param address: The IPv4 address associated with this record, in
+ quad-dotted notation.
+ """
+ if 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) -> str:
+ return f"<A address={self.dottedQuad()} ttl={self.ttl}>"
+
+ __repr__ = __str__
+
+ def dottedQuad(self):
+ return socket.inet_ntoa(self.address)
+
+
+@implementer(IEncodableRecord)
+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{str}
+
+ @param rname: See L{Record_SOA.rname}
+ @type rname: L{bytes} or L{str}
+ """
+ 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(IEncodableRecord)
+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(IEncodableRecord)
+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
+
+ @property
+ def _address(self):
+ return 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{str}
+ @param address: The IPv4 address associated with this record, in
+ quad-dotted notation.
+ """
+ if 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(IEncodableRecord)
+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")
+
+ @property
+ def _address(self):
+ return socket.inet_ntop(AF_INET6, self.address)
+
+ def __init__(self, address="::", ttl=None):
+ """
+ @type address: L{bytes} or L{str}
+ @param address: The IPv6 address for this host, in RFC 2373 format.
+ """
+ if 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(IEncodableRecord)
+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")
+
+ @property
+ def _suffix(self):
+ return socket.inet_ntop(AF_INET6, self.suffix)
+
+ def __init__(
+ self,
+ prefixLen: int = 0,
+ suffix: bytes | str = "::",
+ prefix: bytes | str = b"",
+ ttl: Union[str, bytes, int, None] = None,
+ ):
+ """
+ @param suffix: An IPv6 address suffix in in RFC 2373 format.
+ @type suffix: L{bytes} or L{str}
+
+ @param prefix: An IPv6 address prefix for other A6 records.
+ @type prefix: L{bytes} or L{str}
+ """
+ if 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: object) -> bool:
+ 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) -> str:
+ return "<A6 %s %s (%d) ttl=%s>" % (
+ self.prefix,
+ socket.inet_ntop(AF_INET6, self.suffix),
+ self.prefixLen,
+ self.ttl,
+ )
+
+
+@implementer(IEncodableRecord)
+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{str}
+ """
+ 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(IEncodableRecord)
+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{str}
+ """
+ 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(IEncodableRecord)
+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{str}
+ """
+ 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(IEncodableRecord)
+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{str}
+
+ @param txt: See L{Record_RP.txt}
+ @type txt: L{bytes} or L{str}
+ """
+ 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(IEncodableRecord)
+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: bytes = b"",
+ os: bytes = b"",
+ ttl: Union[str, bytes, int, None] = 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: object) -> bool:
+ 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(IEncodableRecord)
+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{str}
+
+ @param emailbx: See L{Record_MINFO.rmailbx}.
+ @type emailbx: L{bytes} or L{str}
+ """
+ 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(IEncodableRecord)
+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{str}
+ """
+ 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(IEncodableRecord)
+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(IEncodableRecord)
+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(IEncodableRecord)
+class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ 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
+ """
+
+ TYPE = None
+
+ 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(IEncodableRecord)
+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 = []
+ # 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(f" {name}={fieldValue!r}")
+
+ return displayableArgs
+
+
+def _compactRepr(
+ obj: object,
+ alwaysShow: Sequence[str] | None = None,
+ flagNames: Sequence[str] | None = None,
+ fieldNames: Sequence[str] | None = None,
+ sectionNames: Sequence[str] | None = None,
+) -> str:
+ """
+ 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={}".format(",".join(setFlags)))
+
+ for name in sectionNames:
+ section = getattr(obj, name, [])
+ if section:
+ out.append(f" {name}={section!r}")
+
+ 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) -> str:
+ """
+ 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: C{Type[IRecord]}
+ """
+ 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):
+ """
+ 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) -> str:
+ 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:
+ """
+ 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 BaseException:
+ 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 ValueError as ex:
+ log.msg(f"Invalid packet ({ex}) from {addr}")
+ return
+ except BaseException:
+ # 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 BaseException:
+ 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 BaseException:
+ 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)
diff --git a/contrib/python/Twisted/py3/twisted/names/error.py b/contrib/python/Twisted/py3/twisted/names/error.py
new file mode 100644
index 0000000000..185c804472
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/error.py
@@ -0,0 +1,94 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Exception class definitions for Twisted Names.
+"""
+
+
+from twisted.internet.defer import TimeoutError
+
+
+class DomainError(ValueError):
+ """
+ Indicates a lookup failed because there were no records matching the given
+ C{name, class, type} triple.
+ """
+
+
+class AuthoritativeDomainError(ValueError):
+ """
+ Indicates a lookup failed for a name for which this server is authoritative
+ because there were no records matching the given C{name, class, type}
+ triple.
+ """
+
+
+class DNSQueryTimeoutError(TimeoutError):
+ """
+ Indicates a lookup failed due to a timeout.
+
+ @ivar id: The id of the message which timed out.
+ """
+
+ def __init__(self, id):
+ TimeoutError.__init__(self)
+ self.id = id
+
+
+class DNSFormatError(DomainError):
+ """
+ Indicates a query failed with a result of C{twisted.names.dns.EFORMAT}.
+ """
+
+
+class DNSServerError(DomainError):
+ """
+ Indicates a query failed with a result of C{twisted.names.dns.ESERVER}.
+ """
+
+
+class DNSNameError(DomainError):
+ """
+ Indicates a query failed with a result of C{twisted.names.dns.ENAME}.
+ """
+
+
+class DNSNotImplementedError(DomainError):
+ """
+ Indicates a query failed with a result of C{twisted.names.dns.ENOTIMP}.
+ """
+
+
+class DNSQueryRefusedError(DomainError):
+ """
+ Indicates a query failed with a result of C{twisted.names.dns.EREFUSED}.
+ """
+
+
+class DNSUnknownError(DomainError):
+ """
+ Indicates a query failed with an unknown result.
+ """
+
+
+class ResolverError(Exception):
+ """
+ Indicates a query failed because of a decision made by the local
+ resolver object.
+ """
+
+
+__all__ = [
+ "DomainError",
+ "AuthoritativeDomainError",
+ "DNSQueryTimeoutError",
+ "DNSFormatError",
+ "DNSServerError",
+ "DNSNameError",
+ "DNSNotImplementedError",
+ "DNSQueryRefusedError",
+ "DNSUnknownError",
+ "ResolverError",
+]
diff --git a/contrib/python/Twisted/py3/twisted/names/hosts.py b/contrib/python/Twisted/py3/twisted/names/hosts.py
new file mode 100644
index 0000000000..7d77aa4521
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/hosts.py
@@ -0,0 +1,151 @@
+# -*- test-case-name: twisted.names.test.test_hosts -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+hosts(5) support.
+"""
+
+
+from twisted.internet import defer
+from twisted.internet.abstract import isIPAddress, isIPv6Address
+from twisted.names import common, dns
+from twisted.python import failure
+from twisted.python.compat import nativeString
+from twisted.python.filepath import FilePath
+
+
+def searchFileForAll(hostsFile, name):
+ """
+ Search the given file, which is in hosts(5) standard format, for addresses
+ associated with a given name.
+
+ @param hostsFile: The name of the hosts(5)-format file to search.
+ @type hostsFile: L{FilePath}
+
+ @param name: The name to search for.
+ @type name: C{bytes}
+
+ @return: L{None} if the name is not found in the file, otherwise a
+ C{str} giving the address in the file associated with the name.
+ """
+ results = []
+ try:
+ lines = hostsFile.getContent().splitlines()
+ except BaseException:
+ return results
+
+ name = name.lower()
+ for line in lines:
+ idx = line.find(b"#")
+ if idx != -1:
+ line = line[:idx]
+ if not line:
+ continue
+ parts = line.split()
+
+ if name.lower() in [s.lower() for s in parts[1:]]:
+ try:
+ maybeIP = nativeString(parts[0])
+ except ValueError: # Not ASCII.
+ continue
+ if isIPAddress(maybeIP) or isIPv6Address(maybeIP):
+ results.append(maybeIP)
+ return results
+
+
+def searchFileFor(file, name):
+ """
+ Grep given file, which is in hosts(5) standard format, for an address
+ entry with a given name.
+
+ @param file: The name of the hosts(5)-format file to search.
+ @type file: C{str} or C{bytes}
+
+ @param name: The name to search for.
+ @type name: C{bytes}
+
+ @return: L{None} if the name is not found in the file, otherwise a
+ C{str} giving the first address in the file associated with
+ the name.
+ """
+ addresses = searchFileForAll(FilePath(file), name)
+ if addresses:
+ return addresses[0]
+ return None
+
+
+class Resolver(common.ResolverBase):
+ """
+ A resolver that services hosts(5) format files.
+ """
+
+ def __init__(self, file=b"/etc/hosts", ttl=60 * 60):
+ common.ResolverBase.__init__(self)
+ self.file = file
+ self.ttl = ttl
+
+ def _aRecords(self, name):
+ """
+ Return a tuple of L{dns.RRHeader} instances for all of the IPv4
+ addresses in the hosts file.
+ """
+ return tuple(
+ dns.RRHeader(name, dns.A, dns.IN, self.ttl, dns.Record_A(addr, self.ttl))
+ for addr in searchFileForAll(FilePath(self.file), name)
+ if isIPAddress(addr)
+ )
+
+ def _aaaaRecords(self, name):
+ """
+ Return a tuple of L{dns.RRHeader} instances for all of the IPv6
+ addresses in the hosts file.
+ """
+ return tuple(
+ dns.RRHeader(
+ name, dns.AAAA, dns.IN, self.ttl, dns.Record_AAAA(addr, self.ttl)
+ )
+ for addr in searchFileForAll(FilePath(self.file), name)
+ if isIPv6Address(addr)
+ )
+
+ def _respond(self, name, records):
+ """
+ Generate a response for the given name containing the given result
+ records, or a failure if there are no result records.
+
+ @param name: The DNS name the response is for.
+ @type name: C{str}
+
+ @param records: A tuple of L{dns.RRHeader} instances giving the results
+ that will go into the response.
+
+ @return: A L{Deferred} which will fire with a three-tuple of result
+ records, authority records, and additional records, or which will
+ fail with L{dns.DomainError} if there are no result records.
+ """
+ if records:
+ return defer.succeed((records, (), ()))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+ def lookupAddress(self, name, timeout=None):
+ """
+ Read any IPv4 addresses from C{self.file} and return them as
+ L{Record_A} instances.
+ """
+ name = dns.domainString(name)
+ return self._respond(name, self._aRecords(name))
+
+ def lookupIPV6Address(self, name, timeout=None):
+ """
+ Read any IPv6 addresses from C{self.file} and return them as
+ L{Record_AAAA} instances.
+ """
+ name = dns.domainString(name)
+ return self._respond(name, self._aaaaRecords(name))
+
+ # Someday this should include IPv6 addresses too, but that will cause
+ # problems if users of the API (mainly via getHostByName) aren't updated to
+ # know about IPv6 first.
+ # FIXME - getHostByName knows about IPv6 now.
+ lookupAllRecords = lookupAddress
diff --git a/contrib/python/Twisted/py3/twisted/names/newsfragments/.gitignore b/contrib/python/Twisted/py3/twisted/names/newsfragments/.gitignore
new file mode 100644
index 0000000000..f935021a8f
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/newsfragments/.gitignore
@@ -0,0 +1 @@
+!.gitignore
diff --git a/contrib/python/Twisted/py3/twisted/names/resolve.py b/contrib/python/Twisted/py3/twisted/names/resolve.py
new file mode 100644
index 0000000000..af4f40fea9
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/resolve.py
@@ -0,0 +1,91 @@
+# -*- test-case-name: twisted.names.test.test_resolve -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Lookup a name using multiple resolvers.
+
+Future Plans: This needs someway to specify which resolver answered
+the query, or someway to specify (authority|ttl|cache behavior|more?)
+"""
+
+
+from zope.interface import implementer
+
+from twisted.internet import defer, interfaces
+from twisted.names import common, dns, error
+
+
+class FailureHandler:
+ def __init__(self, resolver, query, timeout):
+ self.resolver = resolver
+ self.query = query
+ self.timeout = timeout
+
+ def __call__(self, failure):
+ # AuthoritativeDomainErrors should halt resolution attempts
+ failure.trap(dns.DomainError, defer.TimeoutError, NotImplementedError)
+ return self.resolver(self.query, self.timeout)
+
+
+@implementer(interfaces.IResolver)
+class ResolverChain(common.ResolverBase):
+ """
+ Lookup an address using multiple L{IResolver}s
+ """
+
+ def __init__(self, resolvers):
+ """
+ @type resolvers: L{list}
+ @param resolvers: A L{list} of L{IResolver} providers.
+ """
+ common.ResolverBase.__init__(self)
+ self.resolvers = resolvers
+
+ def _lookup(self, name, cls, type, timeout):
+ """
+ Build a L{dns.Query} for the given parameters and dispatch it
+ to each L{IResolver} in C{self.resolvers} until an answer or
+ L{error.AuthoritativeDomainError} is returned.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type type: C{int}
+ @param type: DNS record type.
+
+ @type cls: C{int}
+ @param cls: DNS record class.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: L{Deferred}
+ @return: A L{Deferred} which fires with a three-tuple of lists of
+ L{twisted.names.dns.RRHeader} instances. The first element of the
+ tuple gives answers. The second element of the tuple gives
+ authorities. The third element of the tuple gives additional
+ information. The L{Deferred} may instead fail with one of the
+ exceptions defined in L{twisted.names.error} or with
+ C{NotImplementedError}.
+ """
+ if not self.resolvers:
+ return defer.fail(error.DomainError())
+ q = dns.Query(name, type, cls)
+ d = self.resolvers[0].query(q, timeout)
+ for r in self.resolvers[1:]:
+ d = d.addErrback(FailureHandler(r.query, q, timeout))
+ return d
+
+ def lookupAllRecords(self, name, timeout=None):
+ # XXX: Why is this necessary? dns.ALL_RECORDS queries should
+ # be handled just the same as any other type by _lookup
+ # above. If I remove this method all names tests still
+ # pass. See #6604 -rwall
+ if not self.resolvers:
+ return defer.fail(error.DomainError())
+ d = self.resolvers[0].lookupAllRecords(name, timeout)
+ for r in self.resolvers[1:]:
+ d = d.addErrback(FailureHandler(r.lookupAllRecords, name, timeout))
+ return d
diff --git a/contrib/python/Twisted/py3/twisted/names/root.py b/contrib/python/Twisted/py3/twisted/names/root.py
new file mode 100644
index 0000000000..3531dbfede
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/root.py
@@ -0,0 +1,331 @@
+# -*- test-case-name: twisted.names.test.test_rootresolve -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Resolver implementation for querying successive authoritative servers to
+lookup a record, starting from the root nameservers.
+
+@author: Jp Calderone
+
+todo::
+ robustify it
+ documentation
+"""
+
+from twisted.internet import defer
+from twisted.names import common, dns, error
+from twisted.python.failure import Failure
+
+
+class _DummyController:
+ """
+ A do-nothing DNS controller. This is useful when all messages received
+ will be responses to previously issued queries. Anything else received
+ will be ignored.
+ """
+
+ def messageReceived(self, *args):
+ pass
+
+
+class Resolver(common.ResolverBase):
+ """
+ L{Resolver} implements recursive lookup starting from a specified list of
+ root servers.
+
+ @ivar hints: See C{hints} parameter of L{__init__}
+ @ivar _maximumQueries: See C{maximumQueries} parameter of L{__init__}
+ @ivar _reactor: See C{reactor} parameter of L{__init__}
+ @ivar _resolverFactory: See C{resolverFactory} parameter of L{__init__}
+ """
+
+ def __init__(self, hints, maximumQueries=10, reactor=None, resolverFactory=None):
+ """
+ @param hints: A L{list} of L{str} giving the dotted quad
+ representation of IP addresses of root servers at which to
+ begin resolving names.
+ @type hints: L{list} of L{str}
+
+ @param maximumQueries: An optional L{int} giving the maximum
+ number of queries which will be attempted to resolve a
+ single name.
+ @type maximumQueries: L{int}
+
+ @param reactor: An optional L{IReactorTime} and L{IReactorUDP}
+ provider to use to bind UDP ports and manage timeouts.
+ @type reactor: L{IReactorTime} and L{IReactorUDP} provider
+
+ @param resolverFactory: An optional callable which accepts C{reactor}
+ and C{servers} arguments and returns an instance that provides a
+ C{queryUDP} method. Defaults to L{twisted.names.client.Resolver}.
+ @type resolverFactory: callable
+ """
+ common.ResolverBase.__init__(self)
+ self.hints = hints
+ self._maximumQueries = maximumQueries
+ self._reactor = reactor
+ if resolverFactory is None:
+ from twisted.names.client import Resolver as resolverFactory
+ self._resolverFactory = resolverFactory
+
+ def _roots(self):
+ """
+ Return a list of two-tuples representing the addresses of the root
+ servers, as defined by C{self.hints}.
+ """
+ return [(ip, dns.PORT) for ip in self.hints]
+
+ def _query(self, query, servers, timeout, filter):
+ """
+ Issue one query and return a L{Deferred} which fires with its response.
+
+ @param query: The query to issue.
+ @type query: L{dns.Query}
+
+ @param servers: The servers which might have an answer for this
+ query.
+ @type servers: L{list} of L{tuple} of L{str} and L{int}
+
+ @param timeout: A timeout on how long to wait for the response.
+ @type timeout: L{tuple} of L{int}
+
+ @param filter: A flag indicating whether to filter the results. If
+ C{True}, the returned L{Deferred} will fire with a three-tuple of
+ lists of L{twisted.names.dns.RRHeader} (like the return value of
+ the I{lookup*} methods of L{IResolver}. IF C{False}, the result
+ will be a L{Message} instance.
+ @type filter: L{bool}
+
+ @return: A L{Deferred} which fires with the response or a timeout
+ error.
+ @rtype: L{Deferred}
+ """
+ r = self._resolverFactory(servers=servers, reactor=self._reactor)
+ d = r.queryUDP([query], timeout)
+ if filter:
+ d.addCallback(r.filterAnswers)
+ return d
+
+ def _lookup(self, name, cls, type, timeout):
+ """
+ Implement name lookup by recursively discovering the authoritative
+ server for the name and then asking it, starting at one of the servers
+ in C{self.hints}.
+ """
+ if timeout is None:
+ # A series of timeouts for semi-exponential backoff, summing to an
+ # arbitrary total of 60 seconds.
+ timeout = (1, 3, 11, 45)
+ return self._discoverAuthority(
+ dns.Query(name, type, cls), self._roots(), timeout, self._maximumQueries
+ )
+
+ def _discoverAuthority(self, query, servers, timeout, queriesLeft):
+ """
+ Issue a query to a server and follow a delegation if necessary.
+
+ @param query: The query to issue.
+ @type query: L{dns.Query}
+
+ @param servers: The servers which might have an answer for this
+ query.
+ @type servers: L{list} of L{tuple} of L{str} and L{int}
+
+ @param timeout: A C{tuple} of C{int} giving the timeout to use for this
+ query.
+
+ @param queriesLeft: A C{int} giving the number of queries which may
+ yet be attempted to answer this query before the attempt will be
+ abandoned.
+
+ @return: A L{Deferred} which fires with a three-tuple of lists of
+ L{twisted.names.dns.RRHeader} giving the response, or with a
+ L{Failure} if there is a timeout or response error.
+ """
+ # Stop now if we've hit the query limit.
+ if queriesLeft <= 0:
+ return Failure(error.ResolverError("Query limit reached without result"))
+
+ d = self._query(query, servers, timeout, False)
+ d.addCallback(self._discoveredAuthority, query, timeout, queriesLeft - 1)
+ return d
+
+ def _discoveredAuthority(self, response, query, timeout, queriesLeft):
+ """
+ Interpret the response to a query, checking for error codes and
+ following delegations if necessary.
+
+ @param response: The L{Message} received in response to issuing C{query}.
+ @type response: L{Message}
+
+ @param query: The L{dns.Query} which was issued.
+ @type query: L{dns.Query}.
+
+ @param timeout: The timeout to use if another query is indicated by
+ this response.
+ @type timeout: L{tuple} of L{int}
+
+ @param queriesLeft: A C{int} giving the number of queries which may
+ yet be attempted to answer this query before the attempt will be
+ abandoned.
+
+ @return: A L{Failure} indicating a response error, a three-tuple of
+ lists of L{twisted.names.dns.RRHeader} giving the response to
+ C{query} or a L{Deferred} which will fire with one of those.
+ """
+ if response.rCode != dns.OK:
+ return Failure(self.exceptionForCode(response.rCode)(response))
+
+ # Turn the answers into a structure that's a little easier to work with.
+ records = {}
+ for answer in response.answers:
+ records.setdefault(answer.name, []).append(answer)
+
+ def findAnswerOrCName(name, type, cls):
+ cname = None
+ for record in records.get(name, []):
+ if record.cls == cls:
+ if record.type == type:
+ return record
+ elif record.type == dns.CNAME:
+ cname = record
+ # If there were any CNAME records, return the last one. There's
+ # only supposed to be zero or one, though.
+ return cname
+
+ seen = set()
+ name = query.name
+ record = None
+ while True:
+ seen.add(name)
+ previous = record
+ record = findAnswerOrCName(name, query.type, query.cls)
+ if record is None:
+ if name == query.name:
+ # If there's no answer for the original name, then this may
+ # be a delegation. Code below handles it.
+ break
+ else:
+ # Try to resolve the CNAME with another query.
+ d = self._discoverAuthority(
+ dns.Query(str(name), query.type, query.cls),
+ self._roots(),
+ timeout,
+ queriesLeft,
+ )
+ # We also want to include the CNAME in the ultimate result,
+ # otherwise this will be pretty confusing.
+
+ def cbResolved(results):
+ answers, authority, additional = results
+ answers.insert(0, previous)
+ return (answers, authority, additional)
+
+ d.addCallback(cbResolved)
+ return d
+ elif record.type == query.type:
+ return (response.answers, response.authority, response.additional)
+ else:
+ # It's a CNAME record. Try to resolve it from the records
+ # in this response with another iteration around the loop.
+ if record.payload.name in seen:
+ raise error.ResolverError("Cycle in CNAME processing")
+ name = record.payload.name
+
+ # Build a map to use to convert NS names into IP addresses.
+ addresses = {}
+ for rr in response.additional:
+ if rr.type == dns.A:
+ addresses[rr.name.name] = rr.payload.dottedQuad()
+
+ hints = []
+ traps = []
+ for rr in response.authority:
+ if rr.type == dns.NS:
+ ns = rr.payload.name.name
+ if ns in addresses:
+ hints.append((addresses[ns], dns.PORT))
+ else:
+ traps.append(ns)
+ if hints:
+ return self._discoverAuthority(query, hints, timeout, queriesLeft)
+ elif traps:
+ d = self.lookupAddress(traps[0], timeout)
+
+ def getOneAddress(results):
+ answers, authority, additional = results
+ return answers[0].payload.dottedQuad()
+
+ d.addCallback(getOneAddress)
+ d.addCallback(
+ lambda hint: self._discoverAuthority(
+ query, [(hint, dns.PORT)], timeout, queriesLeft - 1
+ )
+ )
+ return d
+ else:
+ return Failure(
+ error.ResolverError("Stuck at response without answers or delegation")
+ )
+
+
+def makePlaceholder(deferred, name):
+ def placeholder(*args, **kw):
+ deferred.addCallback(lambda r: getattr(r, name)(*args, **kw))
+ return deferred
+
+ return placeholder
+
+
+class DeferredResolver:
+ def __init__(self, resolverDeferred):
+ self.waiting = []
+ resolverDeferred.addCallback(self.gotRealResolver)
+
+ def gotRealResolver(self, resolver):
+ w = self.waiting
+ self.__dict__ = resolver.__dict__
+ self.__class__ = resolver.__class__
+ for d in w:
+ d.callback(resolver)
+
+ def __getattr__(self, name):
+ if name.startswith("lookup") or name in ("getHostByName", "query"):
+ self.waiting.append(defer.Deferred())
+ return makePlaceholder(self.waiting[-1], name)
+ raise AttributeError(name)
+
+
+def bootstrap(resolver, resolverFactory=None):
+ """
+ Lookup the root nameserver addresses using the given resolver
+
+ Return a Resolver which will eventually become a C{root.Resolver}
+ instance that has references to all the root servers that we were able
+ to look up.
+
+ @param resolver: The resolver instance which will be used to
+ lookup the root nameserver addresses.
+ @type resolver: L{twisted.internet.interfaces.IResolverSimple}
+
+ @param resolverFactory: An optional callable which returns a
+ resolver instance. It will passed as the C{resolverFactory}
+ argument to L{Resolver.__init__}.
+ @type resolverFactory: callable
+
+ @return: A L{DeferredResolver} which will be dynamically replaced
+ with L{Resolver} when the root nameservers have been looked up.
+ """
+ domains = [chr(ord("a") + i) for i in range(13)]
+ L = [resolver.getHostByName("%s.root-servers.net" % d) for d in domains]
+ d = defer.DeferredList(L, consumeErrors=True)
+
+ def buildResolver(res):
+ return Resolver(
+ hints=[e[1] for e in res if e[0]], resolverFactory=resolverFactory
+ )
+
+ d.addCallback(buildResolver)
+
+ return DeferredResolver(d)
diff --git a/contrib/python/Twisted/py3/twisted/names/secondary.py b/contrib/python/Twisted/py3/twisted/names/secondary.py
new file mode 100644
index 0000000000..0b9e184b02
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/secondary.py
@@ -0,0 +1,216 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+__all__ = ["SecondaryAuthority", "SecondaryAuthorityService"]
+
+from twisted.application import service
+from twisted.internet import defer, task
+from twisted.names import client, common, dns, resolve
+from twisted.names.authority import FileAuthority
+from twisted.python import failure, log
+from twisted.python.compat import nativeString
+
+
+class SecondaryAuthorityService(service.Service):
+ """
+ A service that keeps one or more authorities up to date by doing hourly
+ zone transfers from a master.
+
+ @ivar primary: IP address of the master.
+ @type primary: L{str}
+
+ @ivar domains: An authority for each domain mirrored from the master.
+ @type domains: L{list} of L{SecondaryAuthority}
+ """
+
+ calls = None
+
+ _port = 53
+
+ def __init__(self, primary, domains):
+ """
+ @param primary: The IP address of the server from which to perform
+ zone transfers.
+ @type primary: L{str}
+
+ @param domains: A sequence of domain names for which to perform
+ zone transfers.
+ @type domains: L{list} of L{bytes}
+ """
+ self.primary = nativeString(primary)
+ self.domains = [SecondaryAuthority(primary, d) for d in domains]
+
+ @classmethod
+ def fromServerAddressAndDomains(cls, serverAddress, domains):
+ """
+ Construct a new L{SecondaryAuthorityService} from a tuple giving a
+ server address and a C{str} giving the name of a domain for which this
+ is an authority.
+
+ @param serverAddress: A two-tuple, the first element of which is a
+ C{str} giving an IP address and the second element of which is a
+ C{int} giving a port number. Together, these define where zone
+ transfers will be attempted from.
+
+ @param domains: Domain names for which to perform zone transfers.
+ @type domains: sequence of L{bytes}
+
+ @return: A new instance of L{SecondaryAuthorityService}.
+ """
+ primary, port = serverAddress
+ service = cls(primary, [])
+ service._port = port
+ service.domains = [
+ SecondaryAuthority.fromServerAddressAndDomain(serverAddress, d)
+ for d in domains
+ ]
+ return service
+
+ def getAuthority(self):
+ """
+ Get a resolver for the transferred domains.
+
+ @rtype: L{ResolverChain}
+ """
+ return resolve.ResolverChain(self.domains)
+
+ def startService(self):
+ service.Service.startService(self)
+ self.calls = [task.LoopingCall(d.transfer) for d in self.domains]
+ i = 0
+ from twisted.internet import reactor
+
+ for c in self.calls:
+ # XXX Add errbacks, respect proper timeouts
+ reactor.callLater(i, c.start, 60 * 60)
+ i += 1
+
+ def stopService(self):
+ service.Service.stopService(self)
+ for c in self.calls:
+ c.stop()
+
+
+class SecondaryAuthority(FileAuthority):
+ """
+ An Authority that keeps itself updated by performing zone transfers.
+
+ @ivar primary: The IP address of the server from which zone transfers will
+ be attempted.
+ @type primary: L{str}
+
+ @ivar _port: The port number of the server from which zone transfers will
+ be attempted.
+ @type _port: L{int}
+
+ @ivar domain: The domain for which this is the secondary authority.
+ @type domain: L{bytes}
+
+ @ivar _reactor: The reactor to use to perform the zone transfers, or
+ L{None} to use the global reactor.
+ """
+
+ transferring = False
+ soa = records = None
+ _port = 53
+ _reactor = None
+
+ def __init__(self, primaryIP, domain):
+ """
+ @param domain: The domain for which this will be the secondary
+ authority.
+ @type domain: L{bytes} or L{str}
+ """
+ # Yep. Skip over FileAuthority.__init__. This is a hack until we have
+ # a good composition-based API for the complicated DNS record lookup
+ # logic we want to share.
+ common.ResolverBase.__init__(self)
+ self.primary = nativeString(primaryIP)
+ self.domain = dns.domainString(domain)
+
+ @classmethod
+ def fromServerAddressAndDomain(cls, serverAddress, domain):
+ """
+ Construct a new L{SecondaryAuthority} from a tuple giving a server
+ address and a C{bytes} giving the name of a domain for which this is an
+ authority.
+
+ @param serverAddress: A two-tuple, the first element of which is a
+ C{str} giving an IP address and the second element of which is a
+ C{int} giving a port number. Together, these define where zone
+ transfers will be attempted from.
+
+ @param domain: A C{bytes} giving the domain to transfer.
+ @type domain: L{bytes}
+
+ @return: A new instance of L{SecondaryAuthority}.
+ """
+ primary, port = serverAddress
+ secondary = cls(primary, domain)
+ secondary._port = port
+ return secondary
+
+ def transfer(self):
+ """
+ Attempt a zone transfer.
+
+ @returns: A L{Deferred} that fires with L{None} when attempted zone
+ transfer has completed.
+ """
+ # FIXME: This logic doesn't avoid duplicate transfers
+ # https://twistedmatrix.com/trac/ticket/9754
+ if self.transferring: # <-- never true
+ return
+ self.transfering = True # <-- speling
+
+ reactor = self._reactor
+ if reactor is None:
+ from twisted.internet import reactor
+
+ resolver = client.Resolver(
+ servers=[(self.primary, self._port)], reactor=reactor
+ )
+ return (
+ resolver.lookupZone(self.domain)
+ .addCallback(self._cbZone)
+ .addErrback(self._ebZone)
+ )
+
+ def _lookup(self, name, cls, type, timeout=None):
+ if not self.soa or not self.records:
+ # No transfer has occurred yet. Fail non-authoritatively so that
+ # the caller can try elsewhere.
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+ return FileAuthority._lookup(self, name, cls, type, timeout)
+
+ def _cbZone(self, zone):
+ ans, _, _ = zone
+ self.records = r = {}
+ for rec in ans:
+ if not self.soa and rec.type == dns.SOA:
+ self.soa = (rec.name.name.lower(), rec.payload)
+ else:
+ r.setdefault(rec.name.name.lower(), []).append(rec.payload)
+
+ def _ebZone(self, failure):
+ log.msg(
+ "Updating %s from %s failed during zone transfer"
+ % (self.domain, self.primary)
+ )
+ log.err(failure)
+
+ def update(self):
+ self.transfer().addCallbacks(self._cbTransferred, self._ebTransferred)
+
+ def _cbTransferred(self, result):
+ self.transferring = False
+
+ def _ebTransferred(self, failure):
+ self.transferred = False
+ log.msg(
+ "Transferring %s from %s failed after zone transfer"
+ % (self.domain, self.primary)
+ )
+ log.err(failure)
diff --git a/contrib/python/Twisted/py3/twisted/names/server.py b/contrib/python/Twisted/py3/twisted/names/server.py
new file mode 100644
index 0000000000..63fff7a277
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/server.py
@@ -0,0 +1,569 @@
+# -*- test-case-name: twisted.names.test.test_names,twisted.names.test.test_server -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Async DNS server
+
+Future plans:
+ - Better config file format maybe
+ - Make sure to differentiate between different classes
+ - notice truncation bit
+
+Important: No additional processing is done on some of the record types.
+This violates the most basic RFC and is just plain annoying
+for resolvers to deal with. Fix it.
+
+@author: Jp Calderone
+"""
+
+import time
+
+from twisted.internet import protocol
+from twisted.names import dns, resolve
+from twisted.python import log
+
+
+class DNSServerFactory(protocol.ServerFactory):
+ """
+ Server factory and tracker for L{DNSProtocol} connections. This class also
+ provides records for responses to DNS queries.
+
+ @ivar cache: A L{Cache<twisted.names.cache.CacheResolver>} instance whose
+ C{cacheResult} method is called when a response is received from one of
+ C{clients}. Defaults to L{None} if no caches are specified. See
+ C{caches} of L{__init__} for more details.
+ @type cache: L{Cache<twisted.names.cache.CacheResolver>} or L{None}
+
+ @ivar canRecurse: A flag indicating whether this server is capable of
+ performing recursive DNS resolution.
+ @type canRecurse: L{bool}
+
+ @ivar resolver: A L{resolve.ResolverChain} containing an ordered list of
+ C{authorities}, C{caches} and C{clients} to which queries will be
+ dispatched.
+ @type resolver: L{resolve.ResolverChain}
+
+ @ivar verbose: See L{__init__}
+
+ @ivar connections: A list of all the connected L{DNSProtocol} instances
+ using this object as their controller.
+ @type connections: C{list} of L{DNSProtocol} instances
+
+ @ivar protocol: A callable used for building a DNS stream protocol. Called
+ by L{DNSServerFactory.buildProtocol} and passed the L{DNSServerFactory}
+ instance as the one and only positional argument. Defaults to
+ L{dns.DNSProtocol}.
+ @type protocol: L{IProtocolFactory} constructor
+
+ @ivar _messageFactory: A response message constructor with an initializer
+ signature matching L{dns.Message.__init__}.
+ @type _messageFactory: C{callable}
+ """
+
+ # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10004#ticket
+ protocol = dns.DNSProtocol # type: ignore[assignment]
+ cache = None
+ _messageFactory = dns.Message
+
+ def __init__(self, authorities=None, caches=None, clients=None, verbose=0):
+ """
+ @param authorities: Resolvers which provide authoritative answers.
+ @type authorities: L{list} of L{IResolver} providers
+
+ @param caches: Resolvers which provide cached non-authoritative
+ answers. The first cache instance is assigned to
+ C{DNSServerFactory.cache} and its C{cacheResult} method will be
+ called when a response is received from one of C{clients}.
+ @type caches: L{list} of L{Cache<twisted.names.cache.CacheResolver>} instances
+
+ @param clients: Resolvers which are capable of performing recursive DNS
+ lookups.
+ @type clients: L{list} of L{IResolver} providers
+
+ @param verbose: An integer controlling the verbosity of logging of
+ queries and responses. Default is C{0} which means no logging. Set
+ to C{2} to enable logging of full query and response messages.
+ @type verbose: L{int}
+ """
+ resolvers = []
+ if authorities is not None:
+ resolvers.extend(authorities)
+ if caches is not None:
+ resolvers.extend(caches)
+ if clients is not None:
+ resolvers.extend(clients)
+
+ self.canRecurse = not not clients
+ self.resolver = resolve.ResolverChain(resolvers)
+ self.verbose = verbose
+ if caches:
+ self.cache = caches[-1]
+ self.connections = []
+
+ def _verboseLog(self, *args, **kwargs):
+ """
+ Log a message only if verbose logging is enabled.
+
+ @param args: Positional arguments which will be passed to C{log.msg}
+ @param kwargs: Keyword arguments which will be passed to C{log.msg}
+ """
+ if self.verbose > 0:
+ log.msg(*args, **kwargs)
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self)
+ p.factory = self
+ return p
+
+ def connectionMade(self, protocol):
+ """
+ Track a newly connected L{DNSProtocol}.
+
+ @param protocol: The protocol instance to be tracked.
+ @type protocol: L{dns.DNSProtocol}
+ """
+ self.connections.append(protocol)
+
+ def connectionLost(self, protocol):
+ """
+ Stop tracking a no-longer connected L{DNSProtocol}.
+
+ @param protocol: The tracked protocol instance to be which has been
+ lost.
+ @type protocol: L{dns.DNSProtocol}
+ """
+ self.connections.remove(protocol)
+
+ def sendReply(self, protocol, message, address):
+ """
+ Send a response C{message} to a given C{address} via the supplied
+ C{protocol}.
+
+ Message payload will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ @param protocol: The DNS protocol instance to which to send the message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The DNS message to be sent.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the message will be sent or L{None}
+ if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ if self.verbose > 1:
+ s = " ".join([str(a.payload) for a in message.answers])
+ auth = " ".join([str(a.payload) for a in message.authority])
+ add = " ".join([str(a.payload) for a in message.additional])
+ if not s:
+ log.msg("Replying with no answers")
+ else:
+ log.msg("Answers are " + s)
+ log.msg("Authority is " + auth)
+ log.msg("Additional is " + add)
+
+ if address is None:
+ protocol.writeMessage(message)
+ else:
+ protocol.writeMessage(message, address)
+
+ self._verboseLog(
+ "Processed query in %0.3f seconds" % (time.time() - message.timeReceived)
+ )
+
+ def _responseFromMessage(
+ self, message, rCode=dns.OK, answers=None, authority=None, additional=None
+ ):
+ """
+ Generate a L{Message} instance suitable for use as the response to
+ C{message}.
+
+ C{queries} will be copied from the request to the response.
+
+ C{rCode}, C{answers}, C{authority} and C{additional} will be assigned to
+ the response, if supplied.
+
+ The C{recAv} flag will be set on the response if the C{canRecurse} flag
+ on this L{DNSServerFactory} is set to L{True}.
+
+ The C{auth} flag will be set on the response if *any* of the supplied
+ C{answers} have their C{auth} flag set to L{True}.
+
+ The response will have the same C{maxSize} as the request.
+
+ Additionally, the response will have a C{timeReceived} attribute whose
+ value is that of the original request and the
+
+ @see: L{dns._responseFromMessage}
+
+ @param message: The request message
+ @type message: L{Message}
+
+ @param rCode: The response code which will be assigned to the response.
+ @type message: L{int}
+
+ @param answers: An optional list of answer records which will be
+ assigned to the response.
+ @type answers: L{list} of L{dns.RRHeader}
+
+ @param authority: An optional list of authority records which will be
+ assigned to the response.
+ @type authority: L{list} of L{dns.RRHeader}
+
+ @param additional: An optional list of additional records which will be
+ assigned to the response.
+ @type additional: L{list} of L{dns.RRHeader}
+
+ @return: A response L{Message} instance.
+ @rtype: L{Message}
+ """
+ if answers is None:
+ answers = []
+ if authority is None:
+ authority = []
+ if additional is None:
+ additional = []
+ authoritativeAnswer = False
+ for x in answers:
+ if x.isAuthoritative():
+ authoritativeAnswer = True
+ break
+
+ response = dns._responseFromMessage(
+ responseConstructor=self._messageFactory,
+ message=message,
+ recAv=self.canRecurse,
+ rCode=rCode,
+ auth=authoritativeAnswer,
+ )
+
+ # XXX: Timereceived is a hack which probably shouldn't be tacked onto
+ # the message. Use getattr here so that we don't have to set the
+ # timereceived on every message in the tests. See #6957.
+ response.timeReceived = getattr(message, "timeReceived", None)
+
+ # XXX: This is another hack. dns.Message.decode sets maxSize=0 which
+ # means that responses are never truncated. I'll maintain that behaviour
+ # here until #6949 is resolved.
+ response.maxSize = message.maxSize
+
+ response.answers = answers
+ response.authority = authority
+ response.additional = additional
+
+ return response
+
+ def gotResolverResponse(self, response, protocol, message, address):
+ """
+ A callback used by L{DNSServerFactory.handleQuery} for handling the
+ deferred response from C{self.resolver.query}.
+
+ Constructs a response message by combining the original query message
+ with the resolved answer, authority and additional records.
+
+ Marks the response message as authoritative if any of the resolved
+ answers are found to be authoritative.
+
+ The resolved answers count will be logged if C{DNSServerFactory.verbose}
+ is C{>1}.
+
+ @param response: Answer records, authority records and additional records
+ @type response: L{tuple} of L{list} of L{dns.RRHeader} instances
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ ans, auth, add = response
+ response = self._responseFromMessage(
+ message=message, rCode=dns.OK, answers=ans, authority=auth, additional=add
+ )
+ self.sendReply(protocol, response, address)
+
+ l = len(ans) + len(auth) + len(add)
+ self._verboseLog("Lookup found %d record%s" % (l, l != 1 and "s" or ""))
+
+ if self.cache and l:
+ self.cache.cacheResult(message.queries[0], (ans, auth, add))
+
+ def gotResolverError(self, failure, protocol, message, address):
+ """
+ A callback used by L{DNSServerFactory.handleQuery} for handling deferred
+ errors from C{self.resolver.query}.
+
+ Constructs a response message from the original query message by
+ assigning a suitable error code to C{rCode}.
+
+ An error message will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ @param failure: The reason for the failed resolution (as reported by
+ C{self.resolver.query}).
+ @type failure: L{Failure<twisted.python.failure.Failure>}
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ if failure.check(dns.DomainError, dns.AuthoritativeDomainError):
+ rCode = dns.ENAME
+ else:
+ rCode = dns.ESERVER
+ log.err(failure)
+
+ response = self._responseFromMessage(message=message, rCode=rCode)
+
+ self.sendReply(protocol, response, address)
+ self._verboseLog("Lookup failed")
+
+ def handleQuery(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} when a query message is
+ received.
+
+ Takes the first query from the received message and dispatches it to
+ C{self.resolver.query}.
+
+ Adds callbacks L{DNSServerFactory.gotResolverResponse} and
+ L{DNSServerFactory.gotResolverError} to the resulting deferred.
+
+ Note: Multiple queries in a single message are not supported because
+ there is no standard way to respond with multiple rCodes, auth,
+ etc. This is consistent with other DNS server implementations. See
+ U{http://tools.ietf.org/html/draft-ietf-dnsext-edns1-03} for a proposed
+ solution.
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+
+ @return: A C{deferred} which fires with the resolved result or error of
+ the first query in C{message}.
+ @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+ """
+ query = message.queries[0]
+
+ return (
+ self.resolver.query(query)
+ .addCallback(self.gotResolverResponse, protocol, message, address)
+ .addErrback(self.gotResolverError, protocol, message, address)
+ )
+
+ def handleInverseQuery(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} when an inverse query
+ message is received.
+
+ Replies with a I{Not Implemented} error by default.
+
+ An error message will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ Override in a subclass.
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ self._verboseLog(f"Inverse query from {address!r}")
+
+ def handleStatus(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} when a status message is
+ received.
+
+ Replies with a I{Not Implemented} error by default.
+
+ An error message will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ Override in a subclass.
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ self._verboseLog(f"Status request from {address!r}")
+
+ def handleNotify(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} when a notify message is
+ received.
+
+ Replies with a I{Not Implemented} error by default.
+
+ An error message will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ Override in a subclass.
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ self._verboseLog(f"Notify message from {address!r}")
+
+ def handleOther(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} when a message with
+ unrecognised I{OPCODE} is received.
+
+ Replies with a I{Not Implemented} error by default.
+
+ An error message will be logged if C{DNSServerFactory.verbose} is C{>1}.
+
+ Override in a subclass.
+
+ @param protocol: The DNS protocol instance to which to send a response
+ message.
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param message: The original DNS query message for which a response
+ message will be constructed.
+ @type message: L{dns.Message}
+
+ @param address: The address to which the response message will be sent
+ or L{None} if C{protocol} is a stream protocol.
+ @type address: L{tuple} or L{None}
+ """
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ self._verboseLog("Unknown op code (%d) from %r" % (message.opCode, address))
+
+ def messageReceived(self, message, proto, address=None):
+ """
+ L{DNSServerFactory.messageReceived} is called by protocols which are
+ under the control of this L{DNSServerFactory} whenever they receive a
+ DNS query message or an unexpected / duplicate / late DNS response
+ message.
+
+ L{DNSServerFactory.allowQuery} is called with the received message,
+ protocol and origin address. If it returns L{False}, a C{dns.EREFUSED}
+ response is sent back to the client.
+
+ Otherwise the received message is dispatched to one of
+ L{DNSServerFactory.handleQuery}, L{DNSServerFactory.handleInverseQuery},
+ L{DNSServerFactory.handleStatus}, L{DNSServerFactory.handleNotify}, or
+ L{DNSServerFactory.handleOther} depending on the I{OPCODE} of the
+ received message.
+
+ If C{DNSServerFactory.verbose} is C{>0} all received messages will be
+ logged in more or less detail depending on the value of C{verbose}.
+
+ @param message: The DNS message that was received.
+ @type message: L{dns.Message}
+
+ @param proto: The DNS protocol instance which received the message
+ @type proto: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param address: The address from which the message was received. Only
+ provided for messages received by datagram protocols. The origin of
+ Messages received from stream protocols can be gleaned from the
+ protocol C{transport} attribute.
+ @type address: L{tuple} or L{None}
+ """
+ message.timeReceived = time.time()
+
+ if self.verbose:
+ if self.verbose > 1:
+ s = " ".join([str(q) for q in message.queries])
+ else:
+ s = " ".join(
+ [dns.QUERY_TYPES.get(q.type, "UNKNOWN") for q in message.queries]
+ )
+ if not len(s):
+ log.msg(f"Empty query from {address or proto.transport.getPeer()!r}")
+ else:
+ log.msg(f"{s} query from {address or proto.transport.getPeer()!r}")
+
+ if not self.allowQuery(message, proto, address):
+ message.rCode = dns.EREFUSED
+ self.sendReply(proto, message, address)
+ elif message.opCode == dns.OP_QUERY:
+ self.handleQuery(message, proto, address)
+ elif message.opCode == dns.OP_INVERSE:
+ self.handleInverseQuery(message, proto, address)
+ elif message.opCode == dns.OP_STATUS:
+ self.handleStatus(message, proto, address)
+ elif message.opCode == dns.OP_NOTIFY:
+ self.handleNotify(message, proto, address)
+ else:
+ self.handleOther(message, proto, address)
+
+ def allowQuery(self, message, protocol, address):
+ """
+ Called by L{DNSServerFactory.messageReceived} to decide whether to
+ process a received message or to reply with C{dns.EREFUSED}.
+
+ This default implementation permits anything but empty queries.
+
+ Override in a subclass to implement alternative policies.
+
+ @param message: The DNS message that was received.
+ @type message: L{dns.Message}
+
+ @param protocol: The DNS protocol instance which received the message
+ @type protocol: L{dns.DNSDatagramProtocol} or L{dns.DNSProtocol}
+
+ @param address: The address from which the message was received. Only
+ provided for messages received by datagram protocols. The origin of
+ Messages received from stream protocols can be gleaned from the
+ protocol C{transport} attribute.
+ @type address: L{tuple} or L{None}
+
+ @return: L{True} if the received message contained one or more queries,
+ else L{False}.
+ @rtype: L{bool}
+ """
+ return len(message.queries)
diff --git a/contrib/python/Twisted/py3/twisted/names/srvconnect.py b/contrib/python/Twisted/py3/twisted/names/srvconnect.py
new file mode 100644
index 0000000000..6cad7a2539
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/srvconnect.py
@@ -0,0 +1,271 @@
+# -*- test-case-name: twisted.names.test.test_srvconnect -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import random
+
+from zope.interface import implementer
+
+from twisted.internet import error, interfaces
+from twisted.names import client, dns
+from twisted.names.error import DNSNameError
+from twisted.python.compat import nativeString
+
+
+class _SRVConnector_ClientFactoryWrapper:
+ def __init__(self, connector, wrappedFactory):
+ self.__connector = connector
+ self.__wrappedFactory = wrappedFactory
+
+ def startedConnecting(self, connector):
+ self.__wrappedFactory.startedConnecting(self.__connector)
+
+ def clientConnectionFailed(self, connector, reason):
+ self.__connector.connectionFailed(reason)
+
+ def clientConnectionLost(self, connector, reason):
+ self.__connector.connectionLost(reason)
+
+ def __getattr__(self, key):
+ return getattr(self.__wrappedFactory, key)
+
+
+@implementer(interfaces.IConnector)
+class SRVConnector:
+ """
+ A connector that looks up DNS SRV records.
+
+ RFC 2782 details how SRV records should be interpreted and selected
+ for subsequent connection attempts. The algorithm for using the records'
+ priority and weight is implemented in L{pickServer}.
+
+ @ivar servers: List of candidate server records for future connection
+ attempts.
+ @type servers: L{list} of L{dns.Record_SRV}
+
+ @ivar orderedServers: List of server records that have already been tried
+ in this round of connection attempts.
+ @type orderedServers: L{list} of L{dns.Record_SRV}
+ """
+
+ stopAfterDNS = 0
+
+ def __init__(
+ self,
+ reactor,
+ service,
+ domain,
+ factory,
+ protocol="tcp",
+ connectFuncName="connectTCP",
+ connectFuncArgs=(),
+ connectFuncKwArgs={},
+ defaultPort=None,
+ ):
+ """
+ @param domain: The domain to connect to. If passed as a text
+ string, it will be encoded using C{idna} encoding.
+ @type domain: L{bytes} or L{str}
+
+ @param defaultPort: Optional default port number to be used when SRV
+ lookup fails and the service name is unknown. This should be the
+ port number associated with the service name as defined by the IANA
+ registry.
+ @type defaultPort: L{int}
+ """
+ self.reactor = reactor
+ self.service = service
+ self.domain = None if domain is None else dns.domainString(domain)
+ self.factory = factory
+
+ self.protocol = protocol
+ self.connectFuncName = connectFuncName
+ self.connectFuncArgs = connectFuncArgs
+ self.connectFuncKwArgs = connectFuncKwArgs
+ self._defaultPort = defaultPort
+
+ self.connector = None
+ self.servers = None
+ # list of servers already used in this round:
+ self.orderedServers = None
+
+ def connect(self):
+ """Start connection to remote server."""
+ self.factory.doStart()
+ self.factory.startedConnecting(self)
+
+ if not self.servers:
+ if self.domain is None:
+ self.connectionFailed(
+ error.DNSLookupError("Domain is not defined."),
+ )
+ return
+ d = client.lookupService(
+ "_%s._%s.%s"
+ % (
+ nativeString(self.service),
+ nativeString(self.protocol),
+ nativeString(self.domain),
+ ),
+ )
+ d.addCallbacks(self._cbGotServers, self._ebGotServers)
+ d.addCallback(lambda x, self=self: self._reallyConnect())
+ if self._defaultPort:
+ d.addErrback(self._ebServiceUnknown)
+ d.addErrback(self.connectionFailed)
+ elif self.connector is None:
+ self._reallyConnect()
+ else:
+ self.connector.connect()
+
+ def _ebGotServers(self, failure):
+ failure.trap(DNSNameError)
+
+ # Some DNS servers reply with NXDOMAIN when in fact there are
+ # just no SRV records for that domain. Act as if we just got an
+ # empty response and use fallback.
+
+ self.servers = []
+ self.orderedServers = []
+
+ def _cbGotServers(self, result):
+ answers, auth, add = result
+ if (
+ len(answers) == 1
+ and answers[0].type == dns.SRV
+ and answers[0].payload
+ and answers[0].payload.target == dns.Name(b".")
+ ):
+ # decidedly not available
+ raise error.DNSLookupError(
+ "Service %s not available for domain %s."
+ % (repr(self.service), repr(self.domain))
+ )
+
+ self.servers = []
+ self.orderedServers = []
+ for a in answers:
+ if a.type != dns.SRV or not a.payload:
+ continue
+
+ self.orderedServers.append(a.payload)
+
+ def _ebServiceUnknown(self, failure):
+ """
+ Connect to the default port when the service name is unknown.
+
+ If no SRV records were found, the service name will be passed as the
+ port. If resolving the name fails with
+ L{error.ServiceNameUnknownError}, a final attempt is done using the
+ default port.
+ """
+ failure.trap(error.ServiceNameUnknownError)
+ self.servers = [dns.Record_SRV(0, 0, self._defaultPort, self.domain)]
+ self.orderedServers = []
+ self.connect()
+
+ def pickServer(self):
+ """
+ Pick the next server.
+
+ This selects the next server from the list of SRV records according
+ to their priority and weight values, as set out by the default
+ algorithm specified in RFC 2782.
+
+ At the beginning of a round, L{servers} is populated with
+ L{orderedServers}, and the latter is made empty. L{servers}
+ is the list of candidates, and L{orderedServers} is the list of servers
+ that have already been tried.
+
+ First, all records are ordered by priority and weight in ascending
+ order. Then for each priority level, a running sum is calculated
+ over the sorted list of records for that priority. Then a random value
+ between 0 and the final sum is compared to each record in order. The
+ first record that is greater than or equal to that random value is
+ chosen and removed from the list of candidates for this round.
+
+ @return: A tuple of target hostname and port from the chosen DNS SRV
+ record.
+ @rtype: L{tuple} of native L{str} and L{int}
+ """
+ assert self.servers is not None
+ assert self.orderedServers is not None
+
+ if not self.servers and not self.orderedServers:
+ # no SRV record, fall back..
+ return nativeString(self.domain), self.service
+
+ if not self.servers and self.orderedServers:
+ # start new round
+ self.servers = self.orderedServers
+ self.orderedServers = []
+
+ assert self.servers
+
+ self.servers.sort(key=lambda record: (record.priority, record.weight))
+ minPriority = self.servers[0].priority
+
+ index = 0
+ weightSum = 0
+ weightIndex = []
+ for x in self.servers:
+ if x.priority == minPriority:
+ weightSum += x.weight
+ weightIndex.append((index, weightSum))
+ index += 1
+
+ rand = random.randint(0, weightSum)
+ for index, weight in weightIndex:
+ if weight >= rand:
+ chosen = self.servers[index]
+ del self.servers[index]
+ self.orderedServers.append(chosen)
+
+ return str(chosen.target), chosen.port
+
+ raise RuntimeError(f"Impossible {self.__class__.__name__} pickServer result.")
+
+ def _reallyConnect(self):
+ if self.stopAfterDNS:
+ self.stopAfterDNS = 0
+ return
+
+ self.host, self.port = self.pickServer()
+ assert self.host is not None, "Must have a host to connect to."
+ assert self.port is not None, "Must have a port to connect to."
+
+ connectFunc = getattr(self.reactor, self.connectFuncName)
+ self.connector = connectFunc(
+ self.host,
+ self.port,
+ _SRVConnector_ClientFactoryWrapper(self, self.factory),
+ *self.connectFuncArgs,
+ **self.connectFuncKwArgs,
+ )
+
+ def stopConnecting(self):
+ """Stop attempting to connect."""
+ if self.connector:
+ self.connector.stopConnecting()
+ else:
+ self.stopAfterDNS = 1
+
+ def disconnect(self):
+ """Disconnect whatever our are state is."""
+ if self.connector is not None:
+ self.connector.disconnect()
+ else:
+ self.stopConnecting()
+
+ def getDestination(self):
+ assert self.connector
+ return self.connector.getDestination()
+
+ def connectionFailed(self, reason):
+ self.factory.clientConnectionFailed(self, reason)
+ self.factory.doStop()
+
+ def connectionLost(self, reason):
+ self.factory.clientConnectionLost(self, reason)
+ self.factory.doStop()
diff --git a/contrib/python/Twisted/py3/twisted/names/tap.py b/contrib/python/Twisted/py3/twisted/names/tap.py
new file mode 100644
index 0000000000..c971c10386
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/tap.py
@@ -0,0 +1,149 @@
+# -*- test-case-name: twisted.names.test.test_tap -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Domain Name Server
+"""
+
+import os
+import traceback
+
+from twisted.application import internet, service
+from twisted.names import authority, dns, secondary, server
+from twisted.python import usage
+
+
+class Options(usage.Options):
+ optParameters = [
+ ["interface", "i", "", "The interface to which to bind"],
+ ["port", "p", "53", "The port on which to listen"],
+ [
+ "resolv-conf",
+ None,
+ None,
+ "Override location of resolv.conf (implies --recursive)",
+ ],
+ ["hosts-file", None, None, "Perform lookups with a hosts file"],
+ ]
+
+ optFlags = [
+ ["cache", "c", "Enable record caching"],
+ ["recursive", "r", "Perform recursive lookups"],
+ ["verbose", "v", "Log verbosely"],
+ ]
+
+ compData = usage.Completions(
+ optActions={"interface": usage.CompleteNetInterfaces()}
+ )
+
+ zones = None
+ zonefiles = None
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self["verbose"] = 0
+ self.bindfiles = []
+ self.zonefiles = []
+ self.secondaries = []
+
+ def opt_pyzone(self, filename):
+ """Specify the filename of a Python syntax zone definition"""
+ if not os.path.exists(filename):
+ raise usage.UsageError(filename + ": No such file")
+ self.zonefiles.append(filename)
+
+ def opt_bindzone(self, filename):
+ """Specify the filename of a BIND9 syntax zone definition"""
+ if not os.path.exists(filename):
+ raise usage.UsageError(filename + ": No such file")
+ self.bindfiles.append(filename)
+
+ def opt_secondary(self, ip_domain):
+ """Act as secondary for the specified domain, performing
+ zone transfers from the specified IP (IP/domain)
+ """
+ args = ip_domain.split("/", 1)
+ if len(args) != 2:
+ raise usage.UsageError("Argument must be of the form IP[:port]/domain")
+ address = args[0].split(":")
+ if len(address) == 1:
+ address = (address[0], dns.PORT)
+ else:
+ try:
+ port = int(address[1])
+ except ValueError:
+ raise usage.UsageError(
+ f"Specify an integer port number, not {address[1]!r}"
+ )
+ address = (address[0], port)
+ self.secondaries.append((address, [args[1]]))
+
+ def opt_verbose(self):
+ """Increment verbosity level"""
+ self["verbose"] += 1
+
+ def postOptions(self):
+ if self["resolv-conf"]:
+ self["recursive"] = True
+
+ self.svcs = []
+ self.zones = []
+ for f in self.zonefiles:
+ try:
+ self.zones.append(authority.PySourceAuthority(f))
+ except Exception:
+ traceback.print_exc()
+ raise usage.UsageError("Invalid syntax in " + f)
+ for f in self.bindfiles:
+ try:
+ self.zones.append(authority.BindAuthority(f))
+ except Exception:
+ traceback.print_exc()
+ raise usage.UsageError("Invalid syntax in " + f)
+ for f in self.secondaries:
+ svc = secondary.SecondaryAuthorityService.fromServerAddressAndDomains(*f)
+ self.svcs.append(svc)
+ self.zones.append(self.svcs[-1].getAuthority())
+ try:
+ self["port"] = int(self["port"])
+ except ValueError:
+ raise usage.UsageError("Invalid port: {!r}".format(self["port"]))
+
+
+def _buildResolvers(config):
+ """
+ Build DNS resolver instances in an order which leaves recursive
+ resolving as a last resort.
+
+ @type config: L{Options} instance
+ @param config: Parsed command-line configuration
+
+ @return: Two-item tuple of a list of cache resovers and a list of client
+ resolvers
+ """
+ from twisted.names import cache, client, hosts
+
+ ca, cl = [], []
+ if config["cache"]:
+ ca.append(cache.CacheResolver(verbose=config["verbose"]))
+ if config["hosts-file"]:
+ cl.append(hosts.Resolver(file=config["hosts-file"]))
+ if config["recursive"]:
+ cl.append(client.createResolver(resolvconf=config["resolv-conf"]))
+ return ca, cl
+
+
+def makeService(config):
+ ca, cl = _buildResolvers(config)
+
+ f = server.DNSServerFactory(config.zones, ca, cl, config["verbose"])
+ p = dns.DNSDatagramProtocol(f)
+ f.noisy = 0
+ ret = service.MultiService()
+ for klass, arg in [(internet.TCPServer, f), (internet.UDPServer, p)]:
+ s = klass(config["port"], arg, interface=config["interface"])
+ s.setServiceParent(ret)
+ for svc in config.svcs:
+ svc.setServiceParent(ret)
+ return ret