[dns-operations] good async DNS library

Casey Deccio casey at deccio.net
Tue Apr 30 21:48:49 UTC 2019

> On Apr 30, 2019, at 12:27 PM, Matthew Pounsett <matt at conundrum.com> wrote:
> On Tue, 30 Apr 2019 at 10:59, Petr Špaček <petr.spacek at nic.cz> wrote:
>>> For python, dnspython is an excellent library for writing an
>>> application that needs answers out of the DNS, but falls short when it
>>> comes to features you need for doing testing.    It really wants to be
>>> either a stub resolver or a recursive resolver, so it takes a bunch of
>>> boilerplate code to just send a single -RD query to a single server
>>> and get back a reasonable response.  It also tends to do undesirable
>>> things like throw exceptions for successful responses, because it
>>> thinks answers like NXDOMAIN are errors.  And for testing zone
>> Maybe you should have a look at low level interface, it is actually two
>> lines of useful code (once you have libraries imported):
>> query = dns.message.make_query('www.example.com.', 'A')
>> answer = dns.query.udp(query, '')
>> print(answer.rcode())
> What one actually needs to do there, for decent testing code, is to
> write an entirely new helper method that takes care of timeouts,
> fallback from UDP to TCP, etc..   That middle ground is what's missing
> from (that part of) the library.  The zone transfers are a similar
> issue; there's really low level functions you can use to generate a
> and send a message by hand, writing all of your own timeout and
> fallback code, or there's the helper function that does everything for
> you but doesn't provide any insight into the response.

For what it's worth, that was the intent of the DNSViz query module [1].  The idea is you create DNS response handlers, which can be associated with a DNS query class.  Then you instantiate the DNS query class, with all its options, handlers, etc., and execute the query.  Note that there is no stable API at the moment, but that was the goal when it was written.  

For example, classes are defined like the following (these already exist in the code but are shown for illustrative purposes):

class RecursiveDNSQuery(SimpleDNSQuery):
    '''A simple recursive query.'''
    flags = SimpleDNSQuery.flags | dns.flags.RD

class StandardQuery(SimpleDNSQuery):
    '''A standard old-school DNS query that handles truncated packets.'''
    response_handlers = \
                SimpleDNSQuery.response_handlers + \

class StandardRecursiveQuery(StandardQuery, RecursiveDNSQuery):
    '''A standard old-school recursive DNS query that handles truncated packets.'''


>>> import dns.name, dns.rdataclass, dns.rdatatype
>>> from dnsviz.query import StandardRecursiveQuery
>>> from dnsviz.ipaddr import IPAddr
>>> import json
>>> d = StandardRecursiveQuery(dns.name.from_text("example.com"), dns.rdatatype.A, dns.rdataclass.IN, [IPAddr(""), IPAddr(""), IPAddr('')])
>>> d.execute()

Then you can check out the responses, the history, etc:

>>> r1 = d.responses[IPAddr('')][IPAddr('')]
>>> r2 = d.responses[IPAddr('')][IPAddr('')]
>>> r3 = d.responses[IPAddr('')][IPAddr('')]
>>> r1.message
<DNS message, ID 40646>
>>> r2.message
<DNS message, ID 62727>
>>> r3.message
>>> r1.history
>>> r2.history
[<Retry: TC -> USE_TCP>]
>>> r3.history
>>> r1.error
>>> r2.error
>>> r3.error
>>> r2.msg_size
>>> r2.response_time
>>> print(r1.message)
id 40646
opcode QUERY
flags QR RD RA
example.com. IN A
example.com. 20432 IN A
>>> json.dumps(r1.serialize())
'{"message": "nsaBgAABAAEAAAAAB2V4YW1wbGUDY29tAAABAAHADAABAAEAAE/QAARduNgi", "msg_size": 45, "time_elapsed": 20, "history": []}'

It can get a lot more complex than this [2].


[1] https://github.com/dnsviz/dnsviz/blob/master/dnsviz/query.py
[2] https://github.com/dnsviz/dnsviz/blob/master/dnsviz/query.py#L1746

More information about the dns-operations mailing list