diff options
| author | Bob Halley <halley@dnspython.org> | 2020-07-20 06:28:14 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-20 06:28:14 -0700 |
| commit | a7604f891512ca99141c2068a4c57af45db62880 (patch) | |
| tree | 97cb68cbae33a06553d5dea348867f42dd1720f7 | |
| parent | 5cc5b3c7a435d5a9dd63691dd7f54babe65d1922 (diff) | |
| parent | 0a1a837e07016f63f88a52afc424a380a264d79e (diff) | |
| download | dnspython-a7604f891512ca99141c2068a4c57af45db62880.tar.gz | |
Merge pull request #542 from rthalley/canonical_name
Canonical name
| -rw-r--r-- | dns/asyncresolver.py | 30 | ||||
| -rw-r--r-- | dns/resolver.py | 34 | ||||
| -rw-r--r-- | doc/async-resolver-functions.rst | 1 | ||||
| -rw-r--r-- | doc/resolver-functions.rst | 1 | ||||
| -rw-r--r-- | tests/test_async.py | 20 | ||||
| -rw-r--r-- | tests/test_resolver.py | 14 |
6 files changed, 100 insertions, 0 deletions
diff --git a/dns/asyncresolver.py b/dns/asyncresolver.py index 3ac334f..0aaeb9c 100644 --- a/dns/asyncresolver.py +++ b/dns/asyncresolver.py @@ -165,6 +165,28 @@ class Resolver(dns.resolver.Resolver): rdclass=dns.rdataclass.IN, *args, **kwargs) + async def canonical_name(self, name): + """Determine the canonical name of *name*. + + The canonical name is the name the resolver uses for queries + after all CNAME and DNAME renamings have been applied. + + *name*, a ``dns.name.Name`` or ``str``, the query name. + + This method can raise any exception that ``resolve()`` can + raise, other than ``dns.resolver.NoAnswer`` and + ``dns.resolver.NXDOMAIN``. + + Returns a ``dns.name.Name``. + """ + try: + answer = await self.resolve(name, raise_on_no_answer=False) + canonical_name = answer.canonical_name + except dns.resolver.NXDOMAIN as e: + canonical_name = e.canonical_name + return canonical_name + + default_resolver = None @@ -212,6 +234,14 @@ async def resolve_address(ipaddr, *args, **kwargs): return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs) +async def canonical_name(name): + """Determine the canonical name of *name*. + + See ``dns.resolver.Resolver.canonical_name`` for more information on the + parameters and possible exceptions. + """ + + return await get_default_resolver().canonical_name(name) async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None, backend=None): diff --git a/dns/resolver.py b/dns/resolver.py index ec16376..bad24dc 100644 --- a/dns/resolver.py +++ b/dns/resolver.py @@ -81,6 +81,9 @@ class NXDOMAIN(dns.exception.DNSException): IN = dns.rdataclass.IN CNAME = dns.rdatatype.CNAME cname = None + # This code assumes the CNAME chain is in proper order, though + # the Answer code does not make a similar assumption when + # chaining. for qname in self.kwargs['qnames']: response = self.kwargs['responses'][qname] for answer in response.answer: @@ -1172,6 +1175,27 @@ class Resolver: rdclass=dns.rdataclass.IN, *args, **kwargs) + def canonical_name(self, name): + """Determine the canonical name of *name*. + + The canonical name is the name the resolver uses for queries + after all CNAME and DNAME renamings have been applied. + + *name*, a ``dns.name.Name`` or ``str``, the query name. + + This method can raise any exception that ``resolve()`` can + raise, other than ``dns.resolver.NoAnswer`` and + ``dns.resolver.NXDOMAIN``. + + Returns a ``dns.name.Name``. + """ + try: + answer = self.resolve(name, raise_on_no_answer=False) + canonical_name = answer.canonical_name + except dns.resolver.NXDOMAIN as e: + canonical_name = e.canonical_name + return canonical_name + def use_tsig(self, keyring, keyname=None, algorithm=dns.tsig.default_algorithm): """Add a TSIG signature to each query. @@ -1296,6 +1320,16 @@ def resolve_address(ipaddr, *args, **kwargs): return get_default_resolver().resolve_address(ipaddr, *args, **kwargs) +def canonical_name(name): + """Determine the canonical name of *name*. + + See ``dns.resolver.Resolver.canonical_name`` for more information on the + parameters and possible exceptions. + """ + + return get_default_resolver().canonical_name(name) + + def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): """Find the name of the zone which contains the specified name. diff --git a/doc/async-resolver-functions.rst b/doc/async-resolver-functions.rst index fc5798a..b2669f3 100644 --- a/doc/async-resolver-functions.rst +++ b/doc/async-resolver-functions.rst @@ -5,4 +5,5 @@ Asynchronous Resolver Functions .. autofunction:: dns.asyncresolver.resolve .. autofunction:: dns.asyncresolver.resolve_address +.. autofunction:: dns.asyncresolver.canonical_name .. autofunction:: dns.asyncresolver.zone_for_name diff --git a/doc/resolver-functions.rst b/doc/resolver-functions.rst index 6e57957..179484c 100644 --- a/doc/resolver-functions.rst +++ b/doc/resolver-functions.rst @@ -5,6 +5,7 @@ Resolver Functions and The Default Resolver .. autofunction:: dns.resolver.resolve .. autofunction:: dns.resolver.resolve_address +.. autofunction:: dns.resolver.canonical_name .. autofunction:: dns.resolver.zone_for_name .. autofunction:: dns.resolver.query .. autodata:: dns.resolver.default_resolver diff --git a/tests/test_async.py b/tests/test_async.py index db108c8..690a1eb 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -182,6 +182,26 @@ class AsyncTests(unittest.TestCase): dnsgoogle = dns.name.from_text('dns.google.') self.assertEqual(answer[0].target, dnsgoogle) + def testCanonicalNameNoCNAME(self): + cname = dns.name.from_text('www.google.com') + async def run(): + return await dns.asyncresolver.canonical_name('www.google.com') + self.assertEqual(self.async_run(run), cname) + + def testCanonicalNameCNAME(self): + name = dns.name.from_text('www.dnspython.org') + cname = dns.name.from_text('dmfrjf4ips8xa.cloudfront.net') + async def run(): + return await dns.asyncresolver.canonical_name(name) + self.assertEqual(self.async_run(run), cname) + + def testCanonicalNameDangling(self): + name = dns.name.from_text('dangling-cname.dnspython.org') + cname = dns.name.from_text('dangling-target.dnspython.org') + async def run(): + return await dns.asyncresolver.canonical_name(name) + self.assertEqual(self.async_run(run), cname) + def testResolverBadScheme(self): res = dns.asyncresolver.Resolver(configure=False) res.nameservers = ['bogus://dns.google/dns-query'] diff --git a/tests/test_resolver.py b/tests/test_resolver.py index b63ec19..31e49f8 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -510,6 +510,20 @@ class LiveResolverTests(unittest.TestCase): answer2 = res.resolve('dns.google.', 'A') self.assertIs(answer2, answer1) + def testCanonicalNameNoCNAME(self): + cname = dns.name.from_text('www.google.com') + self.assertEqual(dns.resolver.canonical_name('www.google.com'), cname) + + def testCanonicalNameCNAME(self): + name = dns.name.from_text('www.dnspython.org') + cname = dns.name.from_text('dmfrjf4ips8xa.cloudfront.net') + self.assertEqual(dns.resolver.canonical_name(name), cname) + + def testCanonicalNameDangling(self): + name = dns.name.from_text('dangling-cname.dnspython.org') + cname = dns.name.from_text('dangling-target.dnspython.org') + self.assertEqual(dns.resolver.canonical_name(name), cname) + class PollingMonkeyPatchMixin(object): def setUp(self): self.__native_selector_class = dns.query._selector_class |
