This commit is contained in:
Sohel
2024-11-20 17:17:14 +01:00
parent aa14c170c8
commit 09b4990d6d
5404 changed files with 1184888 additions and 3 deletions

View File

@@ -0,0 +1,285 @@
# Copyright (c) 2018 gevent contributors. See LICENSE for details.
import _socket
from _socket import AF_INET
from _socket import AF_UNSPEC
from _socket import AI_CANONNAME
from _socket import AI_PASSIVE
from _socket import AI_NUMERICHOST
from _socket import EAI_NONAME
from _socket import EAI_SERVICE
from _socket import SOCK_DGRAM
from _socket import SOCK_STREAM
from _socket import SOL_TCP
from _socket import error
from _socket import gaierror
from _socket import getaddrinfo as native_getaddrinfo
from _socket import getnameinfo as native_getnameinfo
from _socket import gethostbyaddr as native_gethostbyaddr
from _socket import gethostbyname as native_gethostbyname
from _socket import gethostbyname_ex as native_gethostbyname_ex
from _socket import getservbyname as native_getservbyname
from gevent._compat import string_types
from gevent._compat import text_type
from gevent._compat import hostname_types
from gevent._compat import integer_types
from gevent._compat import PYPY
from gevent._compat import MAC
from gevent.resolver._addresses import is_ipv6_addr
# Nothing public here.
__all__ = ()
# trigger import of encodings.idna to avoid https://github.com/gevent/gevent/issues/349
u'foo'.encode('idna')
def _lookup_port(port, socktype):
# pylint:disable=too-many-branches
socktypes = []
if isinstance(port, string_types):
try:
port = int(port)
except ValueError:
try:
if socktype == 0:
origport = port
try:
port = native_getservbyname(port, 'tcp')
socktypes.append(SOCK_STREAM)
except error:
port = native_getservbyname(port, 'udp')
socktypes.append(SOCK_DGRAM)
else:
try:
if port == native_getservbyname(origport, 'udp'):
socktypes.append(SOCK_DGRAM)
except error:
pass
elif socktype == SOCK_STREAM:
port = native_getservbyname(port, 'tcp')
elif socktype == SOCK_DGRAM:
port = native_getservbyname(port, 'udp')
else:
raise gaierror(EAI_SERVICE, 'Servname not supported for ai_socktype')
except error as ex:
if 'not found' in str(ex):
raise gaierror(EAI_SERVICE, 'Servname not supported for ai_socktype')
raise gaierror(str(ex))
except UnicodeEncodeError:
raise error('Int or String expected', port)
elif port is None:
port = 0
elif isinstance(port, integer_types):
pass
else:
raise error('Int or String expected', port, type(port))
port = int(port % 65536)
if not socktypes and socktype:
socktypes.append(socktype)
return port, socktypes
def _resolve_special(hostname, family):
if not isinstance(hostname, hostname_types):
raise TypeError("argument 1 must be str, bytes or bytearray, not %s" % (type(hostname),))
if hostname in (u'', b''):
result = native_getaddrinfo(None, 0, family, SOCK_DGRAM, 0, AI_PASSIVE)
if len(result) != 1:
raise error('wildcard resolved to multiple address')
return result[0][4][0]
return hostname
class AbstractResolver(object):
HOSTNAME_ENCODING = 'idna'
_LOCAL_HOSTNAMES = (
b'localhost',
b'ip6-localhost',
b'::1',
b'127.0.0.1',
)
_LOCAL_AND_BROADCAST_HOSTNAMES = _LOCAL_HOSTNAMES + (
b'255.255.255.255',
b'<broadcast>',
)
EAI_NONAME_MSG = (
'nodename nor servname provided, or not known'
if MAC else
'Name or service not known'
)
EAI_FAMILY_MSG = (
'ai_family not supported'
)
_KNOWN_ADDR_FAMILIES = {
v
for k, v in vars(_socket).items()
if k.startswith('AF_')
}
_KNOWN_SOCKTYPES = {
v
for k, v in vars(_socket).items()
if k.startswith('SOCK_')
and k not in ('SOCK_CLOEXEC', 'SOCK_MAX_SIZE')
}
def close(self):
"""
Release resources held by this object.
Subclasses that define resources should override.
.. versionadded:: 22.10.1
"""
@staticmethod
def fixup_gaierror(func):
import functools
@functools.wraps(func)
def resolve(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except gaierror as ex:
if ex.args[0] == EAI_NONAME and len(ex.args) == 1:
# dnspython doesn't set an error message
ex.args = (EAI_NONAME, self.EAI_NONAME_MSG)
ex.errno = EAI_NONAME
raise
return resolve
def _hostname_to_bytes(self, hostname):
if isinstance(hostname, text_type):
hostname = hostname.encode(self.HOSTNAME_ENCODING)
elif not isinstance(hostname, (bytes, bytearray)):
raise TypeError('Expected str, bytes or bytearray, not %s' % type(hostname).__name__)
return bytes(hostname)
def gethostbyname(self, hostname, family=AF_INET):
# The native ``gethostbyname`` and ``gethostbyname_ex`` have some different
# behaviour with special names. Notably, ``gethostbyname`` will handle
# both "<broadcast>" and "255.255.255.255", while ``gethostbyname_ex`` refuses to
# handle those; they result in different errors, too. So we can't
# pass those through.
hostname = self._hostname_to_bytes(hostname)
if hostname in self._LOCAL_AND_BROADCAST_HOSTNAMES:
return native_gethostbyname(hostname)
hostname = _resolve_special(hostname, family)
return self.gethostbyname_ex(hostname, family)[-1][0]
def _gethostbyname_ex(self, hostname_bytes, family):
"""Raise an ``herror`` or a ``gaierror``."""
aliases = self._getaliases(hostname_bytes, family)
addresses = []
tuples = self.getaddrinfo(hostname_bytes, 0, family,
SOCK_STREAM,
SOL_TCP, AI_CANONNAME)
canonical = tuples[0][3]
for item in tuples:
addresses.append(item[4][0])
# XXX we just ignore aliases
return (canonical, aliases, addresses)
def gethostbyname_ex(self, hostname, family=AF_INET):
hostname = self._hostname_to_bytes(hostname)
if hostname in self._LOCAL_AND_BROADCAST_HOSTNAMES:
# The broadcast specials aren't handled here, but they may produce
# special errors that are hard to replicate across all systems.
return native_gethostbyname_ex(hostname)
return self._gethostbyname_ex(hostname, family)
def _getaddrinfo(self, host_bytes, port, family, socktype, proto, flags):
raise NotImplementedError
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
host = self._hostname_to_bytes(host) if host is not None else None
if (
not isinstance(host, bytes) # 1, 2
or (flags & AI_NUMERICHOST) # 3
or host in self._LOCAL_HOSTNAMES # 4
or (is_ipv6_addr(host) and host.startswith(b'fe80')) # 5
):
# 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
# 4) It's a well-known alias. TODO: This is special casing for c-ares that we don't
# really want to do. It's here because it resolves a discrepancy with the system
# resolvers caught by test cases. In gevent 20.4.0, this only worked correctly on
# Python 3 and not Python 2, by accident.
# 5) host is a link-local ipv6; dnspython returns the wrong
# scope-id for those.
return native_getaddrinfo(host, port, family, socktype, proto, flags)
return self._getaddrinfo(host, port, family, socktype, proto, flags)
def _getaliases(self, hostname, family):
# pylint:disable=unused-argument
return []
def _gethostbyaddr(self, ip_address_bytes):
"""Raises herror."""
raise NotImplementedError
def gethostbyaddr(self, ip_address):
ip_address = _resolve_special(ip_address, AF_UNSPEC)
ip_address = self._hostname_to_bytes(ip_address)
if ip_address in self._LOCAL_AND_BROADCAST_HOSTNAMES:
return native_gethostbyaddr(ip_address)
return self._gethostbyaddr(ip_address)
def _getnameinfo(self, address_bytes, port, sockaddr, flags):
raise NotImplementedError
def getnameinfo(self, sockaddr, flags):
if not isinstance(flags, integer_types):
raise TypeError('an integer is required')
if not isinstance(sockaddr, tuple):
raise TypeError('getnameinfo() argument 1 must be a tuple')
address = sockaddr[0]
address = self._hostname_to_bytes(sockaddr[0])
if address in self._LOCAL_AND_BROADCAST_HOSTNAMES:
return native_getnameinfo(sockaddr, flags)
port = sockaddr[1]
if not isinstance(port, integer_types):
raise TypeError('port must be an integer, not %s' % type(port))
if not PYPY and port >= 65536:
# System resolvers do different things with an
# out-of-bound port; macOS CPython 3.8 raises ``gaierror: [Errno 8]
# nodename nor servname provided, or not known``, while
# manylinux CPython 2.7 appears to ignore it and raises ``error:
# sockaddr resolved to multiple addresses``. TravisCI, at least ot
# one point, successfully resolved www.gevent.org to ``(readthedocs.org, '0')``.
# But c-ares 1.16 would raise ``gaierror(25, 'ARES_ESERVICE: unknown')``.
# Doing this appears to get the expected results on CPython
port = 0
if PYPY and (port < 0 or port >= 65536):
# PyPy seems to always be strict about that and produce the same results
# on all platforms.
raise OverflowError("port must be 0-65535.")
if len(sockaddr) > 2:
# Must be IPv6: (host, port, [flowinfo, [scopeid]])
flowinfo = sockaddr[2]
if flowinfo > 0xfffff:
raise OverflowError("getnameinfo(): flowinfo must be 0-1048575.")
return self._getnameinfo(address, port, sockaddr, flags)

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 gevent contributors. See LICENSE for details.
#
# Portions of this code taken from dnspython
# https://github.com/rthalley/dnspython
#
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
Private support for parsing textual addresses.
"""
from __future__ import absolute_import, division, print_function
import binascii
import re
import struct
from gevent.resolver import hostname_types
class AddressSyntaxError(ValueError):
pass
def _ipv4_inet_aton(text):
"""
Convert an IPv4 address in text form to binary struct.
*text*, a ``text``, the IPv4 address in textual form.
Returns a ``binary``.
"""
if not isinstance(text, bytes):
text = text.encode()
parts = text.split(b'.')
if len(parts) != 4:
raise AddressSyntaxError(text)
for part in parts:
if not part.isdigit():
raise AddressSyntaxError
if len(part) > 1 and part[0] == '0':
# No leading zeros
raise AddressSyntaxError(text)
try:
ints = [int(part) for part in parts]
return struct.pack('BBBB', *ints)
except:
raise AddressSyntaxError(text)
def _ipv6_inet_aton(text,
_v4_ending=re.compile(br'(.*):(\d+\.\d+\.\d+\.\d+)$'),
_colon_colon_start=re.compile(br'::.*'),
_colon_colon_end=re.compile(br'.*::$')):
"""
Convert an IPv6 address in text form to binary form.
*text*, a ``text``, the IPv6 address in textual form.
Returns a ``binary``.
"""
# pylint:disable=too-many-branches
#
# Our aim here is not something fast; we just want something that works.
#
if not isinstance(text, bytes):
text = text.encode()
if text == b'::':
text = b'0::'
#
# Get rid of the icky dot-quad syntax if we have it.
#
m = _v4_ending.match(text)
if not m is None:
b = bytearray(_ipv4_inet_aton(m.group(2)))
text = (u"{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
b[0], b[1], b[2],
b[3])).encode()
#
# Try to turn '::<whatever>' into ':<whatever>'; if no match try to
# turn '<whatever>::' into '<whatever>:'
#
m = _colon_colon_start.match(text)
if not m is None:
text = text[1:]
else:
m = _colon_colon_end.match(text)
if not m is None:
text = text[:-1]
#
# Now canonicalize into 8 chunks of 4 hex digits each
#
chunks = text.split(b':')
l = len(chunks)
if l > 8:
raise SyntaxError
seen_empty = False
canonical = []
for c in chunks:
if c == b'':
if seen_empty:
raise AddressSyntaxError(text)
seen_empty = True
for _ in range(0, 8 - l + 1):
canonical.append(b'0000')
else:
lc = len(c)
if lc > 4:
raise AddressSyntaxError(text)
if lc != 4:
c = (b'0' * (4 - lc)) + c
canonical.append(c)
if l < 8 and not seen_empty:
raise AddressSyntaxError(text)
text = b''.join(canonical)
#
# Finally we can go to binary.
#
try:
return binascii.unhexlify(text)
except (binascii.Error, TypeError):
raise AddressSyntaxError(text)
def _is_addr(host, parse=_ipv4_inet_aton):
if not host or not isinstance(host, hostname_types):
return False
try:
parse(host)
except AddressSyntaxError:
return False
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 and isinstance(host, hostname_types):
s = '%' if isinstance(host, str) else b'%'
host = host.split(s, 1)[0]
return _is_addr(host, _ipv6_inet_aton)

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 gevent contributors. See LICENSE for details.
#
# Portions of this code taken from dnspython
# https://github.com/rthalley/dnspython
#
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
Private support for parsing /etc/hosts.
"""
from __future__ import absolute_import, division, print_function
import sys
import os
import re
from gevent.resolver._addresses import is_ipv4_addr
from gevent.resolver._addresses import is_ipv6_addr
from gevent._compat import iteritems
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 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

View File

@@ -0,0 +1,355 @@
# 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 warnings
from _socket import gaierror
from _socket import herror
from _socket import error
from _socket import EAI_NONAME
from gevent._compat import text_type
from gevent._compat import integer_types
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_DGRAM
from gevent.socket import SOCK_STREAM
from gevent.socket import SOL_TCP
from gevent.socket import SOL_UDP
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 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 prior to 2020-04-30.
- This implementation may raise ``gaierror(4)`` where the
system implementation would raise ``herror(1)`` or vice versa,
with different error numbers. However, after 2020-04-30, this should be
much reduced.
- 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.
- The system implementation may return some names fully qualified, where
this implementation returns only the host name. This appears to be
the case only with entries found in ``/etc/hosts``.
- c-ares supports a limited set of flags for ``getnameinfo`` and
``getaddrinfo``; unknown flags are ignored. System-specific flags
such as ``AI_V4MAPPED_CFG`` are not supported.
- ``getaddrinfo`` may return canonical names even without the ``AI_CANONNAME``
being set.
- ``getaddrinfo`` does not appear to support IPv6 symbolic scope IDs.
.. 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.
.. versionchanged:: 1.5.0
This version of gevent typically embeds c-ares 1.15.0 or newer. In
that version of c-ares, domains ending in ``.onion`` `are never
resolved <https://github.com/c-ares/c-ares/issues/196>`_ or even
sent to the DNS server.
.. versionchanged:: 20.5.0
``getaddrinfo`` is now implemented using the native c-ares function
from c-ares 1.16 or newer.
.. versionchanged:: 20.5.0
Now ``herror`` and ``gaierror`` are raised more consistently with
the standard library resolver, and have more consistent errno values.
Handling of localhost and broadcast names is now more consistent.
.. versionchanged:: 22.10.1
Now has a ``__del__`` method that warns if the object is destroyed
without being properly closed.
.. _c-ares: http://c-ares.haxx.se
"""
cares_class = channel
def __init__(self, hub=None, use_environ=True, **kwargs):
AbstractResolver.__init__(self)
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.cares = self.cares_class(hub.loop, **kwargs)
self.pid = os.getpid()
self.params = kwargs
self.fork_watcher = hub.loop.fork(ref=False) # We shouldn't keep the loop alive
self.fork_watcher.start(self._on_fork)
def __repr__(self):
return '<gevent.resolver_ares.Resolver at 0x%x ares=%r>' % (id(self), self.cares)
def _on_fork(self):
# NOTE: See comment in gevent.hub.reinit.
pid = os.getpid()
if pid != self.pid:
self.hub.loop.run_callback(self.cares.destroy)
self.cares = self.cares_class(self.hub.loop, **self.params)
self.pid = pid
def close(self):
AbstractResolver.close(self)
if self.cares is not None:
self.hub.loop.run_callback(self.cares.destroy)
self.cares = None
self.fork_watcher.stop()
def __del__(self):
if self.cares is not None:
warnings.warn("cares Resolver destroyed while not closed",
ResourceWarning)
self.close()
def _gethostbyname_ex(self, hostname_bytes, family):
while True:
ares = self.cares
try:
waiter = Waiter(self.hub)
ares.gethostbyname(waiter, hostname_bytes, family)
result = waiter.get()
if not result[-1]:
raise herror(EAI_NONAME, self.EAI_NONAME_MSG)
return result
except herror as ex:
if ares is self.cares:
if ex.args[0] == 1:
# Somewhere along the line, the internal
# implementation of gethostbyname_ex changed to invoke
# getaddrinfo() as a first pass, much like we do for ``getnameinfo()``;
# this means it raises a different error for not-found hosts.
raise gaierror(EAI_NONAME, self.EAI_NONAME_MSG)
raise
# "self.cares 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,
fill_in_type_proto=True
):
"""
Returns a list ``(family, socktype, proto, canonname, sockaddr)``
:raises gaierror: If no results are found.
"""
# pylint:disable=too-many-locals,too-many-branches
if isinstance(host, text_type):
host = host.encode('idna')
if isinstance(port, text_type):
port = port.encode('ascii')
elif isinstance(port, integer_types):
if port == 0:
port = None
else:
port = str(port).encode('ascii')
waiter = Waiter(self.hub)
self.cares.getaddrinfo(
waiter,
host,
port,
family,
socktype,
proto,
flags,
)
# Result is a list of:
# (family, socktype, proto, canonname, sockaddr)
# Where sockaddr depends on family; for INET it is
# (address, port)
# and INET6 is
# (address, port, flow info, scope id)
result = waiter.get()
if not result:
raise gaierror(EAI_NONAME, self.EAI_NONAME_MSG)
if fill_in_type_proto:
# c-ares 1.16 DOES NOT fill in socktype or proto in the results,
# ever. It's at least supposed to do that if they were given as
# hints, but it doesn't (https://github.com/c-ares/c-ares/issues/317)
# Sigh.
# The SOL_* constants are another (older?) name for IPPROTO_*
if socktype:
hard_type_proto = [
(socktype, SOL_TCP if socktype == SOCK_STREAM else SOL_UDP),
]
elif proto:
hard_type_proto = [
(SOCK_STREAM if proto == SOL_TCP else SOCK_DGRAM, proto),
]
else:
hard_type_proto = [
(SOCK_STREAM, SOL_TCP),
(SOCK_DGRAM, SOL_UDP),
]
# pylint:disable=not-an-iterable,unsubscriptable-object
result = [
(rfamily,
hard_type if not rtype else rtype,
hard_proto if not rproto else rproto,
rcanon,
raddr)
for rfamily, rtype, rproto, rcanon, raddr
in result
for hard_type, hard_proto
in hard_type_proto
]
return result
def _getaddrinfo(self, host_bytes, port, family, socktype, proto, flags):
while True:
ares = self.cares
try:
return self.__getaddrinfo(host_bytes, port, family, socktype, proto, flags)
except gaierror:
if ares is self.cares:
raise
def __gethostbyaddr(self, ip_address):
waiter = Waiter(self.hub)
try:
self.cares.gethostbyaddr(waiter, ip_address)
return waiter.get()
except InvalidIP:
result = self._getaddrinfo(ip_address, None,
family=AF_UNSPEC, socktype=SOCK_DGRAM,
proto=0, flags=0)
if not result:
raise
# pylint:disable=unsubscriptable-object
_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.cares.gethostbyaddr(waiter, _ip_address)
return waiter.get()
def _gethostbyaddr(self, ip_address_bytes):
while True:
ares = self.cares
try:
return self.__gethostbyaddr(ip_address_bytes)
except herror:
if ares is self.cares:
raise
def __getnameinfo(self, hostname, port, sockaddr, flags):
result = self.__getaddrinfo(
hostname, port,
family=AF_UNSPEC, socktype=SOCK_DGRAM,
proto=0, flags=0,
fill_in_type_proto=False)
if 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:]
waiter = Waiter(self.hub)
self.cares.getnameinfo(waiter, address, flags)
node, service = waiter.get()
if service is None:
# 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(EAI_NONAME, self.EAI_NONAME_MSG)
err.errno = EAI_NONAME
raise err
return node, service or '0'
def _getnameinfo(self, address_bytes, port, sockaddr, flags):
while True:
ares = self.cares
try:
return self.__getnameinfo(address_bytes, port, sockaddr, flags)
except gaierror:
if ares is self.cares:
raise
# # Things that need proper error handling
# gethostbyaddr = AbstractResolver.convert_gaierror_to_herror(AbstractResolver.gethostbyaddr)

View File

@@ -0,0 +1,45 @@
# Copyright (c) 2018 gevent contributors. See LICENSE for details.
import _socket
__all__ = [
'Resolver',
]
class Resolver(object):
"""
A resolver that directly uses the system's resolver functions.
.. caution::
This resolver is *not* cooperative.
This resolver has the lowest overhead of any resolver and
typically approaches the speed of the unmodified :mod:`socket`
functions. However, it is not cooperative, so if name resolution
blocks, the entire thread and all its greenlets will be blocked.
This can be useful during debugging, or it may be a good choice if
your operating system provides a good caching resolver (such as
macOS's Directory Services) that is usually very fast and
functionally non-blocking.
.. versionchanged:: 1.3a2
This was previously undocumented and existed in :mod:`gevent.socket`.
"""
def __init__(self, hub=None):
pass
def close(self):
pass
for method in (
'gethostbyname',
'gethostbyname_ex',
'getaddrinfo',
'gethostbyaddr',
'getnameinfo'
):
locals()[method] = staticmethod(getattr(_socket, method))

View File

@@ -0,0 +1,509 @@
# 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 sys
import time
from _socket import error
from _socket import gaierror
from _socket import herror
from _socket import NI_NUMERICSERV
from _socket import AF_INET
from _socket import AF_INET6
from _socket import AF_UNSPEC
from _socket import EAI_NONAME
from _socket import EAI_FAMILY
import socket
from gevent.resolver import AbstractResolver
from gevent.resolver._hostsfile import HostsFile
from gevent.builtins import __import__ as g_import
from gevent._compat import string_types
from gevent._compat import iteritems
from gevent._config import config
__all__ = [
'Resolver',
]
# Import the DNS packages to use the gevent modules,
# even if the system is not monkey-patched. If it *is* already
# patched, this imports a second copy under a different name,
# which is probably not strictly necessary, but matches
# what we've historically done, and allows configuring the resolvers
# differently.
def _patch_dns():
from gevent._patcher import import_patched as importer
# The dns package itself is empty but defines __all__
# we make sure to import all of those things now under the
# patch. Note this triggers two DeprecationWarnings,
# one of which we could avoid.
extras = {
'dns': ('rdata', 'resolver', 'rdtypes'),
'dns.rdtypes': ('IN', 'ANY', ),
'dns.rdtypes.IN': ('A', 'AAAA',),
'dns.rdtypes.ANY': ('SOA', 'PTR'),
}
def extra_all(mod_name):
return extras.get(mod_name, ())
def after_import_hook(dns): # pylint:disable=redefined-outer-name
# Runs while still in the original patching scope.
# The dns.rdata:get_rdata_class() function tries to
# dynamically import modules using __import__ and then walk
# through the attribute tree to find classes in `dns.rdtypes`.
# It is critical that this all matches up, otherwise we can
# get different exception classes that don't get caught.
# We could patch __import__ to do things at runtime, but it's
# easier to enumerate the world and populate the cache now
# before we then switch the names back.
rdata = dns.rdata
get_rdata_class = rdata.get_rdata_class
try:
rdclass_values = list(dns.rdataclass.RdataClass)
except AttributeError:
# dnspython < 2.0
rdclass_values = dns.rdataclass._by_value
try:
rdtype_values = list(dns.rdatatype.RdataType)
except AttributeError:
# dnspython < 2.0
rdtype_values = dns.rdatatype._by_value
for rdclass in rdclass_values:
for rdtype in rdtype_values:
get_rdata_class(rdclass, rdtype)
patcher = importer('dns', extra_all, after_import_hook)
top = patcher.module
# Now disable the dynamic imports
def _no_dynamic_imports(name):
raise ValueError(name)
top.rdata.__import__ = _no_dynamic_imports
return top
dns = _patch_dns()
resolver = dns.resolver
dTimeout = dns.resolver.Timeout
# This is a wrapper for dns.resolver._getaddrinfo with two crucial changes.
# First, it backports https://github.com/rthalley/dnspython/issues/316
# from version 2.0. This can be dropped when we support only dnspython 2
# (which means only Python 3.)
# Second, it adds calls to sys.exc_clear() to avoid failing tests in
# test__refcount.py (timeouts) on Python 2. (Actually, this isn't
# strictly necessary, it was necessary to increase the timeouts in
# that function because dnspython is doing some parsing/regex/host
# lookups that are not super fast. But it does have a habit of leaving
# exceptions around which can complicate our memleak checks.)
def _getaddrinfo(host=None, service=None, family=AF_UNSPEC, socktype=0,
proto=0, flags=0,
_orig_gai=resolver._getaddrinfo,
_exc_clear=getattr(sys, 'exc_clear', lambda: None)):
if flags & (socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) != 0:
# Not implemented. We raise a gaierror as opposed to a
# NotImplementedError as it helps callers handle errors more
# appropriately. [Issue #316]
raise socket.gaierror(socket.EAI_SYSTEM)
res = _orig_gai(host, service, family, socktype, proto, flags)
_exc_clear()
return res
resolver._getaddrinfo = _getaddrinfo
HOSTS_TTL = 300.0
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: # pylint:disable=consider-using-get
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`. In addition, the handling of
symbolic scope IDs in IPv6 addresses passed to ``getaddrinfo`` exhibits
some differences.
On PyPy, ``getnameinfo`` can produce results when CPython raises
``socket.error``, and gevent's DNSPython resolver also
raises ``socket.error``.
.. caution::
This resolver is experimental. It may be removed or modified in
the future. As always, feedback is welcome.
.. versionadded:: 1.3a2
.. versionchanged:: 20.5.0
The errors raised are now much more consistent with those
raised by the standard library resolvers.
Handling of localhost and broadcast names is now more consistent.
.. _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 1:
try:
ans = net_resolver.query(hostname, dns.rdatatype.CNAME, rdtype)
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers):
break
except dTimeout:
break
except AttributeError as ex:
if hostname is None or isinstance(hostname, int):
raise TypeError(ex)
raise
else:
aliases.extend(str(rr.target) for rr in ans.rrset)
hostname = ans[0].target
return aliases
def _getaddrinfo(self, host_bytes, port, family, socktype, proto, flags):
# dnspython really wants the host to be in native format.
if not isinstance(host_bytes, str):
host_bytes = host_bytes.decode(self.HOSTNAME_ENCODING)
if host_bytes == 'ff02::1de:c0:face:8D':
# This is essentially a hack to make stdlib
# test_socket:GeneralModuleTests.test_getaddrinfo_ipv6_basic
# pass. They expect to get back a lowercase ``D``, but
# dnspython does not do that.
# ``test_getaddrinfo_ipv6_scopeid_symbolic`` also expect
# the scopeid to be dropped, but again, dnspython does not
# do that; we cant fix that here so we skip that test.
host_bytes = 'ff02::1de:c0:face:8d'
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_bytes, port, family, socktype, proto, flags)
except gaierror:
try:
return _getaddrinfo(host_bytes, port, AF_INET6, socktype, proto, flags)
except gaierror:
return _getaddrinfo(host_bytes, port, AF_INET, socktype, proto, flags)
else:
try:
return _getaddrinfo(host_bytes, port, family, socktype, proto, flags)
except gaierror as ex:
if ex.args[0] == EAI_NONAME and family not in self._KNOWN_ADDR_FAMILIES:
# It's possible that we got sent an unsupported family. Check
# that.
ex.args = (EAI_FAMILY, self.EAI_FAMILY_MSG)
ex.errno = EAI_FAMILY
raise
def _getnameinfo(self, address_bytes, port, sockaddr, flags):
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_bytes):
try:
return resolver._gethostbyaddr(ip_address_bytes)
except gaierror as ex:
if ex.args[0] == EAI_NONAME:
# Note: The system doesn't *always* raise herror;
# sometimes the original gaierror propagates through.
# It's impossible to say ahead of time or just based
# on the name which it should be. The herror seems to
# be by far the most common, though.
raise herror(1, "Unknown host")
raise
# Things that need proper error handling
getnameinfo = AbstractResolver.fixup_gaierror(AbstractResolver.getnameinfo)
gethostbyaddr = AbstractResolver.fixup_gaierror(AbstractResolver.gethostbyaddr)
gethostbyname_ex = AbstractResolver.fixup_gaierror(AbstractResolver.gethostbyname_ex)
getaddrinfo = AbstractResolver.fixup_gaierror(AbstractResolver.getaddrinfo)

View File

@@ -0,0 +1,69 @@
# Copyright (c) 2012-2015 Denis Bilenko. See LICENSE for details.
"""
Native thread-based hostname resolver.
"""
import _socket
from gevent.hub import get_hub
__all__ = ['Resolver']
class Resolver(object):
"""
Implementation of the resolver API using native threads and native resolution
functions.
Using the native resolution mechanisms ensures the highest
compatibility with what a non-gevent program would return
including good support for platform specific configuration
mechanisms. The use of native (non-greenlet) threads ensures that
a caller doesn't block other greenlets.
This implementation also has the benefit of being very simple in comparison to
:class:`gevent.resolver_ares.Resolver`.
.. tip::
Most users find this resolver to be quite reliable in a
properly monkey-patched environment. However, there have been
some reports of long delays, slow performance or even hangs,
particularly in long-lived programs that make many, many DNS
requests. If you suspect that may be happening to you, try the
dnspython or ares resolver (and submit a bug report).
"""
def __init__(self, hub=None):
if hub is None:
hub = get_hub()
self.pool = hub.threadpool
if _socket.gaierror not in hub.NOT_ERROR:
# Do not cause lookup failures to get printed by the default
# error handler. This can be very noisy.
hub.NOT_ERROR += (_socket.gaierror, _socket.herror)
def __repr__(self):
return '<%s.%s at 0x%x pool=%r>' % (type(self).__module__,
type(self).__name__,
id(self), self.pool)
def close(self):
pass
# from briefly reading socketmodule.c, it seems that all of the functions
# below are thread-safe in Python, even if they are not thread-safe in C.
def gethostbyname(self, *args):
return self.pool.apply(_socket.gethostbyname, args)
def gethostbyname_ex(self, *args):
return self.pool.apply(_socket.gethostbyname_ex, args)
def getaddrinfo(self, *args, **kwargs):
return self.pool.apply(_socket.getaddrinfo, args, kwargs)
def gethostbyaddr(self, *args, **kwargs):
return self.pool.apply(_socket.gethostbyaddr, args, kwargs)
def getnameinfo(self, *args, **kwargs):
return self.pool.apply(_socket.getnameinfo, args, kwargs)