Commit f9ad8e95 authored by Jason Madden's avatar Jason Madden

Implement canonical names for c-ares

parent f20e334a
......@@ -90,6 +90,9 @@ class Resolver(AbstractResolver):
``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.
.. caution::
This module is considered extremely experimental on PyPy, and
......@@ -102,6 +105,10 @@ class Resolver(AbstractResolver):
resolved <https://github.com/c-ares/c-ares/issues/196>`_ or even
sent to the DNS server.
.. versionchanged:: NEXT
``getaddrinfo`` is now implemented using the native c-ares function
from c-ares 1.16 or newer.
.. _c-ares: http://c-ares.haxx.se
"""
......@@ -186,8 +193,6 @@ class Resolver(AbstractResolver):
"""
Returns a list ``(family, socktype, proto, canonname, sockaddr)``
TODO: Implement canonical names.
:raises gaierror: If no results are found.
"""
# pylint:disable=too-many-locals,too-many-branches
......
......@@ -535,8 +535,11 @@ cdef class channel:
int timeouts,
cares.ares_addrinfo* result):
cdef cares.ares_addrinfo_node* nodes
cdef cares.ares_addrinfo_cname* cnames
cdef sockaddr_in* sadr4
cdef sockaddr_in6* sadr6
cdef object canonname = ''
cdef channel channel
cdef object callback
# INET6_ADDRSTRLEN is 46, but we can't use that named constant
......@@ -559,6 +562,19 @@ cdef class channel:
if status != cares.ARES_SUCCESS:
callback(Result(None, gaierror(status, strerror(status))))
return
if result.cnames:
# These tend to come in pairs:
#
# alias: www.gevent.org name: python-gevent.readthedocs.org
# alias: python-gevent.readthedocs.org name: readthedocs.io
#
# The standard library returns the last name so we do too.
cnames = result.cnames
while cnames:
canonname = _as_str(cnames.name)
cnames = cnames.next
nodes = result.nodes
while nodes:
if nodes.ai_family == AF_INET:
......@@ -584,7 +600,7 @@ cdef class channel:
nodes.ai_family,
nodes.ai_socktype,
nodes.ai_protocol,
'',
canonname,
sockaddr,
))
nodes = nodes.ai_next
......
......@@ -330,16 +330,25 @@ class TestCase(greentest.TestCase):
# On some systems, the hostname can get caps
return (result[0].lower(), [], ips)
def _normalize_result_getaddrinfo(self, result):
if not RESOLVER_NOT_SYSTEM:
IGNORE_CANONICAL_NAME = RESOLVER_ARES # It tends to return them even when not asked for
if not RESOLVER_NOT_SYSTEM:
def _normalize_result_getaddrinfo(self, result):
return result
else:
def _normalize_result_getaddrinfo(self, result):
# On Python 3, the builtin resolver can return SOCK_RAW results, but
# c-ares doesn't do that. So we remove those if we find them.
if hasattr(socket, 'SOCK_RAW') and isinstance(result, list):
result = [x for x in result if x[1] != socket.SOCK_RAW]
if self.IGNORE_CANONICAL_NAME:
result = [
(family, kind, proto, '', addr)
for family, kind, proto, _, addr
in result
]
if isinstance(result, list):
result.sort()
return result
# On Python 3, the builtin resolver can return SOCK_RAW results, but
# c-ares doesn't do that. So we remove those if we find them.
if hasattr(socket, 'SOCK_RAW') and isinstance(result, list):
result = [x for x in result if x[1] != socket.SOCK_RAW]
if isinstance(result, list):
result.sort()
return result
def _normalize_result_getnameinfo(self, result):
return result
......@@ -583,6 +592,22 @@ class TestGeventOrg(TestCase):
result = ('readthedocs.io', ) + result[1:]
return result
def test_AI_CANONNAME(self):
self.IGNORE_CANONICAL_NAME = False
result = self._test('getaddrinfo',
# host
TestGeventOrg.HOSTNAME,
# port
None,
# family
socket.AF_INET,
# type
0,
# proto
0,
# flags
socket.AI_CANONNAME)
self.assertEqual(result[0][3], 'readthedocs.io')
add(TestGeventOrg, TestGeventOrg.HOSTNAME)
......
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