diff --git a/aiodnsresolver.py b/aiodnsresolver.py index 75febcd..f936410 100644 --- a/aiodnsresolver.py +++ b/aiodnsresolver.py @@ -99,6 +99,10 @@ class DnsRecordDoesNotExist(DnsError): pass +class DnsNoMatchingAnswers(DnsRecordDoesNotExist): + pass + + class DnsPointerLoop(DnsError): pass @@ -153,7 +157,7 @@ def pack_name(name): return b''.join( bytes((len(part),)) + part for part in name.split(b'.') - ) + b'\0' + ) + b'\0' if name else b'\0' def pack_resource(record): rdata = \ @@ -589,11 +593,12 @@ async def req(): last_exception = DnsResponseCode(res.rcode) set_timeout_cause(last_exception) logger.debug('Error from %s', addr_port) - elif name_error or (not cname_answers and not qtype_answers): - # a name error can be returned by some non-authoritative - # servers on not-existing, contradicting RFC 1035 + elif name_error: logger.debug('Record not found from %s', addr_port) raise DnsRecordDoesNotExist() + elif not cname_answers and not qtype_answers: + logger.debug('No answers from %s', addr_port) + raise DnsNoMatchingAnswers() else: return cname_answers, qtype_answers diff --git a/test.py b/test.py index e7b7c5d..009842d 100644 --- a/test.py +++ b/test.py @@ -21,6 +21,7 @@ TYPES, DnsCnameChainTooLong, DnsError, + DnsNoMatchingAnswers, DnsRecordDoesNotExist, DnsSocketError, DnsTimeout, @@ -1772,6 +1773,18 @@ async def test_localhost_aaaa(self): self.assertEqual(str(res[0]), '::1') self.assertEqual(res[0].expires_at, loop.time()) + @async_test + async def test_root_servers_queries(self): + # Upstream DNS server should return NOERROR status with an empty `A` records set. + # The default Github's does not do this, so we need to switch to Google's server. + self.addCleanup(patch_open(upstream_ip='8.8.8.8')) + + resolve, _ = Resolver() + with self.assertRaises(DnsNoMatchingAnswers): + await resolve('', TYPES.A) + with self.assertRaises(DnsNoMatchingAnswers): + await resolve('root-servers.net', TYPES.A) + class TestMemoizedMutex(unittest.TestCase): @async_test @@ -1808,11 +1821,11 @@ async def func(): self.assertEqual(await task3, 42) -def patch_open(): +def patch_open(upstream_ip='127.0.0.1'): def mock_open(file_name, _): lines = \ - ['127.0.0.1 localhost'] if file_name == '/etc/hosts' else \ - ['nameserver 127.0.0.1'] + [f'{upstream_ip} localhost'] if file_name == '/etc/hosts' else \ + [f'nameserver {upstream_ip}'] context_manager = MagicMock() context_manager.__enter__.return_value = lines