[dns-operations] good async DNS library

Viktor Dukhovni ietf-dane at dukhovni.org
Fri Apr 26 20:32:32 UTC 2019



> On Apr 26, 2019, at 2:44 PM, Matthew Pounsett <matt at conundrum.com> 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.

Not an issue with Haskell's Net.DNS.  It exposes both a high-level
interface that returns specific answer RDATA or an error, and a
low level interface that returns a decoded DNSMessage (header
plus the usual query, answer, auth, and additional lists of RRsets
with an EDNS pseudo-header decoded from the OPT record):

    -- | ... the default values of the RD, AD, CD and DO
    -- flag bits, as well as various EDNS features, can be adjusted via the
    -- 'QueryControls' parameter.
    --
    lookupRawCtl :: Resolver      -- ^ Resolver obtained via 'withResolver'
                 -> Domain        -- ^ Query domain
                 -> TYPE          -- ^ Query RRtype
                 -> QueryControls -- ^ Query flag and EDNS overrides
                 -> IO (Either DNSError DNSMessage)
    lookupRawCtl rslv dom typ ctls = resolve dom typ rslv ctls receive

    -- | DNS message format for queries and replies.
    --
    data DNSMessage = DNSMessage {
        header     :: !DNSHeader        -- ^ Header with extended 'RCODE'
      , ednsHeader :: EDNSheader        -- ^ EDNS pseudo-header
      , question   :: [Question]        -- ^ The question for the name server
      , answer     :: Answers           -- ^ RRs answering the question
      , authority  :: AuthorityRecords  -- ^ RRs pointing toward an authority
      , additional :: AdditionalRecords -- ^ RRs holding additional information
      } deriving (Eq, Show)

    type Answers = [ResourceRecord]
    type AuthorityRecords = [ResourceRecord]
    type AdditionalRecords = [ResourceRecord]

You only get a "DNSError" rather than a "DNSMessage" if no response came
back within the configured timeout (μs) or the response could not be decoded.

You can also use the library to construct your own DNSMessage objects,
serialize them to ByteStrings, and then do your own I/O if you want to see
the content of potentially malformed replies:

    -- | Encode a 'DNSMessage' for transmission over UDP.  For transmission over
    -- TCP encapsulate the result via 'Network.DNS.IO.encodeVC', or use
    -- 'Network.DNS.IO.sendVC', which handles this internally.
    --
    encode :: DNSMessage -> ByteString

Decoding is similar, but decoding of RRSIG records requires knowledge of
the current date to choose the right epoch, since the timestamps are
32-bit clock arithmetic, but the "show" and "decode" interfaces are
"pure", so clock access is verboten.  I therefore added a "decodeAt"
function that takes an epoch time input.

    -- | Decode an input buffer containing a single encoded DNS message.  If the
    -- input buffer has excess content beyond the end of the message an error is
    -- returned.  DNS /circle-arithmetic/ timestamps (e.g. in RRSIG records) are
    -- interpreted at the supplied epoch time.
    --
    decodeAt :: Int64                      -- ^ current epoch time
             -> ByteString                 -- ^ encoded input buffer
             -> Either DNSError DNSMessage -- ^ decoded message or error

    -- | Decode an input buffer containing a single encoded DNS message.  If the
    -- input buffer has excess content beyond the end of the message an error is
    -- returned.  DNS /circle-arithmetic/ timestamps (e.g. in RRSIG records) are
    -- interpreted based on a nominal time in the year 2073 chosen to maximize
    -- the time range for which this gives correct translations of 32-bit epoch
    -- times to absolute 64-bit epoch times.  This will yield incorrect results
    -- starting circa 2141.
    --
    decode :: ByteString                 -- ^ encoded input buffer
           -> Either DNSError DNSMessage -- ^ decoded message or error

> And for testing zone
> transfers, I found I had to write my own xfr method from scratch,
> because the dnspython method for doing that either throws an exception
> or returns a zone object.. nothing in between.. which means you can't
> do things like examine the rcode of the response.

We've finally hit something not yet supported.  There's a pull request
for AXFR support I've not yet had time to review.

> Neither library allows you to fiddle very deep in the message layers,
> which makes them unsatisfactory for monitoring infrastructures load
> balanced with ECMP.  To do that, properly, you need to be able to set
> the IP TTL to 1, so that there's no chance your test queries (intended
> for loopback) will leave the host and get a response from another
> server.  Net::DNS is the only library I've encountered that allows
> messing with the IP layer, because you have access to the underlying
> Net::Cmd goo.

Haskell's Net.DNS does not expose the transport layer sockets
via the query interface, so if you really must tweak the IP
stack, you'd finally need to switch to using just the codecs
and do your own packets, timeouts, retries, ...

Mind you, a feature that would let you specify a list of socket
options to set as part of the resolver settings is not out of
the question.  Most of the controls tweak the structure of the
question, but the list of servers to query, the timeout and
retry settings and whether to query serially or in parallel,
... are I/O rather than content controls, and more could be
added.

-- 
	Viktor.





More information about the dns-operations mailing list