diff options
| author | Sergey Shepelev <temotor@gmail.com> | 2016-12-22 16:25:47 +0300 |
|---|---|---|
| committer | Sergey Shepelev <temotor@gmail.com> | 2016-12-22 16:33:55 +0300 |
| commit | f266be30f5c3ff1889e9ac3f0bad698a49d40e99 (patch) | |
| tree | a6e2a9f8d3f767f60b7e8c9d65a10ba8c83dc7ab | |
| parent | 3a8115c560e831a7f5442312529e18b41dc85b32 (diff) | |
| download | eventlet-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.py | 56 | ||||
| -rw-r--r-- | tests/greendns_test.py | 18 |
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) |
