123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- # -*- test-case-name: twisted.names.test -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Base functionality useful to various parts of Twisted Names.
- """
- from __future__ import division, absolute_import
- import socket
- from zope.interface import implementer
- from twisted.names import dns
- from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError
- from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError
- from twisted.names.error import DNSUnknownError
- from twisted.internet import defer, error, interfaces
- from twisted.logger import Logger
- # 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',
- }
|