diff options
Diffstat (limited to 'python/gevent/resolver/ares.py')
-rw-r--r-- | python/gevent/resolver/ares.py | 357 |
1 files changed, 0 insertions, 357 deletions
diff --git a/python/gevent/resolver/ares.py b/python/gevent/resolver/ares.py deleted file mode 100644 index ea6e919..0000000 --- a/python/gevent/resolver/ares.py +++ /dev/null @@ -1,357 +0,0 @@ -# Copyright (c) 2011-2015 Denis Bilenko. See LICENSE for details. -""" -c-ares based hostname resolver. -""" -from __future__ import absolute_import, print_function, division -import os -import sys - -from _socket import getaddrinfo -from _socket import gaierror -from _socket import error - -from gevent._compat import string_types -from gevent._compat import text_type - -from gevent._compat import reraise -from gevent._compat import PY3 - -from gevent.hub import Waiter -from gevent.hub import get_hub - -from gevent.socket import AF_UNSPEC -from gevent.socket import AF_INET -from gevent.socket import AF_INET6 -from gevent.socket import SOCK_STREAM -from gevent.socket import SOCK_DGRAM -from gevent.socket import SOCK_RAW -from gevent.socket import AI_NUMERICHOST - -from gevent._config import config -from gevent._config import AresSettingMixin - -from .cares import channel, InvalidIP # pylint:disable=import-error,no-name-in-module -from . import _lookup_port as lookup_port -from . import _resolve_special -from . import AbstractResolver - -__all__ = ['Resolver'] - - -class Resolver(AbstractResolver): - """ - Implementation of the resolver API using the `c-ares`_ library. - - This implementation uses the c-ares library to handle name - resolution. c-ares is natively asynchronous at the socket level - and so integrates well into gevent's event loop. - - In comparison to :class:`gevent.resolver_thread.Resolver` (which - delegates to the native system resolver), the implementation is - much more complex. In addition, there have been reports of it not - properly honoring certain system configurations (for example, the - order in which IPv4 and IPv6 results are returned may not match - the threaded resolver). However, because it does not use threads, - it may scale better for applications that make many lookups. - - There are some known differences from the system resolver: - - - ``gethostbyname_ex`` and ``gethostbyaddr`` may return different - for the ``aliaslist`` tuple member. (Sometimes the same, - sometimes in a different order, sometimes a different alias - altogether.) - - ``gethostbyname_ex`` may return the ``ipaddrlist`` in a different order. - - ``getaddrinfo`` does not return ``SOCK_RAW`` results. - - ``getaddrinfo`` may return results in a different order. - - Handling of ``.local`` (mDNS) names may be different, even if they are listed in - the hosts file. - - c-ares will not resolve ``broadcasthost``, even if listed in the hosts file. - - This implementation may raise ``gaierror(4)`` where the system implementation would raise - ``herror(1)``. - - The results for ``localhost`` may be different. In particular, some system - resolvers will return more results from ``getaddrinfo`` than c-ares does, - such as SOCK_DGRAM results, and c-ares may report more ips on a multi-homed - host. - - .. caution:: This module is considered extremely experimental on PyPy, and - due to its implementation in cython, it may be slower. It may also lead to - interpreter crashes. - - .. _c-ares: http://c-ares.haxx.se - """ - - ares_class = channel - - def __init__(self, hub=None, use_environ=True, **kwargs): - if hub is None: - hub = get_hub() - self.hub = hub - if use_environ: - for setting in config.settings.values(): - if isinstance(setting, AresSettingMixin): - value = setting.get() - if value is not None: - kwargs.setdefault(setting.kwarg_name, value) - self.ares = self.ares_class(hub.loop, **kwargs) - self.pid = os.getpid() - self.params = kwargs - self.fork_watcher = hub.loop.fork(ref=False) - self.fork_watcher.start(self._on_fork) - - def __repr__(self): - return '<gevent.resolver_ares.Resolver at 0x%x ares=%r>' % (id(self), self.ares) - - def _on_fork(self): - # NOTE: See comment in gevent.hub.reinit. - pid = os.getpid() - if pid != self.pid: - self.hub.loop.run_callback(self.ares.destroy) - self.ares = self.ares_class(self.hub.loop, **self.params) - self.pid = pid - - def close(self): - if self.ares is not None: - self.hub.loop.run_callback(self.ares.destroy) - self.ares = None - self.fork_watcher.stop() - - def gethostbyname(self, hostname, family=AF_INET): - hostname = _resolve_special(hostname, family) - return self.gethostbyname_ex(hostname, family)[-1][0] - - def gethostbyname_ex(self, hostname, family=AF_INET): - if PY3: - if isinstance(hostname, str): - hostname = hostname.encode('idna') - elif not isinstance(hostname, (bytes, bytearray)): - raise TypeError('Expected es(idna), not %s' % type(hostname).__name__) - else: - if isinstance(hostname, text_type): - hostname = hostname.encode('ascii') - elif not isinstance(hostname, str): - raise TypeError('Expected string, not %s' % type(hostname).__name__) - - while True: - ares = self.ares - try: - waiter = Waiter(self.hub) - ares.gethostbyname(waiter, hostname, family) - result = waiter.get() - if not result[-1]: - raise gaierror(-5, 'No address associated with hostname') - return result - except gaierror: - if ares is self.ares: - if hostname == b'255.255.255.255': - # The stdlib handles this case in 2.7 and 3.x, but ares does not. - # It is tested by test_socket.py in 3.4. - # HACK: So hardcode the expected return. - return ('255.255.255.255', [], ['255.255.255.255']) - raise - # "self.ares is not ares" means channel was destroyed (because we were forked) - - def _lookup_port(self, port, socktype): - return lookup_port(port, socktype) - - def _getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0): - # pylint:disable=too-many-locals,too-many-branches - if isinstance(host, text_type): - host = host.encode('idna') - elif 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) AI_NUMERICHOST flag is set - return getaddrinfo(host, port, family, socktype, proto, flags) - # we also call _socket.getaddrinfo below if family is not one of AF_* - - port, socktypes = self._lookup_port(port, socktype) - - socktype_proto = [(SOCK_STREAM, 6), (SOCK_DGRAM, 17), (SOCK_RAW, 0)] - if socktypes: - socktype_proto = [(x, y) for (x, y) in socktype_proto if x in socktypes] - if proto: - socktype_proto = [(x, y) for (x, y) in socktype_proto if proto == y] - - ares = self.ares - - if family == AF_UNSPEC: - ares_values = Values(self.hub, 2) - ares.gethostbyname(ares_values, host, AF_INET) - ares.gethostbyname(ares_values, host, AF_INET6) - elif family == AF_INET: - ares_values = Values(self.hub, 1) - ares.gethostbyname(ares_values, host, AF_INET) - elif family == AF_INET6: - ares_values = Values(self.hub, 1) - ares.gethostbyname(ares_values, host, AF_INET6) - else: - raise gaierror(5, 'ai_family not supported: %r' % (family, )) - - values = ares_values.get() - if len(values) == 2 and values[0] == values[1]: - values.pop() - - result = [] - result4 = [] - result6 = [] - - for addrs in values: - if addrs.family == AF_INET: - for addr in addrs[-1]: - sockaddr = (addr, port) - for socktype4, proto4 in socktype_proto: - result4.append((AF_INET, socktype4, proto4, '', sockaddr)) - elif addrs.family == AF_INET6: - for addr in addrs[-1]: - if addr == '::1': - dest = result - else: - dest = result6 - sockaddr = (addr, port, 0, 0) - for socktype6, proto6 in socktype_proto: - dest.append((AF_INET6, socktype6, proto6, '', sockaddr)) - - # As of 2016, some platforms return IPV6 first and some do IPV4 first, - # and some might even allow configuration of which is which. For backwards - # compatibility with earlier releases (but not necessarily resolver_thread!) - # we return 4 first. See https://github.com/gevent/gevent/issues/815 for more. - result += result4 + result6 - - if not result: - raise gaierror(-5, 'No address associated with hostname') - - return result - - def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0): - while True: - ares = self.ares - try: - return self._getaddrinfo(host, port, family, socktype, proto, flags) - except gaierror: - if ares is self.ares: - raise - - def _gethostbyaddr(self, ip_address): - if PY3: - if isinstance(ip_address, str): - ip_address = ip_address.encode('idna') - elif not isinstance(ip_address, (bytes, bytearray)): - raise TypeError('Expected es(idna), not %s' % type(ip_address).__name__) - else: - if isinstance(ip_address, text_type): - ip_address = ip_address.encode('ascii') - elif not isinstance(ip_address, str): - raise TypeError('Expected string, not %s' % type(ip_address).__name__) - - waiter = Waiter(self.hub) - try: - self.ares.gethostbyaddr(waiter, ip_address) - return waiter.get() - except InvalidIP: - result = self._getaddrinfo(ip_address, None, family=AF_UNSPEC, socktype=SOCK_DGRAM) - if not result: - raise - _ip_address = result[0][-1][0] - if isinstance(_ip_address, text_type): - _ip_address = _ip_address.encode('ascii') - if _ip_address == ip_address: - raise - waiter.clear() - self.ares.gethostbyaddr(waiter, _ip_address) - return waiter.get() - - def gethostbyaddr(self, ip_address): - ip_address = _resolve_special(ip_address, AF_UNSPEC) - while True: - ares = self.ares - try: - return self._gethostbyaddr(ip_address) - except gaierror: - if ares is self.ares: - raise - - def _getnameinfo(self, sockaddr, flags): - if not isinstance(flags, int): - raise TypeError('an integer is required') - if not isinstance(sockaddr, tuple): - raise TypeError('getnameinfo() argument 1 must be a tuple') - - address = sockaddr[0] - if not PY3 and isinstance(address, text_type): - address = address.encode('ascii') - - if not isinstance(address, string_types): - raise TypeError('sockaddr[0] must be a string, not %s' % type(address).__name__) - - port = sockaddr[1] - if not isinstance(port, int): - raise TypeError('port must be an integer, not %s' % type(port)) - - waiter = Waiter(self.hub) - result = self._getaddrinfo(address, str(sockaddr[1]), family=AF_UNSPEC, socktype=SOCK_DGRAM) - if not result: - reraise(*sys.exc_info()) - elif len(result) != 1: - raise error('sockaddr resolved to multiple addresses') - family, _socktype, _proto, _name, address = result[0] - - if family == AF_INET: - if len(sockaddr) != 2: - raise error("IPv4 sockaddr must be 2 tuple") - elif family == AF_INET6: - address = address[:2] + sockaddr[2:] - - self.ares.getnameinfo(waiter, address, flags) - node, service = waiter.get() - - if service is None: - if PY3: - # ares docs: "If the query did not complete - # successfully, or one of the values was not - # requested, node or service will be NULL ". Python 2 - # allows that for the service, but Python 3 raises - # an error. This is tested by test_socket in py 3.4 - err = gaierror('nodename nor servname provided, or not known') - err.errno = 8 - raise err - service = '0' - return node, service - - def getnameinfo(self, sockaddr, flags): - while True: - ares = self.ares - try: - return self._getnameinfo(sockaddr, flags) - except gaierror: - if ares is self.ares: - raise - - -class Values(object): - # helper to collect multiple values; ignore errors unless nothing has succeeded - # QQQ could probably be moved somewhere - hub.py? - - __slots__ = ['count', 'values', 'error', 'waiter'] - - def __init__(self, hub, count): - self.count = count - self.values = [] - self.error = None - self.waiter = Waiter(hub) - - def __call__(self, source): - self.count -= 1 - if source.exception is None: - self.values.append(source.value) - else: - self.error = source.exception - if self.count <= 0: - self.waiter.switch(None) - - def get(self): - self.waiter.get() - if self.values: - return self.values - assert error is not None - raise self.error # pylint:disable=raising-bad-type |