Lecture Overview

The Internet has two different ways to identify websites. Humans refer to websites using human-readable names such as google.com and www.usna.edu, while computers refer to websites using IP addresses such as 172.217.4.174 and 136.160.88.139. The Domain Name System (DNS) is the protocol that translates between the two.

In this lecture, we overview how the DNS protocol works.

Domain Hierarchy and Name Servers

Since the Internet has a huge number of hosts, the domain namespace is structured by a tree-like hierarchy to efficiently handle the name resolution process. See the picture below (from wiki).

DNS uses a collection of many name servers, which are servers dedicated to replying to DNS requests. Each name server is responsible for a specific zone of domains, so that no single server needs to store every domain on the Internet.

Note: The domain name and zone are slightly different. The part of the namespace controlled by a name server is called a zone. It is often the case that a domain name and the zone are the same, but it is not uncommon that a single zone has multiple subdomains.

If you look at the configuration file of a DNS server, it is essentially about the zone that it takes care of.

For example, a name server responsible for the .com zone only needs to answer queries for domains that end in .com. This name server doesn’t need to store any DNS information related to wikipedia.org. Likewise, a name server responsible for the usna.edu zone doesn’t need to store any DNS information related to usma.edu.

DNS Query Process

DNS queries work as follows:
  • Your host will make a query to its local DNS server, which is specified by the network setting of your host.
  • Your local DNS server will always start with make a query to a DNS server for the root zone.
[Local DNS Server]   IP address of en.wikipedia.org?     --->   [root server]
                       <----- Ask .org server.


[Local DNS Server]   IP address of en.wikipedia.org?     --->   [.org server]
                       <----- Ask wikipedia.org server.


[Local DNS Server]   IP address of en.wikipedia.org?     --->   [wikipedia.org server]
                       <----- It is 208.80.154.224.

DNS Message Format

DNS is designed to be lightweight and fast. It uses UDP and has a fairly simple message format. You can find more detail in the RFC document.
     [2 bytes]      :        [2 bytes]
