greendns: Return answers from /etc/hosts despite nameserver errors

When calling getaddrinfo with an unspecified address family, we:

 * try to get an IPv6 address for the host, first trying /etc/hosts and
   failing that, using any external nameserver, then
 * try to get an IPv4 address for it, with the same /etc/hosts ->
   external nameserver priority.

Note that if the host can be resolved using /etc/hosts but only to one
of the two address families, we still attempt to contact the external
nameserver to resolve it in the other family.

Previously, if this timed out or raised a DNSException other than
NXDOMAIN, EAI_EAGAIN_ERROR or EAI_NODATA_ERROR would be raised even
though we could resolve the host. Now, those errors will only be raised
if neither attempt at resolution succeeded.

https://github.com/eventlet/eventlet/pull/354
This commit is contained in:
Tim Burke 2016-11-08 21:39:16 -08:00 committed by Sergey Shepelev
parent 4872be7700
commit f508a7e330
2 changed files with 68 additions and 3 deletions

View File

@ -402,10 +402,19 @@ def _getaddrinfo_lookup(host, family, flags):
raise EAI_NONAME_ERROR
addrs = []
if family == socket.AF_UNSPEC:
err = None
for qfamily in [socket.AF_INET6, socket.AF_INET]:
answer = resolve(host, qfamily, False)
if answer.rrset:
addrs.extend([rr.address for rr in answer.rrset])
try:
answer = resolve(host, qfamily, False)
except socket.gaierror as e:
if e.errno not in (socket.EAI_AGAIN, socket.EAI_NODATA):
raise
err = e
else:
if answer.rrset:
addrs.extend(rr.address for rr in answer.rrset)
if err is not None and not addrs:
raise err
elif family == socket.AF_INET6 and flags & socket.AI_V4MAPPED:
answer = resolve(host, socket.AF_INET6, False)
if answer.rrset:

View File

@ -517,6 +517,62 @@ class TestGetaddrinfo(tests.LimitedTestCase):
addr = [('dead:beef::1', 0, 0, 0)] * len(res)
assert addr == [ai[-1] for ai in res]
def test_getaddrinfo_hosts_only_ans_with_timeout(self):
def clear_raises(res_self):
res_self.raises = None
return greendns.dns.resolver.NoAnswer()
hostsres = _make_mock_base_resolver()
hostsres.raises = clear_raises
hostsres.rr.address = '1.2.3.4'
greendns.resolver = greendns.ResolverProxy(hostsres())
res = _make_mock_base_resolver()
res.raises = greendns.dns.exception.Timeout
greendns.resolver._resolver = res()
result = greendns.getaddrinfo('example.com', 0, 0)
addr = [('1.2.3.4', 0)] * len(result)
assert addr == [ai[-1] for ai in result]
def test_getaddrinfo_hosts_only_ans_with_error(self):
def clear_raises(res_self):
res_self.raises = None
return greendns.dns.resolver.NoAnswer()
hostsres = _make_mock_base_resolver()
hostsres.raises = clear_raises
hostsres.rr.address = '1.2.3.4'
greendns.resolver = greendns.ResolverProxy(hostsres())
res = _make_mock_base_resolver()
res.raises = greendns.dns.exception.DNSException
greendns.resolver._resolver = res()
result = greendns.getaddrinfo('example.com', 0, 0)
addr = [('1.2.3.4', 0)] * len(result)
assert addr == [ai[-1] for ai in result]
def test_getaddrinfo_hosts_only_timeout(self):
hostsres = _make_mock_base_resolver()
hostsres.raises = greendns.dns.resolver.NoAnswer
greendns.resolver = greendns.ResolverProxy(hostsres())
res = _make_mock_base_resolver()
res.raises = greendns.dns.exception.Timeout
greendns.resolver._resolver = res()
with tests.assert_raises(socket.gaierror):
greendns.getaddrinfo('example.com', 0, 0)
def test_getaddrinfo_hosts_only_dns_error(self):
hostsres = _make_mock_base_resolver()
hostsres.raises = greendns.dns.resolver.NoAnswer
greendns.resolver = greendns.ResolverProxy(hostsres())
res = _make_mock_base_resolver()
res.raises = greendns.dns.exception.DNSException
greendns.resolver._resolver = res()
with tests.assert_raises(socket.gaierror):
greendns.getaddrinfo('example.com', 0, 0)
def test_canonname(self):
greendns.resolve = _make_mock_resolve()
greendns.resolve.add('host.example.com', '1.2.3.4')