* * * * *
The sane library for decoding DNS packets
Obligatory Sidebar Links
* C-Ares [1]
* UDNS [2]
* GNU adns [3]
* libdns [4]
* djbdns [5]
Every so often over the past twenty years or so, I've had the need to make
DNS (Domain Name System) queries other than the standard A (Address) type;
you know, like MX (Mail eXchange) records, or PTR (Pointer) records, but the
standard calls available under Unix are lacking. And each time I've
investigated other DNS resolving libraries, they've been horrendously complex
while at the same time, only resolving a few record types.
So when my attention turned once again towards DNS, I decided that the best
course of action was to buckle down and write my own library [6], which I did
over this past Thanksgiving holiday [7] weekend.
My approach to the problem I think is unique. At least, compared to all the
other DNS resolving libraries I glanced over, it's unique. First off, I
pretty much ignored the actual network aspect. Sure, I have a simple routine
to send a query to a DNS server, but the code is dumb and frankly, not
terribly efficient, seeing how it opens up a new socket for each request.
Also, it only handles UDP (User Datagram Protocol) based requests, and pretty
much assumes there will be no network errors that drop the request (which as
far as I can tell, hasn't actually happened).
Instead, I concentrated on the actual protocol aspect of the problem,
decoding raw packets into something useful, and taking something useful and
encoding it into a raw packet. I tackled encoding first, and it's an elegent
solution—you fill in a structure with appropriate data and call an encoding
routine which does all the dirty work.
> dns_question_t domain; /* what we're asking about */
> dns_query_t query; /* the "form" we fill out */
> dns_packet_t request[DNS_BUFFER_UDP]; /* the encoded query */
> size_t reqsize;
> dns_rcode_t rc;
>
> domain.name = "conman.org."; /* only fully qualified domains name */
> domain.type = RR_LOC; /* let's see where this is located */
> domain.class = CLASS_IN;
>
> query.id = random(); /* randomize the ID for security */
> query.query = true; /* yes, this is a query */
> query.rd = true; /* we're asking for a recursive lookup */
> query.opcode = OP_QUERY; /* obviously */
> query.qdcount = 1; /* we're asking one question */
> query.questions = &domain; /* about this domain */
>
> reqsize = sizeof(request);
> rc = dns_encode(request,&reqsize,&query);
>
> if (rc != RCODE_OKAY)
> {
> /* Houston, we have a problem! */
> }
>
Once encoded, we can send it off via the network to a DNS server. Now, you
may think that's quite a bit of code to make a single query, and yes, it is.
And yes, it may seem silly to mark the query as a query, and have to specify
the actual operation code as a query, but there are a few other types of
operations one can specify and besides, this beats the pants of setting up
queries in all the other DNS resolving libraries.
And this approach to filling out a structure, then calling an explicit
encoding routine is not something I've seen elsewhere. At best, you might get
a library that lets you fill out a structure (but most likely, it's a
particular call for a particular record type) but then you call this “all-
dancing, all-singing” function that does the encoding, sending,
retransmissions on lost packets, decoding and returns a single answer. My
way, sure, there's a step for encoding, but it allows you to handle the
networking portion as it fits into your application.
Anyway, on the backend, once you've received the binary blob back from the
DNS server, you simply call one function to decode the whole thing:
> dns_packet_t reply[DNS_BUFFER_UDP];
> size_t replysize;
> dns_decoded_t decoded[DNS_DECODEBUF_4K];
> size_t decodesize;
> dns_query_t *result;
>
> /* reply contains the packet; replysize is the amount of data */
>
> decodesize = sizeof(decoded);
> rc = dns_decode(decoded,&decodesize,reply,replysize);
>
> if (rc != RCODE_OKAY)
> {
> /* Houston, we have another problem! */
> }
>
> /* Using the above query for the LOC resource record */
>
> result = (dns_query_t *)decoded;
>
> if (result->ancount == 0)
> {
> /* no answers */
> }
>
> printf(
> "Latitude: %3d %2d %2d %s\n"
> "Longitude: %3d %2d %2d %s\n"
> "Altitude: %ld\n",
> result->answers[0].loc.latitude.deg,
> result->answers[0].loc.latitude.min,
> result->answers[0].loc.latitude.sec,
> result->answers[0].loc.latitude.nw ? "N" : "S",
> result->answers[0].loc.longitude.deg,
> result->answers[0].loc.longitude.min,
> result->answers[0].loc.longitude.sec,
> result->answers[0].loc.longitude.nw ? "W" : "E",
> result->answers[0].loc.altitude
> );
>
Yes, it really is that simple, and yes, I support LOC (Location) records,
along with 29 other DNS record types (out of 59 defined DNS record types). So
I'm fully decoding half the record types. Which sounds horrible (“only 50%?”)
until you compare it to the other DNS resolving libraries, which typically
only handle around half a dozen records, if that. Even more remarkable is the
amount of code it takes to do all this:
Table: Lines of Code to decode DNS records
Library Lines of code Records decoded
------------------------------
spcdns 1321 30
c-ares 1452 7
udns 872 6
adns 1558 13
djbdns 1276 5
(I should also mention that the 1,321 lines for spcdns include the encoding
routine; line counts for all the other libraries exclude such code; I'm too
lazy to separate out the encoding routine in my code since it's all in one
file.)
How was I able to get such densities of code? Well, aside from good code
reuse (really! Nine records are decoded by one routine; another twelve by
just three routines) perhaps it was just the different approach I took to the
whole problem.
Another feature of the code is its lack of memory allocation. Yup, the
decoding routine does not once call malloc(); nope, all the memory it uses is
passed to it (in the example above, it's the dns_decoded_t
decoded[DNS_DECODEBUF_4K] line). I've found through testing that a 4K
(Kilobyte) buffer was enough to decode any UDP-based result. And by giving
the decode routine a block of memory to work with, not only can it avoid a
potentially expensive malloc() call, it also avoids fragmenting memory and
keeping the memory cache more coherent (when Mark [8] saw an earlier version
he expressed concern about alignment issues, as at the time, I was passing in
a character buffer; I reworked the code such that the dns_decoded_t type
forced the memory alignment, but because an application may be making queries
via TCP (Transmission Control Protocol), which means they can be bigger, I
didn't want to hardcode a size into the type; it would either be too big,
thus wasting memory, or too small, which makes it useless; the way I have it
now, with the array, you can adjust the memory to fit the situation).
And from that, the code itself is thread safe; there are no globals used,
unlike some other protocol stacks I've been forced to use [9], so there
should be absolutely no issues with incorporating the library into an
application.
Oh, and I almost forgot the Lua [10] bindings I made for the library. After
all, a protocol stack should make things easy, right?
Update on Saturday, October 26^th, 2013
I was brought to my attention (thank you, Richard E. Brown) that I should
link to the source code [11] so that it's easier to find.
So linked.
[1]
http://c-ares.haxx.se/
[2]
http://www.corpit.ru/mjt/udns.html
[3]
http://www.chiark.greenend.org.uk/~ian/adns/
[4]
http://www.25thandclement.com/~william/projects/dns.c.html
[5]
http://cr.yp.to/djbdns.html
[6]
https://github.com/spc476/SPCDNS
[7]
http://en.wikipedia.org/wiki/Thanksgiving_(United_States)
[8]
http://gladesoft.com/
[9]
gopher://gopher.conman.org/0Phlog:2010/11/30.1
[10]
http://www.lua.org/
[11]
https://github.com/spc476/SPCDNS
Email author at
[email protected]