+-------------------+----------------------+
| Identification    | Flags                |
+-------------------+----------------------+
| # Questions       | # Answer RRs         |
+-------------------+----------------------+
| # Authority RRs   | # Additional RRs     |
+------------------------------------------+
| Questions (variable # of RRs)            |
+------------------------------------------+
| Answers (variable # of RRs)              |
+------------------------------------------+
| Authority (variable # of RRs)            |
+------------------------------------------+
| Additional info (variable # of RRs)      |
+------------------------------------------+
  • Identification (2 bytes): It is randomly selected per query and used to match requests to responses. When a DNS query is sent, the ID field is filled with random bits. Since UDP is stateless, the DNS response must send back the same bits in the ID field so that the original query sender knows which DNS query the response corresponds to.

    Suppose that an adversary somehow knows this ID field.

    What can he do?

  • Flags (2 bytes): They specify whether the message is a query or a response, as well as whether the query was successful (e.g. the NOERROR flag is set in the reply if the query succeeded, the NXDOMAIN flag is set in the reply if the query asked about a non-existent name).

Structure of resource records

Each RR is a key-value pair with an associated type.

Side notes: more details about DNS flags

In this lecture, we will see the 2 byte field of flags in more detail.
 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode    |AA|TC|RD|RA|Z |AD|CD| RCODE     |           
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Checking Actual Messages

We will use dig command to see the messages. The command has an option (using the @ symbol) to specify what DNS server to send a query to.
The USNA firewall blocks DNS messages, so the following commands will not work out. To test the commands, we recommend the Google clould shell.

Step 1: Asking a root name server

For a root name server, we use a.root-servers.net (as you can guess, b.root-servers.net is also a root name server).
First check out the flags.
  • "qr": This flag is set (to be 1), and it means it's an answer (not a query).
  • "rd": This means recursion desired.
Recall that the answer has four sections: Question, Answer, Authority, Additional.
  • The question section contains the question en.wikipedia.edu.
  • There is no record in the answer section, since the root name server doesn't know the exact IP.
  • In the authority section, there the child name servers are specified. As you see, the name servers control the zone of namespace ".org".

    The number 172800 is the TTL (in seconds).

  • In the additional section, the IP address of name servers are provided.
    • A stands for an IPv4 address.
    • AAAA stands for an IPv6 address.
$ dig @a.root-servers.net en.wikipedia.org

; <<>> DiG 9.11.3-1ubuntu1.13-Ubuntu <<>> @a.root-servers.net en.wikipedia.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39058
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 13
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;en.wikipedia.org.              IN      A

;; AUTHORITY SECTION:
org.                    172800  IN      NS      d0.org.afilias-nst.org.
org.                    172800  IN      NS      a0.org.afilias-nst.info.
...(omitted)...
org.                    172800  IN      NS      b2.org.afilias-nst.org.


;; ADDITIONAL SECTION:
d0.org.afilias-nst.org. 172800  IN      A       199.19.57.1
d0.org.afilias-nst.org. 172800  IN      AAAA    2001:500:f::1
...(omitted)...
b2.org.afilias-nst.org. 172800  IN      A       199.249.120.1
b2.org.afilias-nst.org. 172800  IN      AAAA    2001:500:48::1

;; Query time: 10 msec
;; SERVER: 198.41.0.4#53(198.41.0.4)
;; WHEN: Wed Jul 27 15:43:31 EDT 2022
;; MSG SIZE  rcvd: 447

Step 2: Asking .org name server

$ dig @d0.org.afilias-nst.org en.wikipedia.org

; <<>> DiG 9.11.3-1ubuntu1.13-Ubuntu <<>> @d0.org.afilias-nst.org en.wikipedia.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2344
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 3, ADDITIONAL: 4
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;en.wikipedia.org.              IN      A

;; AUTHORITY SECTION:
wikipedia.org.          3600    IN      NS      ns1.wikimedia.org.
wikipedia.org.          3600    IN      NS      ns2.wikimedia.org.
wikipedia.org.          3600    IN      NS      ns0.wikimedia.org.

;; ADDITIONAL SECTION:
ns0.wikimedia.org.      3600    IN      A       208.80.154.238
ns1.wikimedia.org.      3600    IN      A       208.80.153.231
ns2.wikimedia.org.      3600    IN      A       91.198.174.239

;; Query time: 82 msec
;; SERVER: 199.19.57.1#53(199.19.57.1)
;; WHEN: Wed Jul 27 16:01:11 EDT 2022
;; MSG SIZE  rcvd: 157
As before, this is a redirection answer. We will need to go to ns1.wikimedia.org and ask once more.

Step 3: Asking wikimedia.org name server

$ dig @ns1.wikimedia.org en.wikipedia.org

; <<>> DiG 9.11.3-1ubuntu1.13-Ubuntu <<>> @ns1.wikimedia.org en.wikipedia.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27943
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1024
; COOKIE: 2db0c1bf9d498a555cbc370a3e8ec13b (good)
;; QUESTION SECTION:
;en.wikipedia.org.              IN      A

;; ANSWER SECTION:
en.wikipedia.org.       86400   IN      CNAME   dyna.wikimedia.org.

;; Query time: 43 msec
;; SERVER: 208.80.153.231#53(208.80.153.231)
;; WHEN: Wed Jul 27 16:03:26 EDT 2022
;; MSG SIZE  rcvd: 94

Final step : Asking wikimedia.org name server once again

Below, we see the actual IP address.
$ dig @ns1.wikimedia.org dyna.wikimedia.org

; <<>> DiG 9.11.3-1ubuntu1.13-Ubuntu <<>> @ns1.wikimedia.org dyna.wikimedia.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19683
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1024
; COOKIE: 6376c9df1b66e06c5d4695d508eddb99 (good)
;; QUESTION SECTION:
;dyna.wikimedia.org.            IN      A

;; ANSWER SECTION:
dyna.wikimedia.org.     600     IN      A       208.80.154.224

;; Query time: 43 msec
;; SERVER: 208.80.153.231#53(208.80.153.231)
;; WHEN: Wed Jul 27 16:05:56 EDT 2022
;; MSG SIZE  rcvd: 83
If we don't use @ option, the Local DNS Server does all these steps by itself.
$ dig en.wikipedia.org

; <<>> DiG 9.11.3-1ubuntu1.13-Ubuntu <<>> en.wikipedia.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32263
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;en.wikipedia.org.              IN      A

;; ANSWER SECTION:
en.wikipedia.org.       85815   IN      CNAME   dyna.wikimedia.org.
dyna.wikimedia.org.     417     IN      A       208.80.154.224

;; Query time: 17 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Wed Jul 27 16:12:17 EDT 2022
;; MSG SIZE  rcvd: 90

DNS and scapy

Let's write a simple DNS packet sniffer using scapy. The documentation is found in here.

#!/usr/bin/python3
# sniff_dns.py
from scapy.all import *

def show_pkt(pkt):
  if DNS in pkt: 
    pkt.show2()
  
sniff(filter="udp", prn=show_pkt, count=2)
To avoid additional DNS packets, we directly use the IP address for the @address field.
dig @208.80.153.231 en.wikipedia.org
Note the above corresponds to Step 3.
The following is what the sniffer shows:
First packet
...(omitted)...
###[ DNS ]### 
           id        = 50528
           qr        = 0
           opcode    = QUERY
           aa        = 0
           tc        = 0
           rd        = 1
           ra        = 0
           z         = 0
           ad        = 1
           cd        = 0
           rcode     = ok
           qdcount   = 1
           ancount   = 0
           nscount   = 0
           arcount   = 1
           \qd        \
            |###[ DNS Question Record ]### 
            |  qname     = 'en.wikipedia.org.'
            |  qtype     = A
            |  qclass    = IN
           an        = None
           ns        = None
           \ar        \
            |###[ DNS OPT Resource Record ]### 
            |  rrname    = '.'
            |  type      = OPT
            |  rclass    = 4096
            |  extrcode  = 0
            |  version   = 0
            |  z         = 0
            |  rdlen     = None
            |  \rdata     \
            |   |###[ DNS EDNS0 TLV ]### 
            |   |  optcode   = 10
            |   |  optlen    = 8
            |   |  optdata   = '\xa38\xfe\xc4\x7fo\x80='
The packets consists of the following:
  • id, flags, four counts
  • four sections: qd (question section), an (answer section), ns (authority section), ar (additional section, optional pseudosection). In particular, optcode 10 in the ar record is about using a DNS Cookie.
Second packet
...(omitted)...
###[ DNS ]### 
           id        = 50528
           qr        = 1
           opcode    = QUERY
           aa        = 1
           tc        = 0
           rd        = 1
           ra        = 0
           z         = 0
           ad        = 0
           cd        = 0
           rcode     = ok
           qdcount   = 1
           ancount   = 1
           nscount   = 0
           arcount   = 1
           \qd        \
            |###[ DNS Question Record ]### 
            |  qname     = 'en.wikipedia.org.'
            |  qtype     = A
            |  qclass    = IN
           \an        \
            |###[ DNS Resource Record ]### 
            |  rrname    = 'en.wikipedia.org.'
            |  type      = CNAME
            |  rclass    = IN
            |  ttl       = 86400
            |  rdlen     = None
            |  rdata     = 'dyna.wikimedia.org.'
           ns        = None
           \ar        \
            |###[ DNS OPT Resource Record ]### 
            |  rrname    = '.'
            |  type      = OPT
            |  rclass    = 1024
            |  extrcode  = 0
            |  version   = 0
            |  z         = 0
            |  rdlen     = None
            |  \rdata     \
            |   |###[ DNS EDNS0 TLV ]### 
            |   |  optcode   = 10
            |   |  optlen    = 16
            |   |  optdata   = '\xa38\xfe\xc4\x7fo
                                \x80=k\x13\xa9.\xe6\xa3\xb2@'