summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Shepelev <temotor@gmail.com>2016-12-22 16:25:47 +0300
committerSergey Shepelev <temotor@gmail.com>2016-12-22 16:33:55 +0300
commitf266be30f5c3ff1889e9ac3f0bad698a49d40e99 (patch)
treea6e2a9f8d3f767f60b7e8c9d65a10ba8c83dc7ab
parent3a8115c560e831a7f5442312529e18b41dc85b32 (diff)
downloadeventlet-issue-363.tar.gz
dns: try unqualified queries as top levelissue-363
`resolv.conf` docs say unqualified names must resolve from search (or local) domain. However, common OS `getaddrinfo()` implementations append trailing dot (e.g. `db -> db.`) and ask nameservers, as if top-level domain was queried. Eventlet now supports this behavior. https://github.com/eventlet/eventlet/issues/363
-rw-r--r--eventlet/support/greendns.py56
-rw-r--r--tests/greendns_test.py18
2 files changed, 64 insertions, 10 deletions
diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py
index f5326cd..c84b9c7 100644
--- a/eventlet/support/greendns.py
+++ b/eventlet/support/greendns.py
@@ -303,17 +303,57 @@ class ResolverProxy(object):
self._resolver.cache = dns.resolver.LRUCache()
def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
- tcp=False, source=None, raise_on_no_answer=True):
- """Query the resolver, using /etc/hosts if enabled"""
+ tcp=False, source=None, raise_on_no_answer=True,
+ _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA)):
+ """Query the resolver, using /etc/hosts if enabled.
+ """
+ result = [None, None, 0]
+
if qname is None:
qname = '0.0.0.0'
- if rdclass == dns.rdataclass.IN and self._hosts:
+ if isinstance(qname, six.string_types):
+ qname = dns.name.from_text(qname, None)
+
+ def step(fun, *args, **kwargs):
try:
- return self._hosts.query(qname, rdtype)
- except dns.resolver.NoAnswer:
- pass
- return self._resolver.query(qname, rdtype, rdclass,
- tcp, source, raise_on_no_answer)
+ a = fun(*args, **kwargs)
+ except Exception as e:
+ result[1] = e
+ return False
+ if a.rrset is not None and len(a.rrset):
+ if result[0] is None:
+ result[0] = a
+ else:
+ result[0].rrset.union_update(a.rrset)
+ result[2] += len(a.rrset)
+ return True
+
+ # Main query
+ step(self._resolver.query, qname, rdtype, rdclass, tcp, source, raise_on_no_answer=False)
+
+ # `resolv.conf` docs say unqualified names must resolve from search (or local) domain.
+ # However, common OS `getaddrinfo()` implementations append trailing dot (e.g. `db -> db.`)
+ # and ask nameservers, as if top-level domain was queried.
+ # This step follows established practice.
+ # https://github.com/nameko/nameko/issues/392
+ # https://github.com/eventlet/eventlet/issues/363
+ if len(qname) == 1:
+ step(self._resolver.query, qname.concatenate(dns.name.root),
+ rdtype, rdclass, tcp, source, raise_on_no_answer=False)
+
+ # Return answers from /etc/hosts despite nameserver errors
+ # https://github.com/eventlet/eventlet/pull/354
+ if result[2] == 0 and rdclass == dns.rdataclass.IN and rdtype in _hosts_rdtypes and self._hosts:
+ step(self._hosts.query, qname, rdtype, raise_on_no_answer=False)
+
+ if result[0] is not None:
+ if raise_on_no_answer and result[2] == 0:
+ raise dns.resolver.NoAnswer
+ return result[0]
+ if result[1] is not None:
+ if raise_on_no_answer or not isinstance(result[1], dns.resolver.NoAnswer):
+ raise result[1]
+ raise dns.resolver.NXDOMAIN(qnames=(qname,))
def getaliases(self, hostname):
"""Return a list of all the aliases of a given hostname"""
diff --git a/tests/greendns_test.py b/tests/greendns_test.py
index cf17999..7ec259f 100644
--- a/tests/greendns_test.py
+++ b/tests/greendns_test.py
@@ -6,10 +6,10 @@ import socket
import tempfile
import time
-import tests
-from tests import mock
from eventlet.support import greendns
from eventlet.support.greendns import dns
+import tests
+import tests.mock
class TestHostsResolver(tests.LimitedTestCase):
@@ -777,3 +777,17 @@ class TestGethostbyname_ex(tests.LimitedTestCase):
def test_reverse_name():
tests.run_isolated('greendns_from_address_203.py')
+
+
+def test_proxy_resolve_unqualified():
+ # https://github.com/eventlet/eventlet/issues/363
+ rp = greendns.ResolverProxy(filename=None)
+ rp._resolver.search.append(dns.name.from_text('example.com'))
+ with tests.mock.patch('dns.resolver.Resolver.query', side_effect=dns.resolver.NoAnswer) as m:
+ try:
+ rp.query('machine')
+ assert False, 'Expected NoAnswer exception'
+ except dns.resolver.NoAnswer:
+ pass
+ assert any(call[0][0] == dns.name.from_text('machine') for call in m.call_args_list)
+ assert any(call[0][0] == dns.name.from_text('machine.') for call in m.call_args_list)