diff options
Diffstat (limited to 'python/gevent/resolver/dnspython.py')
-rw-r--r-- | python/gevent/resolver/dnspython.py | 662 |
1 files changed, 0 insertions, 662 deletions
diff --git a/python/gevent/resolver/dnspython.py b/python/gevent/resolver/dnspython.py deleted file mode 100644 index c2ae73b..0000000 --- a/python/gevent/resolver/dnspython.py +++ /dev/null @@ -1,662 +0,0 @@ -# Copyright (c) 2018 gevent contributors. See LICENSE for details. - -# Portions of this code taken from the gogreen project: -# http://github.com/slideinc/gogreen -# -# Copyright (c) 2005-2010 Slide, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of the author nor the names of other -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Portions of this code taken from the eventlet project: -# https://github.com/eventlet/eventlet/blob/master/eventlet/support/greendns.py - -# Unless otherwise noted, the files in Eventlet are under the following MIT license: - -# Copyright (c) 2005-2006, Bob Ippolito -# Copyright (c) 2007-2010, Linden Research, Inc. -# Copyright (c) 2008-2010, Eventlet Contributors (see AUTHORS) - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -from __future__ import absolute_import, print_function, division - -import time -import re -import os -import sys - -import _socket -from _socket import AI_NUMERICHOST -from _socket import error -from _socket import NI_NUMERICSERV -from _socket import AF_INET -from _socket import AF_INET6 -from _socket import AF_UNSPEC - -import socket - -from . import AbstractResolver -from . import hostname_types - -from gevent._compat import string_types -from gevent._compat import iteritems -from gevent._patcher import import_patched -from gevent._config import config - -__all__ = [ - 'Resolver', -] - -# Import the DNS packages to use the gevent modules, -# even if the system is not monkey-patched. -def _patch_dns(): - top = import_patched('dns') - for pkg in ('dns', - 'dns.rdtypes', - 'dns.rdtypes.IN', - 'dns.rdtypes.ANY'): - mod = import_patched(pkg) - for name in mod.__all__: - setattr(mod, name, import_patched(pkg + '.' + name)) - return top - -dns = _patch_dns() - -def _dns_import_patched(name): - assert name.startswith('dns') - import_patched(name) - return dns - -# This module tries to dynamically import classes -# using __import__, and it's important that they match -# the ones we just created, otherwise exceptions won't be caught -# as expected. It uses a one-arg __import__ statement and then -# tries to walk down the sub-modules using getattr, so we can't -# directly use import_patched as-is. -dns.rdata.__import__ = _dns_import_patched - -resolver = dns.resolver -dTimeout = dns.resolver.Timeout - -_exc_clear = getattr(sys, 'exc_clear', lambda: None) - -# This is a copy of resolver._getaddrinfo with the crucial change that it -# doesn't have a bare except:, because that breaks Timeout and KeyboardInterrupt -# A secondary change is that calls to sys.exc_clear() have been inserted to avoid -# failing tests in test__refcount.py (timeouts). -# See https://github.com/rthalley/dnspython/pull/300 -def _getaddrinfo(host=None, service=None, family=AF_UNSPEC, socktype=0, - proto=0, flags=0): - # pylint:disable=too-many-locals,broad-except,too-many-statements - # pylint:disable=too-many-branches - # pylint:disable=redefined-argument-from-local - # pylint:disable=consider-using-in - if flags & (socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) != 0: - raise NotImplementedError - if host is None and service is None: - raise socket.gaierror(socket.EAI_NONAME) - v6addrs = [] - v4addrs = [] - canonical_name = None - try: - # Is host None or a V6 address literal? - if host is None: - canonical_name = 'localhost' - if flags & socket.AI_PASSIVE != 0: - v6addrs.append('::') - v4addrs.append('0.0.0.0') - else: - v6addrs.append('::1') - v4addrs.append('127.0.0.1') - else: - parts = host.split('%') - if len(parts) == 2: - ahost = parts[0] - else: - ahost = host - addr = dns.ipv6.inet_aton(ahost) - v6addrs.append(host) - canonical_name = host - except Exception: - _exc_clear() - try: - # Is it a V4 address literal? - addr = dns.ipv4.inet_aton(host) - v4addrs.append(host) - canonical_name = host - except Exception: - _exc_clear() - if flags & socket.AI_NUMERICHOST == 0: - try: - if family == socket.AF_INET6 or family == socket.AF_UNSPEC: - v6 = resolver._resolver.query(host, dns.rdatatype.AAAA, - raise_on_no_answer=False) - # Note that setting host ensures we query the same name - # for A as we did for AAAA. - host = v6.qname - canonical_name = v6.canonical_name.to_text(True) - if v6.rrset is not None: - for rdata in v6.rrset: - v6addrs.append(rdata.address) - if family == socket.AF_INET or family == socket.AF_UNSPEC: - v4 = resolver._resolver.query(host, dns.rdatatype.A, - raise_on_no_answer=False) - host = v4.qname - canonical_name = v4.canonical_name.to_text(True) - if v4.rrset is not None: - for rdata in v4.rrset: - v4addrs.append(rdata.address) - except dns.resolver.NXDOMAIN: - _exc_clear() - raise socket.gaierror(socket.EAI_NONAME) - except Exception: - _exc_clear() - raise socket.gaierror(socket.EAI_SYSTEM) - port = None - try: - # Is it a port literal? - if service is None: - port = 0 - else: - port = int(service) - except Exception: - _exc_clear() - if flags & socket.AI_NUMERICSERV == 0: - try: - port = socket.getservbyname(service) - except Exception: - _exc_clear() - - if port is None: - raise socket.gaierror(socket.EAI_NONAME) - tuples = [] - if socktype == 0: - socktypes = [socket.SOCK_DGRAM, socket.SOCK_STREAM] - else: - socktypes = [socktype] - if flags & socket.AI_CANONNAME != 0: - cname = canonical_name - else: - cname = '' - if family == socket.AF_INET6 or family == socket.AF_UNSPEC: - for addr in v6addrs: - for socktype in socktypes: - for proto in resolver._protocols_for_socktype[socktype]: - tuples.append((socket.AF_INET6, socktype, proto, - cname, (addr, port, 0, 0))) # XXX: gevent: this can get the scopeid wrong - if family == socket.AF_INET or family == socket.AF_UNSPEC: - for addr in v4addrs: - for socktype in socktypes: - for proto in resolver._protocols_for_socktype[socktype]: - tuples.append((socket.AF_INET, socktype, proto, - cname, (addr, port))) - if len(tuples) == 0: # pylint:disable=len-as-condition - raise socket.gaierror(socket.EAI_NONAME) - return tuples - - -resolver._getaddrinfo = _getaddrinfo - -HOSTS_TTL = 300.0 - -def _is_addr(host, parse=dns.ipv4.inet_aton): - if not host: - return False - assert isinstance(host, hostname_types), repr(host) - try: - parse(host) - except dns.exception.SyntaxError: - return False - else: - return True - -# Return True if host is a valid IPv4 address -_is_ipv4_addr = _is_addr - - -def _is_ipv6_addr(host): - # Return True if host is a valid IPv6 address - if host: - s = '%' if isinstance(host, str) else b'%' - host = host.split(s, 1)[0] - return _is_addr(host, dns.ipv6.inet_aton) - -class HostsFile(object): - """ - A class to read the contents of a hosts file (/etc/hosts). - """ - - LINES_RE = re.compile(r""" - \s* # Leading space - ([^\r\n#]+?) # The actual match, non-greedy so as not to include trailing space - \s* # Trailing space - (?:[#][^\r\n]+)? # Comments - (?:$|[\r\n]+) # EOF or newline - """, re.VERBOSE) - - def __init__(self, fname=None): - self.v4 = {} # name -> ipv4 - self.v6 = {} # name -> ipv6 - self.aliases = {} # name -> canonical_name - self.reverse = {} # ip addr -> some name - if fname is None: - if os.name == 'posix': - fname = '/etc/hosts' - elif os.name == 'nt': # pragma: no cover - fname = os.path.expandvars( - r'%SystemRoot%\system32\drivers\etc\hosts') - self.fname = fname - assert self.fname - self._last_load = 0 - - - def _readlines(self): - # Read the contents of the hosts file. - # - # Return list of lines, comment lines and empty lines are - # excluded. Note that this performs disk I/O so can be - # blocking. - with open(self.fname, 'rb') as fp: - fdata = fp.read() - - - # XXX: Using default decoding. Is that correct? - udata = fdata.decode(errors='ignore') if not isinstance(fdata, str) else fdata - - return self.LINES_RE.findall(udata) - - def load(self): # pylint:disable=too-many-locals - # Load hosts file - - # This will (re)load the data from the hosts - # file if it has changed. - - try: - load_time = os.stat(self.fname).st_mtime - needs_load = load_time > self._last_load - except (IOError, OSError): - from gevent import get_hub - get_hub().handle_error(self, *sys.exc_info()) - needs_load = False - - if not needs_load: - return - - v4 = {} - v6 = {} - aliases = {} - reverse = {} - - for line in self._readlines(): - parts = line.split() - if len(parts) < 2: - continue - ip = parts.pop(0) - if _is_ipv4_addr(ip): - ipmap = v4 - elif _is_ipv6_addr(ip): - if ip.startswith('fe80'): - # Do not use link-local addresses, OSX stores these here - continue - ipmap = v6 - else: - continue - cname = parts.pop(0).lower() - ipmap[cname] = ip - for alias in parts: - alias = alias.lower() - ipmap[alias] = ip - aliases[alias] = cname - - # XXX: This is wrong for ipv6 - if ipmap is v4: - ptr = '.'.join(reversed(ip.split('.'))) + '.in-addr.arpa' - else: - ptr = ip + '.ip6.arpa.' - if ptr not in reverse: - reverse[ptr] = cname - - self._last_load = load_time - self.v4 = v4 - self.v6 = v6 - self.aliases = aliases - self.reverse = reverse - - def iter_all_host_addr_pairs(self): - self.load() - for name, addr in iteritems(self.v4): - yield name, addr - for name, addr in iteritems(self.v6): - yield name, addr - -class _HostsAnswer(dns.resolver.Answer): - # Answer class for HostsResolver object - - def __init__(self, qname, rdtype, rdclass, rrset, raise_on_no_answer=True): - self.response = None - self.qname = qname - self.rdtype = rdtype - self.rdclass = rdclass - self.canonical_name = qname - if not rrset and raise_on_no_answer: - raise dns.resolver.NoAnswer() - self.rrset = rrset - self.expiration = (time.time() + - rrset.ttl if hasattr(rrset, 'ttl') else 0) - - -class _HostsResolver(object): - """ - Class to parse the hosts file - """ - - def __init__(self, fname=None, interval=HOSTS_TTL): - self.hosts_file = HostsFile(fname) - self.interval = interval - self._last_load = 0 - - def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, - tcp=False, source=None, raise_on_no_answer=True): # pylint:disable=unused-argument - # Query the hosts file - # - # The known rdtypes are dns.rdatatype.A, dns.rdatatype.AAAA and - # dns.rdatatype.CNAME. - # The ``rdclass`` parameter must be dns.rdataclass.IN while the - # ``tcp`` and ``source`` parameters are ignored. - # Return a HostAnswer instance or raise a dns.resolver.NoAnswer - # exception. - - now = time.time() - hosts_file = self.hosts_file - if self._last_load + self.interval < now: - self._last_load = now - hosts_file.load() - - rdclass = dns.rdataclass.IN # Always - if isinstance(qname, string_types): - name = qname - qname = dns.name.from_text(qname) - else: - name = str(qname) - - name = name.lower() - rrset = dns.rrset.RRset(qname, rdclass, rdtype) - rrset.ttl = self._last_load + self.interval - now - - if rdtype == dns.rdatatype.A: - mapping = hosts_file.v4 - kind = dns.rdtypes.IN.A.A - elif rdtype == dns.rdatatype.AAAA: - mapping = hosts_file.v6 - kind = dns.rdtypes.IN.AAAA.AAAA - elif rdtype == dns.rdatatype.CNAME: - mapping = hosts_file.aliases - kind = lambda c, t, addr: dns.rdtypes.ANY.CNAME.CNAME(c, t, dns.name.from_text(addr)) - elif rdtype == dns.rdatatype.PTR: - mapping = hosts_file.reverse - kind = lambda c, t, addr: dns.rdtypes.ANY.PTR.PTR(c, t, dns.name.from_text(addr)) - - - addr = mapping.get(name) - if not addr and qname.is_absolute(): - addr = mapping.get(name[:-1]) - if addr: - rrset.add(kind(rdclass, rdtype, addr)) - return _HostsAnswer(qname, rdtype, rdclass, rrset, raise_on_no_answer) - - def getaliases(self, hostname): - # Return a list of all the aliases of a given cname - - # Due to the way store aliases this is a bit inefficient, this - # clearly was an afterthought. But this is only used by - # gethostbyname_ex so it's probably fine. - aliases = self.hosts_file.aliases - result = [] - if hostname in aliases: - cannon = aliases[hostname] - else: - cannon = hostname - result.append(cannon) - for alias, cname in iteritems(aliases): - if cannon == cname: - result.append(alias) - result.remove(hostname) - return result - -class _DualResolver(object): - - def __init__(self): - self.hosts_resolver = _HostsResolver() - self.network_resolver = resolver.get_default_resolver() - self.network_resolver.cache = resolver.LRUCache() - - def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, - tcp=False, source=None, raise_on_no_answer=True, - _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA, dns.rdatatype.PTR)): - # Query the resolver, using /etc/hosts - - # Behavior: - # 1. if hosts is enabled and contains answer, return it now - # 2. query nameservers for qname - if qname is None: - qname = '0.0.0.0' - - if not isinstance(qname, string_types): - if isinstance(qname, bytes): - qname = qname.decode("idna") - - if isinstance(qname, string_types): - qname = dns.name.from_text(qname, None) - - if isinstance(rdtype, string_types): - rdtype = dns.rdatatype.from_text(rdtype) - - if rdclass == dns.rdataclass.IN and rdtype in _hosts_rdtypes: - try: - answer = self.hosts_resolver.query(qname, rdtype, raise_on_no_answer=False) - except Exception: # pylint: disable=broad-except - from gevent import get_hub - get_hub().handle_error(self, *sys.exc_info()) - else: - if answer.rrset: - return answer - - return self.network_resolver.query(qname, rdtype, rdclass, - tcp, source, raise_on_no_answer=raise_on_no_answer) - -def _family_to_rdtype(family): - if family == socket.AF_INET: - rdtype = dns.rdatatype.A - elif family == socket.AF_INET6: - rdtype = dns.rdatatype.AAAA - else: - raise socket.gaierror(socket.EAI_FAMILY, - 'Address family not supported') - return rdtype - -class Resolver(AbstractResolver): - """ - An *experimental* resolver that uses `dnspython`_. - - This is typically slower than the default threaded resolver - (unless there's a cache hit, in which case it can be much faster). - It is usually much faster than the c-ares resolver. It tends to - scale well as more concurrent resolutions are attempted. - - Under Python 2, if the ``idna`` package is installed, this - resolver can resolve Unicode host names that the system resolver - cannot. - - .. note:: - - This **does not** use dnspython's default resolver object, or share any - classes with ``import dns``. A separate copy of the objects is imported to - be able to function in a non monkey-patched process. The documentation for the resolver - object still applies. - - The resolver that we use is available as the :attr:`resolver` attribute - of this object (typically ``gevent.get_hub().resolver.resolver``). - - .. caution:: - - Many of the same caveats about DNS results apply here as are documented - for :class:`gevent.resolver.ares.Resolver`. - - .. caution:: - - This resolver is experimental. It may be removed or modified in - the future. As always, feedback is welcome. - - .. versionadded:: 1.3a2 - - .. _dnspython: http://www.dnspython.org - """ - - def __init__(self, hub=None): # pylint: disable=unused-argument - if resolver._resolver is None: - _resolver = resolver._resolver = _DualResolver() - if config.resolver_nameservers: - _resolver.network_resolver.nameservers[:] = config.resolver_nameservers - if config.resolver_timeout: - _resolver.network_resolver.lifetime = config.resolver_timeout - # Different hubs in different threads could be sharing the same - # resolver. - assert isinstance(resolver._resolver, _DualResolver) - self._resolver = resolver._resolver - - @property - def resolver(self): - """ - The dnspython resolver object we use. - - This object has several useful attributes that can be used to - adjust the behaviour of the DNS system: - - * ``cache`` is a :class:`dns.resolver.LRUCache`. Its maximum size - can be configured by calling :meth:`resolver.cache.set_max_size` - * ``nameservers`` controls which nameservers to talk to - * ``lifetime`` configures a timeout for each individual query. - """ - return self._resolver.network_resolver - - def close(self): - pass - - def _getaliases(self, hostname, family): - if not isinstance(hostname, str): - if isinstance(hostname, bytes): - hostname = hostname.decode("idna") - aliases = self._resolver.hosts_resolver.getaliases(hostname) - net_resolver = self._resolver.network_resolver - rdtype = _family_to_rdtype(family) - while True: - try: - ans = net_resolver.query(hostname, dns.rdatatype.CNAME, rdtype) - except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers): - break - except dTimeout: - break - else: - aliases.extend(str(rr.target) for rr in ans.rrset) - hostname = ans[0].target - return aliases - - def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0): - if ((host in (u'localhost', b'localhost') - or (_is_ipv6_addr(host) and host.startswith('fe80'))) - or not isinstance(host, str) or (flags & AI_NUMERICHOST)): - # this handles cases which do not require network access - # 1) host is None - # 2) host is of an invalid type - # 3) host is localhost or a link-local ipv6; dnspython returns the wrong - # scope-id for those. - # 3) AI_NUMERICHOST flag is set - - return _socket.getaddrinfo(host, port, family, socktype, proto, flags) - - if family == AF_UNSPEC: - # This tends to raise in the case that a v6 address did not exist - # but a v4 does. So we break it into two parts. - - # Note that if there is no ipv6 in the hosts file, but there *is* - # an ipv4, and there *is* an ipv6 in the nameservers, we will return - # both (from the first call). The system resolver on OS X only returns - # the results from the hosts file. doubleclick.com is one example. - - # See also https://github.com/gevent/gevent/issues/1012 - try: - return _getaddrinfo(host, port, family, socktype, proto, flags) - except socket.gaierror: - try: - return _getaddrinfo(host, port, AF_INET6, socktype, proto, flags) - except socket.gaierror: - return _getaddrinfo(host, port, AF_INET, socktype, proto, flags) - else: - return _getaddrinfo(host, port, family, socktype, proto, flags) - - def getnameinfo(self, sockaddr, flags): - if (sockaddr - and isinstance(sockaddr, (list, tuple)) - and sockaddr[0] in ('::1', '127.0.0.1', 'localhost')): - return _socket.getnameinfo(sockaddr, flags) - if isinstance(sockaddr, (list, tuple)) and not isinstance(sockaddr[0], hostname_types): - raise TypeError("getnameinfo(): illegal sockaddr argument") - try: - return resolver._getnameinfo(sockaddr, flags) - except error: - if not flags: - # dnspython doesn't like getting ports it can't resolve. - # We have one test, test__socket_dns.py:Test_getnameinfo_geventorg.test_port_zero - # that does this. We conservatively fix it here; this could be expanded later. - return resolver._getnameinfo(sockaddr, NI_NUMERICSERV) - - def gethostbyaddr(self, ip_address): - if ip_address in (u'127.0.0.1', u'::1', - b'127.0.0.1', b'::1', - 'localhost'): - return _socket.gethostbyaddr(ip_address) - - if not isinstance(ip_address, hostname_types): - raise TypeError("argument 1 must be str, bytes or bytearray, not %s" % (type(ip_address),)) - - return resolver._gethostbyaddr(ip_address) |