diff options
author | Bob Halley <halley@dnspython.org> | 2023-04-19 08:51:16 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-19 08:51:16 -0700 |
commit | 6daff0efc931aca94b75fadf57fcfa95da9fce1b (patch) | |
tree | e3d450e973e3d60479bcc5201d653b61e91be5ce | |
parent | 4783033f4365a9e9cd55e01bc6839f7df2cfd456 (diff) | |
download | dnspython-6daff0efc931aca94b75fadf57fcfa95da9fce1b.tar.gz |
Add make_resolver_at() and resolve_at(). (#926)
-rw-r--r-- | dns/asyncresolver.py | 81 | ||||
-rw-r--r-- | dns/resolver.py | 77 | ||||
-rw-r--r-- | doc/async-resolver-functions.rst | 2 | ||||
-rw-r--r-- | doc/resolver-functions.rst | 2 | ||||
-rw-r--r-- | doc/whatsnew.rst | 4 | ||||
-rw-r--r-- | examples/async_dns.py | 4 | ||||
-rw-r--r-- | examples/query_specific.py | 23 | ||||
-rw-r--r-- | tests/test_async.py | 22 | ||||
-rw-r--r-- | tests/test_resolver.py | 16 |
9 files changed, 227 insertions, 4 deletions
diff --git a/dns/asyncresolver.py b/dns/asyncresolver.py index aa8af7c..a78f2d6 100644 --- a/dns/asyncresolver.py +++ b/dns/asyncresolver.py @@ -394,3 +394,84 @@ async def zone_for_name( name = name.parent() except dns.name.NoParent: # pragma: no cover raise NoRootSOA + + +async def make_resolver_at( + where: Union[dns.name.Name, str], + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Optional[Resolver] = None, +) -> Resolver: + """Make a stub resolver using the specified destination as the full resolver. + + *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the + full resolver. + + *port*, an ``int``, the port to use. If not specified, the default is 53. + + *family*, an ``int``, the address family to use. This parameter is used if + *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case + the first address returned by ``resolve_name()`` will be used, otherwise the + first address of the specified family will be used. + + *resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the resolver to use for + resolution of hostnames. If not specified, the default resolver will be used. + + Returns a ``dns.resolver.Resolver`` or raises an exception. + """ + if resolver is None: + resolver = get_default_resolver() + nameservers = [] + if isinstance(where, str) and dns.inet.is_address(where): + nameservers.append(dns.nameserver.Do53Nameserver(where, port)) + else: + answers = await resolver.resolve_name(where, family) + for address in answers.addresses(): + nameservers.append(dns.nameserver.Do53Nameserver(address, port)) + res = dns.asyncresolver.Resolver(configure=False) + res.nameservers = nameservers + return res + + +async def resolve_at( + where: Union[dns.name.Name, str], + qname: Union[dns.name.Name, str], + rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, + rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, + tcp: bool = False, + source: Optional[str] = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: Optional[float] = None, + search: Optional[bool] = None, + backend: Optional[dns.asyncbackend.Backend] = None, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Optional[Resolver] = None, +) -> dns.resolver.Answer: + """Query nameservers to find the answer to the question. + + This is a convenience function that calls ``dns.asyncresolver.make_resolver_at()`` + to make a resolver, and then uses it to resolve the query. + + See ``dns.asyncresolver.Resolver.resolve`` for more information on the resolution + parameters, and ``dns.asyncresolver.make_resolver_at`` for information about the + resolver parameters *where*, *port*, *family*, and *resolver*. + + If making more than one query, it is more efficient to call + ``dns.asyncresolver.make_resolver_at()`` and then use that resolver for the queries + instead of calling ``resolve_at()`` multiple times. + """ + res = await make_resolver_at(where, port, family, resolver) + return await res.resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + backend, + ) diff --git a/dns/resolver.py b/dns/resolver.py index c28c137..f12d997 100644 --- a/dns/resolver.py +++ b/dns/resolver.py @@ -1736,6 +1736,83 @@ def zone_for_name( raise NoRootSOA +def make_resolver_at( + where: Union[dns.name.Name, str], + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Optional[Resolver] = None, +) -> Resolver: + """Make a stub resolver using the specified destination as the full resolver. + + *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the + full resolver. + + *port*, an ``int``, the port to use. If not specified, the default is 53. + + *family*, an ``int``, the address family to use. This parameter is used if + *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case + the first address returned by ``resolve_name()`` will be used, otherwise the + first address of the specified family will be used. + + *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use for + resolution of hostnames. If not specified, the default resolver will be used. + + Returns a ``dns.resolver.Resolver`` or raises an exception. + """ + if resolver is None: + resolver = get_default_resolver() + nameservers = [] + if isinstance(where, str) and dns.inet.is_address(where): + nameservers.append(dns.nameserver.Do53Nameserver(where, port)) + else: + for address in resolver.resolve_name(where, family).addresses(): + nameservers.append(dns.nameserver.Do53Nameserver(address, port)) + res = dns.resolver.Resolver(configure=False) + res.nameservers = nameservers + return res + + +def resolve_at( + where: Union[dns.name.Name, str], + qname: Union[dns.name.Name, str], + rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, + rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, + tcp: bool = False, + source: Optional[str] = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: Optional[float] = None, + search: Optional[bool] = None, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Optional[Resolver] = None, +) -> Answer: + """Query nameservers to find the answer to the question. + + This is a convenience function that calls ``dns.resolver.make_resolver_at()`` to + make a resolver, and then uses it to resolve the query. + + See ``dns.resolver.Resolver.resolve`` for more information on the resolution + parameters, and ``dns.resolver.make_resolver_at`` for information about the resolver + parameters *where*, *port*, *family*, and *resolver*. + + If making more than one query, it is more efficient to call + ``dns.resolver.make_resolver_at()`` and then use that resolver for the queries + instead of calling ``resolve_at()`` multiple times. + """ + return make_resolver_at(where, port, family, resolver).resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + ) + + # # Support for overriding the system resolver for all python code in the # running process. diff --git a/doc/async-resolver-functions.rst b/doc/async-resolver-functions.rst index c79d581..8e81dea 100644 --- a/doc/async-resolver-functions.rst +++ b/doc/async-resolver-functions.rst @@ -9,6 +9,8 @@ Asynchronous Resolver Functions .. autofunction:: dns.asyncresolver.canonical_name .. autofunction:: dns.asyncresolver.try_ddr .. autofunction:: dns.asyncresolver.zone_for_name +.. autofunction:: dns.asyncresolver.make_resolver_at +.. autofunction:: dns.asyncresolver.resolve_at .. autodata:: dns.asyncresolver.default_resolver .. autofunction:: dns.asyncresolver.get_default_resolver .. autofunction:: dns.asyncresolver.reset_default_resolver diff --git a/doc/resolver-functions.rst b/doc/resolver-functions.rst index 0399a0b..531ca78 100644 --- a/doc/resolver-functions.rst +++ b/doc/resolver-functions.rst @@ -10,6 +10,8 @@ Resolver Functions and The Default Resolver .. autofunction:: dns.resolver.try_ddr .. autofunction:: dns.resolver.zone_for_name .. autofunction:: dns.resolver.query +.. autofunction:: dns.resolver.make_resolver_at +.. autofunction:: dns.resolver.resolve_at .. autodata:: dns.resolver.default_resolver .. autofunction:: dns.resolver.get_default_resolver .. autofunction:: dns.resolver.reset_default_resolver diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 3d42c8d..ac5548d 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -28,6 +28,10 @@ What's New in dnspython DNS-over-QUIC. This feature is currently experimental as the standard is still in draft stage. +* The resolver and async resolver now have the ``make_resolver_at()`` and + ``resolve_at()`` functions, as a convenience for making queries to specific + recursive servers. + * Curio support has been removed. 2.3.0 diff --git a/examples/async_dns.py b/examples/async_dns.py index f7e3fe5..297afcb 100644 --- a/examples/async_dns.py +++ b/examples/async_dns.py @@ -25,6 +25,10 @@ async def main(): print(a.response) zn = await dns.asyncresolver.zone_for_name(host) print(zn) + answer = await dns.asyncresolver.resolve_at("8.8.8.8", "amazon.com", "NS") + print("The amazon.com nameservers are:") + for rr in answer: + print(rr.target) if __name__ == "__main__": diff --git a/examples/query_specific.py b/examples/query_specific.py index 73dc351..2f13b24 100644 --- a/examples/query_specific.py +++ b/examples/query_specific.py @@ -25,16 +25,31 @@ for rr in ns_rrset: print("") print("") -# A higher-level way +# A higher-level way: import dns.resolver -resolver = dns.resolver.Resolver(configure=False) -resolver.nameservers = ["8.8.8.8"] -answer = resolver.resolve("amazon.com", "NS") +answer = dns.resolver.resolve_at("8.8.8.8", "amazon.com", "NS") print("The nameservers are:") for rr in answer: print(rr.target) +print("") +print("") + +# If you're going to make a bunch of queries to the server, make the resolver once +# and then use it multiple times: + +res = dns.resolver.make_resolver_at("dns.google") +answer = res.resolve("amazon.com", "NS") +print("The amazon.com nameservers are:") +for rr in answer: + print(rr.target) +answer = res.resolve("google.com", "NS") +print("The google.com nameservers are:") +for rr in answer: + print(rr.target) +print("") +print("") # Sending a query with the all flags set to 0. This is the easiest way # to make a query with the RD flag off. diff --git a/tests/test_async.py b/tests/test_async.py index 5ae8854..d46f79e 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -560,6 +560,28 @@ class AsyncTests(unittest.TestCase): self.async_run(run) + @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") + def testResolveAtAddress(self): + async def run(): + answer = await dns.asyncresolver.resolve_at("8.8.8.8", "dns.google.", "A") + seen = set([rdata.address for rdata in answer]) + self.assertIn("8.8.8.8", seen) + self.assertIn("8.8.4.4", seen) + + self.async_run(run) + + @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") + def testResolveAtName(self): + async def run(): + answer = await dns.asyncresolver.resolve_at( + "dns.google", "dns.google.", "A", family=socket.AF_INET + ) + seen = set([rdata.address for rdata in answer]) + self.assertIn("8.8.8.8", seen) + self.assertIn("8.8.4.4", seen) + + self.async_run(run) + def testSleep(self): async def run(): before = time.time() diff --git a/tests/test_resolver.py b/tests/test_resolver.py index c73451e..9037808 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -767,6 +767,22 @@ class LiveResolverTests(unittest.TestCase): self.assertIn("94.140.14.14", seen) self.assertIn("94.140.15.15", seen) + @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") + def testResolveAtAddress(self): + answer = dns.resolver.resolve_at("8.8.8.8", "dns.google.", "A") + seen = set([rdata.address for rdata in answer]) + self.assertIn("8.8.8.8", seen) + self.assertIn("8.8.4.4", seen) + + @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") + def testResolveAtName(self): + answer = dns.resolver.resolve_at( + "dns.google", "dns.google.", "A", family=socket.AF_INET + ) + seen = set([rdata.address for rdata in answer]) + self.assertIn("8.8.8.8", seen) + self.assertIn("8.8.4.4", seen) + def testCanonicalNameNoCNAME(self): cname = dns.name.from_text("www.google.com") self.assertEqual(dns.resolver.canonical_name("www.google.com"), cname) |