Interesting DNS facts

January 28, 2021
Many people are often surprised by the depth of the DNS. In this post we highlight several items not typically learnt about the protocol and its implementations.

Introduction

We’ve spent quite a lot of time working with the DNS for both operational and security use cases. Quite often we come across interesting aspects of the protocol which are not often learnt. This post details some of these items which we think may be of interest.

What’s your hostname DNS server?

Administrators of the ISC BIND product will be familiar with the so called version.bind query:

$ dig +short @127.0.0.1 CHAOS TXT version.bind.
"X.X.X"

Many users, however, will not be familiar with the hostname.bind and authors.bind queries:

$ dig +short @127.0.0.1 CHAOS TXT hostname.bind.
"dns1-centos7"

$ dig +short @127.0.0.1 CHAOS TXT authors.bind.
"Bob Halley"
"Mark Andrews"
"Danny Mayer"
"Jeremy C. Reed"
"Andreas Gustafsson"
"John H. DuBois III"
"Evan Hunt"
"Scott Mann"
"James Brister"
"Francis Dupont"
"Michael Sawyer"
"JINMEI Tatuya"
"Curtis Blackburn"
"David Lawrence"
"Matt Nelson"
"Brian Wellington"
"Damien Neil"
"Ben Cottrell"
"Michael Graff"

Like the version returned to the version.bind query, the hostname returned to the hostname.bind query can be overridden:

options {
    ...
    hostname "generic";
    ...
};

For the authors.bind query, if version has been overridden in the named configuration file the authors list will be returned as empty.

How long is that TCP message?

For DNS messages sent using UDP a DNS server and client can easily determine the bounds of a message since each UDP packet contains one DNS message.

For DNS messages sent over TCP however, since it is a continuous stream of octets, a DNS client and server needs some other way to know it has enough data to parse the next DNS message in the TCP stream.

DNS messages sent over a TCP stream are prefixed with 2 octets. These 2 octets are a 16 bit unsigned integer in network byte order, and will specify the length of the DNS message following it.

This allows a DNS server and client to know if it has enough data to parse the next message in the TCP stream.

Using Wireshark to demonstrate, we issue the same DNS query twice, with the first using UDP and the second using TCP. The first UDP based query is as we’d expect:

dns-query-udp

The UDP query is not prefixed with a 2 octet length. At the highlighted section we can see the start of the DNS header.

The second query is a TCP based:

dns-query-tcp

Here we can see the TCP stream making up the query consists of two segments, the first contains two octets of data which specifies the length of the DNS message to follow. Hex 0026 equates to 38 octets. The second segment contains the same 38 octet DNS message as seen in the first UDP packet.

One Message, Multiple Questions

In the DNS header the Question Count field (also referred to as QDCount) specifies the number of questions in the DNS message. When working with the DNS most users will always assume each DNS message contains only a single question.

The DNS protocol provisions for multiple questions in a single message, hence the field name Question Count. If you refer to the source code of the most popular DNS implementations, you will see however, that most, if not all, will not support such a message, and will respond with a not-implemented, format-error, or server-failure style response.

For example, we see the following in the ISC BIND source code:

/*
 * Check for multiple question queries, since edns1 is dead.
 */
if (message->counts[DNS_SECTION_QUESTION] > 1) {
    query_error(client, DNS_R_FORMERR, __LINE__);
    return;
}

We see the following in the PowerDNS source code:

if (query && (d_header.qdcount > 1))
  throw MOADNSException("Query with QD > 1 ("+std::to_string(d_header.qdcount)+")");

Finally, we also see the following in the Unbound source code:

if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) {
	verbose(VERB_QUERY, "request wrong nr qd=%d", 
		LDNS_QDCOUNT(sldns_buffer_begin(pkt)));
	return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
}

TYPEn & CLASSn

The DNS protocol is designed in such a way that each intermediate server in the path for a DNS query need not know how to interpret the value of a DNS resource record.

DNS resource records have the following attributes when being placed into a DNS message:

  • owner - A sequence of labels specifying the records fully qualified domain name, the format of each label is the length of the label and then the label data with the final label having a length of zero, i.e. www.vendorn.com would be [3]www[12]vendorn[3]com[0] (the values in brackets are unsigned 8 bit integers, i.e. a single octet)
  • type - Unsigned 16 bit integer specifying the records type, i.e. 5 for CNAME
  • class - Unsigned 16 bit integer specifying the records class, i.e. 1 for IN
  • ttl - Signed 32 bit integer specifying the time-to-live for the record in seconds, i.e. 3600
  • rdatalen - Unsigned 16 bit integer specifying the number of octets in the rdata field
  • rdata - Records raw data, the format of this value is record type specific, i.e., for A records, this will be an unsigned 32bit integer

Refer to IANA DNS parameters for a list of registered DNS record and class types.

Since the length of the rdata field is known the format of the data in the rdata section need not be known, or even interpreted. A DNS server can receive and store this data in its cache without having to know how it would finally be interpreted by any client.

When logging DNS queries using the ISC BIND DNS server, for example, you will sometimes see a DNS resource record type of TYPEn or a class of CLASSn where n is a integer. This is a type, or class, that the DNS server didn’t know about, but it was still able to work with it.

You can see this behaviour using the dig command and DNS query logging:

$ dig @127.0.0.1 TYPE123 CLASS5 www.vendorn.com.
...
;; QUESTION SECTION:
;www.vendorn.com.          CLASS5  TYPE123
...

$ tail -1 /var/log/querylog.log
client 127.0.0.1#41368 (www.vendorn.com): query: www.vendorn.com CLASS5 TYPE123 +E (127.0.0.1)

We know our DNS server won’t have any data in this case, but we can at least see the custom type and class being processed.