summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Halley <halley@dnspython.org>2020-07-20 06:28:14 -0700
committerGitHub <noreply@github.com>2020-07-20 06:28:14 -0700
commita7604f891512ca99141c2068a4c57af45db62880 (patch)
tree97cb68cbae33a06553d5dea348867f42dd1720f7
parent5cc5b3c7a435d5a9dd63691dd7f54babe65d1922 (diff)
parent0a1a837e07016f63f88a52afc424a380a264d79e (diff)
downloaddnspython-a7604f891512ca99141c2068a4c57af45db62880.tar.gz
Merge pull request #542 from rthalley/canonical_name
Canonical name
-rw-r--r--dns/asyncresolver.py30
-rw-r--r--dns/resolver.py34
-rw-r--r--doc/async-resolver-functions.rst1
-rw-r--r--doc/resolver-functions.rst1
-rw-r--r--tests/test_async.py20
-rw-r--r--tests/test_resolver.py14
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