new ip
This commit is contained in:
@@ -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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
Binary file not shown.
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user