aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/resolver/ares.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/resolver/ares.py')
-rw-r--r--python/gevent/resolver/ares.py357
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