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.
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.
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:
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:
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.
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);
}
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
CNAMEclass
- Unsigned 16 bit integer specifying the records class, i.e. 1
for
INttl
- 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
fieldrdata
- Records raw data, the format of this value is record type specific,
i.e., for A records, this will be an unsigned 32bit integerRefer 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.