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
andANSWER
sections. - You can see we query google.com, in the Internet class (
IN
) and we ask for theA
(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 inwatch -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 of142.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 offoo
,bar
is a subdomain ofbaz
, 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 DNS1.1.1.1
—Cloudflare public DNS9.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 (justIN
), 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 asfoo
) 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 theSRV
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, wherebar
is the alias of the canonical namefoo
. When you drill anA
record against aCNAME
, 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 theTXT
record—the type of queried record defaults toA
, 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 specializedMX
record. - We mentioned delegation through the
NS
record: this record indicates that forsub.example.local
(inclusive, and all its subdomains), the authoritative name server (the server which should have the current, valid information) isns.sub
. The subsequentA
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
ofmail.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
ofmail.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.