Introduction

Domain Name System, or DNS, is an integral part of not only the web, but in fact almost any network service. Despite being conceptually simple, it’s not always easy to understand all of the practical aspects of running a DNS server, and it’s therefore useful to spend some time with it.

In this first part, we’ll take a look at the very basics of authoritative and recursive DNS server implementations for Linux, NSD and Unbound, respectively—both high quality software. In part 2 of this lecture, we’ll take a look at DNSSEC, a security layer on top of DNS.

Basics

When you ping google.com (for example), the hostname (google.com) is automatically translated to an IP address for you:

~% ping google.com
PING google.com (142.251.36.142) 56(84) bytes of data.
64 bytes from prg03s12-in-f14.1e100.net (142.251.36.142): icmp_seq=1 ttl=118 time=16.3 ms
64 bytes from prg03s12-in-f14.1e100.net (142.251.36.142): icmp_seq=2 ttl=118 time=9.26 ms
64 bytes from prg03s12-in-f14.1e100.net (142.251.36.142): icmp_seq=3 ttl=118 time=7.84 ms

What precisely the IP address is going to be depends on many factors, such as your physical location, the location of your VPN’s exit node (if you’re using any), whether or not you have an IPv6 address assigned, etc. What’s important is that you’ll ping some IP address associated with google.com. The mechanism responsible for the translation of hostname to an IP address is, of course, DNS.

When you run netstat (for example), you may notice that the IP address of the remote end of some connections is translated to hostnames:

Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 10.13.0.103:59808       prg03s10-in-f3.1e10:443 ESTABLISHED
tcp        0      0 10.13.0.103:33000       ec2-3-68-18-70.eu-c:443 ESTABLISHED
tcp        0      0 localhost.localdom:1313 localhost.localdo:52652 ESTABLISHED
tcp        0      0 localhost.localdo:53348 localhost.localdom:1313 ESTABLISHED
tcp        0      0 10.13.0.103:53582       prg03s11-in-f22.1e1:443 TIME_WAIT
tcp        0      0 localhost.localdom:1313 localhost.localdo:52666 ESTABLISHED
tcp        0      0 10.13.0.103:49796       ec2-52-41-252-32.us:443 ESTABLISHED
tcp        0      0 10.13.0.103:52376       prg03s12-in-f14.1e1:443 TIME_WAIT

This, too, is DNS—working in reverse (sometimes called rDNS). The lookup process is fundamentally the same, but instead of looking up a hostname to obtain an IP, we lookup an IP and get a hostname.

Using drill

Both ping and netstat (and many other tools) use DNS, but that’s just a side effect of what they do. There are specialized tools to interact with DNS servers, such as drill or dig. We’ll be using the former.

Let’s illustrate the two operations—forward and reverse lookup—using drill. Forward lookup:

~% drill google.com
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 19787
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; google.com.  IN      A

;; ANSWER SECTION:
google.com.     300     IN      A       142.251.36.142

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 16 msec
;; SERVER: 8.8.8.8
;; WHEN: Wed Nov 30 21:45:03 2022
;; MSG SIZE  rcvd: 44

Things to note:

  • There are several sections in the output, for now, we only care about QUESTION and ANSWER sections.
  • You can see we query google.com, in the Internet class (IN) and we ask for the A (address) record specifically. In practice, this means we ask for an IPv4 address.
  • You can see the answer to our query is that google.com, in class Internet (IN) has an address (A) 142.251.36.142.
  • The query to 16 milliseconds.
  • The server handling the request was 8.8.8.8 (more on that later).
  • If you watch this command, as in watch -n.5 -d drill google.com, you’ll likely observe the numeric field in the answer (300) going down, about once per second. We discussed this and agreed this was some kind of TTL. More about that later.

For reverse lookup:

~% drill -x 142.251.36.142
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 477
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; 142.36.251.142.in-addr.arpa. IN      PTR

;; ANSWER SECTION:
142.36.251.142.in-addr.arpa.    18092   IN      PTR     prg03s12-in-f14.1e100.net.

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 10 msec
;; SERVER: 8.8.4.4
;; WHEN: Wed Nov 30 21:50:04 2022
;; MSG SIZE  rcvd: 84

Things to note:

  • We’re asking for a different record this time: pointer (PTR) record of 142.36.251.142.in-addr.arpa. in class Internet (IN).
  • The answer has a much longer TTL, but is otherwise the answer to our question, as with forward lookup.
  • A different server was handling this query (but that’s unrelated to the fact that this was a reverse lookup).

We can specify the DNS server to query. We had some fun with that, and picked a very remote DNS server, to observe the latency:

~% drill @102.33.41.30 www.dcepelik.cz
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 40321
;; flags: qr rd ra ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; www.dcepelik.cz.     IN      A

;; ANSWER SECTION:
www.dcepelik.cz.        1800    IN      CNAME   praha.dcepelik.cz.
praha.dcepelik.cz.      1800    IN      A       37.205.14.117

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 1044 msec
;; SERVER: 102.33.41.30
;; WHEN: Wed Nov 30 21:53:01 2022
;; MSG SIZE  rcvd: 69

We noted two things:

  • Since we drilled for www.dcepelik.cz, which likely isn’t cached, the request took very long (more than a second).
  • A web browser does many DNS lookups for every single page, and the DNS queries block fetching of resources on the page. So the choice of DNS server has impact on your user experience. In the case of other services which make many DNS queries, it will affect their performance, too.

Domain names

We reminded ourselves that in qoox.foo.bar.baz,

  • baz is the top level domain (TLD),
  • bar is the second level domain,
  • foo is a subdomain,
  • qoox is a subdomain of foo, bar is a subdomain of baz, etc.

We discussed what google.com. would mean (mind the . at the end). We realized that the domain (with empty name) after the . is the so-called root domain, they will be of utter importance. The dot is optional in most cases, but not in zone files (more on that soon).

We tried to open google.com. in Firefox, and to ping it. Not surprisingly, both worked.

We also mentioned that baz, local or una (which we’ll use during the labs) are perfectly fine TLDs, they just don’t happen to be the official TLDs managed by IANA. That’s not of concern when running a DNS server for the local network.

Wireshark and tcpdump

We used Wireshark and tcpdump during the lecture, to see the actual DNS packets. To capture the packets, you can simply use

% tcpdump -w capture -s 65535 dst port 53 or src port 53

(We recalled that DNS is usually running on UDP port 53, but can also run over TCP on the same port.)

This creates a capture file, with maximum packet capture size around 64k, which can then be loaded into Wireshark and analyzed. We did a quick dive into the analyzer and why it’s a useful tool for many things.

We also realized that DNS isn’t secured in any way; it’s clear text and can be modified in transit by an adversary. Both issues concern us.

Public DNS servers

There are several public DNS servers (that is, DNS servers you can use free of charge):

  • 8.8.8.8, 8.8.4.4—Google public DNS
  • 1.1.1.1—Cloudflare public DNS
  • 9.9.9.9—Quad9 public DNS

Please note that if it’s free, you’re the product. I didn’t study the terms and conditions of those services (did you?), but most likely at least some public DNS providers sell your (partially? anonymized?) DNS logs to advertisers.

Authoritative DNS

We set up NSD using the following config file and zone files. Do not copy that config file, it’s not reasonable for anything but this lecture.

~/mff/linux-adm/dns% cat nsd.conf
server:
        zonelistfile: zone.list
        port: 53
        server-count: 1
        verbosity: 3
        database: ""
        zonesdir: .
        pidfile: ""
        chroot: ""
        username: ""
        xfrdfile: xrfd.state

zone:
        name: example.local
        zonefile: example.local.zone

~/mff/linux-adm/dns% cat example.local.zone
$ORIGIN example.local.
$TTL    30M

@            IN      SOA     ns.example.local.      d.dcepelik.cz.  ( 2 30M 15M 2W 15M )
@            IN      NS      ns
ns           IN      A       127.0.0.1

foo          IN      A       192.168.1.1
bar          IN      CNAME   foo
@            IN      TXT     "this is some free-form text record" "more"
@            IN      MX      10 mx
mx           IN      A       192.168.1.2
_smtp._tcp 5 IN      SRV     10 10 25 example.local.

sub          IN      NS      ns.sub
ns.sub       IN      A       192.168.1.1

For the zone file:

  • The are some directives for NSD in the file (starting with $) and then there are so-called records. Basically, this is a DNS database file. Each record has a name (e.g. foo or @), a class (just IN), a type (e.g. CNAME) and some rvalue (value on the right, type of value and its structure depends on the type of record).
  • $ORIGIN is the base name from which all unqualified names (names not ending in ., such as foo) are derived.
  • @ is synonymous with $ORIGIN.
  • . at the end matters in the zone file. Without the dot at the end, the domain name is “relative”, in other words, $ORIGIN is appended to it. We explained the analogy between relative file names and relative domain names.
  • $TTL is the default TTL (time-to-live, or expiration) of the records. It can be overridden per record (it’s set to 5 for the SRV record).

We then went ahead and drilled each record against the locally running NSD:

  • The A record returns the address, and that’s it.
  • The CNAME establishes as mapping, where bar is the alias of the canonical name foo. When you drill an A record against a CNAME, the DNS server will return two answers: one which resolves the alias to the canonical name, and another which retrieves the address (A) of the canonical name:
~% drill @127.0.0.1 bar.example.local
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 46014
;; flags: qr aa rd ; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 1
;; QUESTION SECTION:
;; bar.example.local.   IN      A

;; ANSWER SECTION:
bar.example.local.      1800    IN      CNAME   foo.example.local.
foo.example.local.      1800    IN      A       192.168.1.1

;; AUTHORITY SECTION:
example.local.  1800    IN      NS      ns.example.local.

;; ADDITIONAL SECTION:
ns.example.local.       1800    IN      A       127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1
;; WHEN: Wed Nov 30 22:00:50 2022
;; MSG SIZE  rcvd: 102
  • We noted that TXT records are useful to attach free-form metadata to domain names, and that it’s going to be useful for our e-mail setup. We also tried to drill the TXT record—the type of queried record defaults to A, but can be overridden on the command line:
~% drill @127.0.0.1 TXT example.local
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 54554
;; flags: qr aa rd ; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; QUESTION SECTION:
;; example.local.       IN      TXT

;; ANSWER SECTION:
example.local.  1800    IN      TXT     "this is some free-form text record" "more"

;; AUTHORITY SECTION:
example.local.  1800    IN      NS      ns.example.local.

;; ADDITIONAL SECTION:
ns.example.local.       1800    IN      A       127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1
;; WHEN: Wed Nov 30 22:02:32 2022
;; MSG SIZE  rcvd: 116
  • The MX record configures a mail exchanger for the domain, with a priority. The lower the priority, the more preferred the mail exchanger. We mentioned in the passage why it’s useful to have multiple mail exchangers (fault tolerance, so that mail isn’t lost during outage of the primary server, and is delivered in a timely manner).
  • We briefly discussed the SRV (service locator) record, and how it can be thought of as a generalization of the specialized MX record.
  • We mentioned delegation through the NS record: this record indicates that for sub.example.local (inclusive, and all its subdomains), the authoritative name server (the server which should have the current, valid information) is ns.sub. The subsequent A record then configures its address.

Recursive DNS

We took a look at Unbound, a recursive, caching, validating DNS server. We used the following config. That config is only useful for the lecture, do not copy it.

server:
  # listen on localhost:53 only, default access-control applies:
  # server localhost requests only
  interface: 127.0.0.1
  do-not-query-localhost: no
  logfile: unbound.log
  verbosity: 5
  username: d
  directory: .
  access-control: 127.0.0.0/8 allow
  tls-system-cert: yes
  tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

stub-zone:
  name: example.local
  stub-addr: 127.0.0.1@5353

For the config file:

  • Nothing special here, apart from increased verbosity, so that we can Unbound working,
  • and a stub zone configured which relays all queries for the example.local domain to our NSD (we moved NSD to port 5353 for further testing, which is quite common).

“Validating” refers to DNSSEC and will be of interest in part 2; “caching” means it caches responses to speed things up. Recursive is what concerns us at the moment.

To look up an A record of mail.google.com (for example), a recursive DNS server either serves and answer from cache (which is fast, and boring), or it needs to perform a_recursive DNS lookup_. Let’s assume the cache of Unbound is completely empty, and Unbound has to query everything.

  • The lookup starts with figuring out what the name server for com is. It’ll query the root name servers to figure that out.
  • Next, it will ask one of the root servers for A of mail.google.com. The name server will (in this case) reply that it doesn’t know the address of that host, but will provide the address of the authoritative name server of google.com.
  • Unbound will consult that authoritative name server, asking for A of mail.google.com. The server will respond with an IP address.

The question is, where does Unbound get the IP addresses of the root servers? The answer is, it’s hardcoded. Judging from the comments in the file, the list obviously doesn’t change much. Note that this doesn’t mean there are only 13 root name servers globally—there are 13 IP addresses, but multiple servers on the network handle each IP. So-called anycast routing is used to route your query to the nearest server handling a root name server IP address. We didn’t go into further details.

Next steps

During the labs, we’ll add DNS into our infrastructure.