Commit 5901babc authored by Jason Madden's avatar Jason Madden

Move dnshelper.c into cython and fix some test discrepencies for localhost.

parent 1ff01c9b
...@@ -95,7 +95,6 @@ ARES = Extension( ...@@ -95,7 +95,6 @@ ARES = Extension(
libraries=list(LIBRARIES), libraries=list(LIBRARIES),
define_macros=list(DEFINE_MACROS), define_macros=list(DEFINE_MACROS),
depends=glob_many( depends=glob_many(
'src/gevent/resolver/dnshelper.c',
'src/gevent/resolver/cares_*.[ch]') 'src/gevent/resolver/cares_*.[ch]')
) )
......
As part of this, certain parts of the c-ares extension were adapted to
use modern Cython idioms.
A few minor errors and discrepancies were fixed as well, such as
``gethostbyaddr(''localhost')`` working on Python 3 and failing on
Python 2.
...@@ -83,6 +83,10 @@ class Resolver(AbstractResolver): ...@@ -83,6 +83,10 @@ class Resolver(AbstractResolver):
results, and c-ares may report more ips on a multi-homed results, and c-ares may report more ips on a multi-homed
host. 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``.
.. caution:: .. caution::
This module is considered extremely experimental on PyPy, and This module is considered extremely experimental on PyPy, and
...@@ -175,11 +179,16 @@ class Resolver(AbstractResolver): ...@@ -175,11 +179,16 @@ class Resolver(AbstractResolver):
# pylint:disable=too-many-locals,too-many-branches # pylint:disable=too-many-locals,too-many-branches
if isinstance(host, text_type): if isinstance(host, text_type):
host = host.encode('idna') host = host.encode('idna')
elif not isinstance(host, str) or (flags & AI_NUMERICHOST): if not isinstance(host, bytes) or (flags & AI_NUMERICHOST) or host in (
b'localhost', b'ip6-localhost'):
# this handles cases which do not require network access # this handles cases which do not require network access
# 1) host is None # 1) host is None
# 2) host is of an invalid type # 2) host is of an invalid type
# 3) AI_NUMERICHOST flag is set # 3) AI_NUMERICHOST flag is set
# 4) It's a well-known alias. TODO: This is special casing 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.
return getaddrinfo(host, port, family, socktype, proto, flags) return getaddrinfo(host, port, family, socktype, proto, flags)
# we also call _socket.getaddrinfo below if family is not one of AF_* # we also call _socket.getaddrinfo below if family is not one of AF_*
......
...@@ -3,14 +3,17 @@ ...@@ -3,14 +3,17 @@
# seems to be buggy (at least for the `result` class) and produces code that # seems to be buggy (at least for the `result` class) and produces code that
# can't compile ("local variable 'result' referenced before assignment"). # can't compile ("local variable 'result' referenced before assignment").
# See https://github.com/cython/cython/issues/1786 # See https://github.com/cython/cython/issues/1786
# cython: auto_pickle=False # cython: auto_pickle=False,language_level=3str
cimport libcares as cares cimport libcares as cares
import sys import sys
from cpython.tuple cimport PyTuple_Check
from cpython.getargs cimport PyArg_ParseTuple
from cpython.ref cimport Py_INCREF from cpython.ref cimport Py_INCREF
from cpython.ref cimport Py_DECREF from cpython.ref cimport Py_DECREF
from cpython.mem cimport PyMem_Malloc from cpython.mem cimport PyMem_Malloc
from cpython.mem cimport PyMem_Free from cpython.mem cimport PyMem_Free
from libc.string cimport memset
from _socket import gaierror from _socket import gaierror
...@@ -32,14 +35,27 @@ TIMEOUT = 1 ...@@ -32,14 +35,27 @@ TIMEOUT = 1
DEF EV_READ = 1 DEF EV_READ = 1
DEF EV_WRITE = 2 DEF EV_WRITE = 2
cdef extern from *:
"""
#ifdef CARES_EMBED
#include "ares_setup.h"
#endif
cdef extern from "dnshelper.c": #ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
"""
cdef extern from "ares.h":
int AF_INET int AF_INET
int AF_INET6 int AF_INET6
int INET6_ADDRSTRLEN
struct hostent: struct hostent:
char* h_name char* h_name
int h_addrtype int h_addrtype
char** h_aliases
char** h_addr_list
struct sockaddr_t "sockaddr": struct sockaddr_t "sockaddr":
pass pass
...@@ -47,21 +63,27 @@ cdef extern from "dnshelper.c": ...@@ -47,21 +63,27 @@ cdef extern from "dnshelper.c":
struct ares_channeldata: struct ares_channeldata:
pass pass
object parse_h_name(hostent*) struct in_addr:
object parse_h_aliases(hostent*) unsigned int s_addr
object parse_h_addr_list(hostent*)
void* create_object_from_hostent(void*) struct sockaddr_in:
int sin_family
int sin_port
in_addr sin_addr
struct in6_addr:
char s6_addr[16]
# this imports _socket lazily
object PyUnicode_FromString(char*)
int PyTuple_Check(object)
int PyArg_ParseTuple(object, char*, ...) except 0
struct sockaddr_in6: struct sockaddr_in6:
pass int sin6_family
int gevent_make_sockaddr(char* hostp, int port, int flowinfo, int scope_id, sockaddr_in6* sa6) int sin6_port
unsigned int sin6_flowinfo
in6_addr sin6_addr
unsigned int sin6_scope_id
void memset(void*, int, int) unsigned int htons(unsigned int hostshort)
ARES_SUCCESS = cares.ARES_SUCCESS ARES_SUCCESS = cares.ARES_SUCCESS
...@@ -207,6 +229,43 @@ class ares_host_result(tuple): ...@@ -207,6 +229,43 @@ class ares_host_result(tuple):
return (self.family, tuple(self)) return (self.family, tuple(self))
cdef list _parse_h_aliases(hostent* host):
cdef list result = []
cdef char** aliases = host.h_aliases
if not aliases or not aliases[0]:
return result
while aliases[0]: # *aliases
# The old C version of this excluded an alias if
# it matched the host name. I don't think the stdlib does that?
result.append(_as_str(aliases[0]))
aliases += 1
return result
cdef list _parse_h_addr_list(hostent* host):
cdef list result = []
cdef char** addr_list = host.h_addr_list
cdef int addr_type = host.h_addrtype
# INET6_ADDRSTRLEN is 46, but we can't use that named constant
# here; cython doesn't like it.
cdef char tmpbuf[46]
if not addr_list or not addr_list[0]:
return result
while addr_list[0]:
if not cares.ares_inet_ntop(host.h_addrtype, addr_list[0], tmpbuf, INET6_ADDRSTRLEN):
import _socket
raise _socket.error("Failed in ares_inet_ntop")
result.append(_as_str(tmpbuf))
addr_list += 1
return result
cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent* host): cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent* host):
cdef channel channel cdef channel channel
cdef object callback cdef object callback
...@@ -218,7 +277,10 @@ cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent ...@@ -218,7 +277,10 @@ cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent
callback(result(None, gaierror(status, strerror(status)))) callback(result(None, gaierror(status, strerror(status))))
else: else:
try: try:
host_result = ares_host_result(host.h_addrtype, (parse_h_name(host), parse_h_aliases(host), parse_h_addr_list(host))) host_result = ares_host_result(host.h_addrtype,
(_as_str(host.h_name),
_parse_h_aliases(host),
_parse_h_addr_list(host)))
except: except:
callback(result(None, sys.exc_info()[1])) callback(result(None, sys.exc_info()[1]))
else: else:
...@@ -226,6 +288,16 @@ cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent ...@@ -226,6 +288,16 @@ cdef void gevent_ares_host_callback(void *arg, int status, int timeouts, hostent
except: except:
channel.loop.handle_error(callback, *sys.exc_info()) channel.loop.handle_error(callback, *sys.exc_info())
from cpython.version cimport PY_MAJOR_VERSION
cdef object _as_str(const char* val):
if not val:
return None
if PY_MAJOR_VERSION < 3:
return <bytes>val
return val.decode('utf-8')
cdef void gevent_ares_nameinfo_callback(void *arg, int status, int timeouts, char *c_node, char *c_service): cdef void gevent_ares_nameinfo_callback(void *arg, int status, int timeouts, char *c_node, char *c_service):
cdef channel channel cdef channel channel
...@@ -238,19 +310,30 @@ cdef void gevent_ares_nameinfo_callback(void *arg, int status, int timeouts, cha ...@@ -238,19 +310,30 @@ cdef void gevent_ares_nameinfo_callback(void *arg, int status, int timeouts, cha
if status: if status:
callback(result(None, gaierror(status, strerror(status)))) callback(result(None, gaierror(status, strerror(status))))
else: else:
if c_node: node = _as_str(c_node)
node = PyUnicode_FromString(c_node) service = _as_str(c_service)
else:
node = None
if c_service:
service = PyUnicode_FromString(c_service)
else:
service = None
callback(result((node, service))) callback(result((node, service)))
except: except:
channel.loop.handle_error(callback, *sys.exc_info()) channel.loop.handle_error(callback, *sys.exc_info())
cdef int _make_sockaddr(const char* hostp, int port, int flowinfo, int scope_id, sockaddr_in6* sa6):
if cares.ares_inet_pton(AF_INET, hostp, &(<sockaddr_in*>sa6).sin_addr.s_addr) > 0:
(<sockaddr_in*>sa6).sin_family = AF_INET
(<sockaddr_in*>sa6).sin_port = htons(port)
return sizeof(sockaddr_in)
if cares.ares_inet_pton(AF_INET6, hostp, &(sa6.sin6_addr).s6_addr) > 0:
sa6.sin6_family = AF_INET6
sa6.sin6_port = htons(port)
sa6.sin6_flowinfo = flowinfo
sa6.sin6_scope_id = scope_id
return sizeof(sockaddr_in6);
return -1;
cdef class channel: cdef class channel:
cdef public object loop cdef public object loop
...@@ -447,7 +530,7 @@ cdef class channel: ...@@ -447,7 +530,7 @@ cdef class channel:
PyArg_ParseTuple(sockaddr, "si|ii", &hostp, &port, &flowinfo, &scope_id) PyArg_ParseTuple(sockaddr, "si|ii", &hostp, &port, &flowinfo, &scope_id)
if port < 0 or port > 65535: if port < 0 or port > 65535:
raise gaierror(-8, 'Invalid value for port: %r' % port) raise gaierror(-8, 'Invalid value for port: %r' % port)
cdef int length = gevent_make_sockaddr(hostp, port, flowinfo, scope_id, &sa6) cdef int length = _make_sockaddr(hostp, port, flowinfo, scope_id, &sa6)
if length <= 0: if length <= 0:
raise InvalidIP(repr(hostp)) raise InvalidIP(repr(hostp))
cdef object arg = (self, callback) cdef object arg = (self, callback)
......
/* Copyright (c) 2011 Denis Bilenko. See LICENSE for details. */
#include "Python.h"
#ifdef CARES_EMBED
#include "ares_setup.h"
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#include "ares.h"
#if PY_MAJOR_VERSION >= 3
#define PY3K
#define GPyNative_FromString PyUnicode_FromString
#else
#define GPyNative_FromString PyString_FromString
#endif
static PyObject* _socket_error = 0;
static PyObject*
get_socket_object(PyObject** pobject, const char* name)
{
if (!*pobject) {
PyObject* _socket;
_socket = PyImport_ImportModule("_socket");
if (_socket) {
*pobject = PyObject_GetAttrString(_socket, name);
if (!*pobject) {
PyErr_WriteUnraisable(Py_None);
}
Py_DECREF(_socket);
}
else {
PyErr_WriteUnraisable(Py_None);
}
if (!*pobject) {
*pobject = PyExc_IOError;
}
}
return *pobject;
}
static int
gevent_append_addr(PyObject* list, int family, void* src, char* tmpbuf, size_t tmpsize) {
int status = -1;
PyObject* tmp;
if (ares_inet_ntop(family, src, tmpbuf, tmpsize)) {
tmp = GPyNative_FromString(tmpbuf);
if (tmp) {
status = PyList_Append(list, tmp);
Py_DECREF(tmp);
}
}
return status;
}
static PyObject*
parse_h_name(struct hostent *h)
{
return GPyNative_FromString(h->h_name);
}
static PyObject*
parse_h_aliases(struct hostent *h)
{
char **pch;
PyObject *result = NULL;
PyObject *tmp;
result = PyList_New(0);
if (result && h->h_aliases) {
for (pch = h->h_aliases; *pch != NULL; pch++) {
if (*pch != h->h_name && strcmp(*pch, h->h_name)) {
int status;
tmp = GPyNative_FromString(*pch);
if (tmp == NULL) {
break;
}
status = PyList_Append(result, tmp);
Py_DECREF(tmp);
if (status) {
break;
}
}
}
}
return result;
}
static PyObject *
parse_h_addr_list(struct hostent *h)
{
char **pch;
PyObject *result = NULL;
result = PyList_New(0);
if (result) {
switch (h->h_addrtype) {
case AF_INET:
{
char tmpbuf[sizeof "255.255.255.255"];
for (pch = h->h_addr_list; *pch != NULL; pch++) {
if (gevent_append_addr(result, AF_INET, *pch, tmpbuf, sizeof(tmpbuf))) {
break;
}
}
break;
}
case AF_INET6:
{
char tmpbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
for (pch = h->h_addr_list; *pch != NULL; pch++) {
if (gevent_append_addr(result, AF_INET6, *pch, tmpbuf, sizeof(tmpbuf))) {
break;
}
}
break;
}
default:
PyErr_SetString(get_socket_object(&_socket_error, "error"), "unsupported address family");
Py_DECREF(result);
result = NULL;
}
}
return result;
}
static int
gevent_make_sockaddr(char* hostp, int port, int flowinfo, int scope_id, struct sockaddr_in6* sa6) {
if ( ares_inet_pton(AF_INET, hostp, &((struct sockaddr_in*)sa6)->sin_addr.s_addr) > 0 ) {
((struct sockaddr_in*)sa6)->sin_family = AF_INET;
((struct sockaddr_in*)sa6)->sin_port = htons(port);
return sizeof(struct sockaddr_in);
}
else if ( ares_inet_pton(AF_INET6, hostp, &sa6->sin6_addr.s6_addr) > 0 ) {
sa6->sin6_family = AF_INET6;
sa6->sin6_port = htons(port);
sa6->sin6_flowinfo = flowinfo;
sa6->sin6_scope_id = scope_id;
return sizeof(struct sockaddr_in6);
}
return -1;
}
...@@ -139,11 +139,6 @@ cdef extern from "ares.h": ...@@ -139,11 +139,6 @@ cdef extern from "ares.h":
ares_addrinfo_cname *cnames ares_addrinfo_cname *cnames
ares_addrinfo_node *nodes ares_addrinfo_node *nodes
# typedef void (*ares_addrinfo_callback)(
# void *arg, int status,
# int timeouts,
# ares_addrinfo *result)
void ares_getaddrinfo( void ares_getaddrinfo(
void* channel, void* channel,
const char *name, const char *name,
...@@ -156,3 +151,4 @@ cdef extern from "ares.h": ...@@ -156,3 +151,4 @@ cdef extern from "ares.h":
void ares_freeaddrinfo(ares_addrinfo *ai) void ares_freeaddrinfo(ares_addrinfo *ai)
int ares_inet_pton(int af, const char *src, void *dst) int ares_inet_pton(int af, const char *src, void *dst)
const char* ares_inet_ntop(int af, const void *src, char *dst, ares_socklen_t size);
...@@ -327,13 +327,18 @@ class TestCase(greentest.TestCase): ...@@ -327,13 +327,18 @@ class TestCase(greentest.TestCase):
result.sort() result.sort()
return result return result
def _normalize_result_getnameinfo(self, result):
return result
NORMALIZE_GHBA_IGNORE_ALIAS = False
def _normalize_result_gethostbyaddr(self, result): def _normalize_result_gethostbyaddr(self, result):
if not RESOLVER_NOT_SYSTEM: if not RESOLVER_NOT_SYSTEM:
return result return result
if isinstance(result, tuple): if self.NORMALIZE_GHBA_IGNORE_ALIAS and isinstance(result, tuple):
# On some systems, a random alias is found in the aliaslist # On some systems, a random alias is found in the aliaslist
# by the system resolver, but not by cares and vice versa. We deem the aliaslist # by the system resolver, but not by cares and vice versa. This is *probably* only the
# case for localhost or things otherwise in /etc/hosts. We deem the aliaslist
# unimportant and discard it. # unimportant and discard it.
return (result[0], [], result[2]) return (result[0], [], result[2])
return result return result
...@@ -381,9 +386,9 @@ add(TestTypeError, 25) ...@@ -381,9 +386,9 @@ add(TestTypeError, 25)
class TestHostname(TestCase): class TestHostname(TestCase):
NORMALIZE_GHBA_IGNORE_ALIAS = True
def _normalize_result_gethostbyaddr(self, result): def _ares_normalize_name(self, result):
result = TestCase._normalize_result_gethostbyaddr(self, result)
if RESOLVER_ARES and isinstance(result, tuple): if RESOLVER_ARES and isinstance(result, tuple):
# The system resolver can return the FQDN, in the first result, # The system resolver can return the FQDN, in the first result,
# when given certain configurations. But c-ares # when given certain configurations. But c-ares
...@@ -392,6 +397,16 @@ class TestHostname(TestCase): ...@@ -392,6 +397,16 @@ class TestHostname(TestCase):
name = name.split('.', 1)[0] name = name.split('.', 1)[0]
result = (name,) + result[1:] result = (name,) + result[1:]
return result return result
def _normalize_result_gethostbyaddr(self, result):
result = TestCase._normalize_result_gethostbyaddr(self, result)
return self._ares_normalize_name(result)
def _normalize_result_getnameinfo(self, result):
result = TestCase._normalize_result_getnameinfo(self, result)
if PY2:
# Not sure why we only saw this on Python 2
result = self._ares_normalize_name(result)
return result
add( add(
TestHostname, TestHostname,
...@@ -417,6 +432,7 @@ class TestLocalhost(TestCase): ...@@ -417,6 +432,7 @@ class TestLocalhost(TestCase):
return () return ()
return super(TestLocalhost, self)._normalize_result_getaddrinfo(result) return super(TestLocalhost, self)._normalize_result_getaddrinfo(result)
NORMALIZE_GHBA_IGNORE_ALIAS = True
if greentest.RUNNING_ON_TRAVIS and greentest.PY2 and RESOLVER_NOT_SYSTEM: if greentest.RUNNING_ON_TRAVIS and greentest.PY2 and RESOLVER_NOT_SYSTEM:
def _normalize_result_gethostbyaddr(self, result): def _normalize_result_gethostbyaddr(self, result):
# Beginning in November 2017 after an upgrade to Travis, # Beginning in November 2017 after an upgrade to Travis,
...@@ -455,7 +471,7 @@ add(Test1234, '1.2.3.4') ...@@ -455,7 +471,7 @@ add(Test1234, '1.2.3.4')
class Test127001(TestCase): class Test127001(TestCase):
pass NORMALIZE_GHBA_IGNORE_ALIAS = True
add( add(
Test127001, '127.0.0.1', Test127001, '127.0.0.1',
......
...@@ -36,7 +36,7 @@ from gevent.testing.sysinfo import RESOLVER_DNSPYTHON ...@@ -36,7 +36,7 @@ from gevent.testing.sysinfo import RESOLVER_DNSPYTHON
# by default we skip the tests everywhere else. # by default we skip the tests everywhere else.
class Test6(TestCase): class Test6(TestCase):
NORMALIZE_GHBA_IGNORE_ALIAS = True
# host that only has AAAA record # host that only has AAAA record
host = 'aaaa.test-ipv6.com' host = 'aaaa.test-ipv6.com'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment