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)
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
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))
|
||||
Executable
BIN
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