dns: try unqualified queries as top level
`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
This commit is contained in:
@@ -303,17 +303,58 @@ class ResolverProxy(object):
|
|||||||
self._resolver.cache = dns.resolver.LRUCache()
|
self._resolver.cache = dns.resolver.LRUCache()
|
||||||
|
|
||||||
def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
|
def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
|
||||||
tcp=False, source=None, raise_on_no_answer=True):
|
tcp=False, source=None, raise_on_no_answer=True,
|
||||||
"""Query the resolver, using /etc/hosts if enabled"""
|
_hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA)):
|
||||||
|
"""Query the resolver, using /etc/hosts if enabled.
|
||||||
|
"""
|
||||||
|
result = [None, None, 0]
|
||||||
|
|
||||||
if qname is None:
|
if qname is None:
|
||||||
qname = '0.0.0.0'
|
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:
|
try:
|
||||||
return self._hosts.query(qname, rdtype)
|
a = fun(*args, **kwargs)
|
||||||
except dns.resolver.NoAnswer:
|
except Exception as e:
|
||||||
pass
|
result[1] = e
|
||||||
return self._resolver.query(qname, rdtype, rdclass,
|
return False
|
||||||
tcp, source, raise_on_no_answer)
|
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 self._hosts and
|
||||||
|
(rdclass == dns.rdataclass.IN) and (rdtype in _hosts_rdtypes)):
|
||||||
|
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):
|
def getaliases(self, hostname):
|
||||||
"""Return a list of all the aliases of a given hostname"""
|
"""Return a list of all the aliases of a given hostname"""
|
||||||
|
@@ -6,10 +6,10 @@ import socket
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import tests
|
|
||||||
from tests import mock
|
|
||||||
from eventlet.support import greendns
|
from eventlet.support import greendns
|
||||||
from eventlet.support.greendns import dns
|
from eventlet.support.greendns import dns
|
||||||
|
import tests
|
||||||
|
import tests.mock
|
||||||
|
|
||||||
|
|
||||||
class TestHostsResolver(tests.LimitedTestCase):
|
class TestHostsResolver(tests.LimitedTestCase):
|
||||||
@@ -777,3 +777,17 @@ class TestGethostbyname_ex(tests.LimitedTestCase):
|
|||||||
|
|
||||||
def test_reverse_name():
|
def test_reverse_name():
|
||||||
tests.run_isolated('greendns_from_address_203.py')
|
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)
|
||||||
|
Reference in New Issue
Block a user