LearnThe Network StackThe Application Layer

DNS

dig - a hands-on Linux lab on a real virtual machine.

Every network connection starts with a name lookup. dig lets you make DNS queries and see the full response. This is the bridge between names and IP addresses.

A name is not an address

Somewhere on the network inside this VM there is a machine at 10.0.1.20. It answers exactly one kind of question: what address goes with this name.

Nothing else on that network cares about names. The wire carries numbers. curl, ssh, ping and your browser all carry numbers. Between the name a person types and the packet that leaves the interface there is a translation step, and it finishes so fast that most engineers never watch it happen even once.

You are about to watch it. By the end of this lesson you will have asked that machine a question by hand and read every field of the reply. Then you will catch the two packets that carried the exchange as they cross the wire.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The lab user is student and the machine is named dns-lab. Fields that change on every run, such as the query ID, the timings and the clock, are printed as labels rather than numbers, because your run will not match anyone else.

What DNS actually is

The Domain Name System, almost always written DNS, is a distributed database that maps human-readable names to IP addresses and to other data.

When you type curl example.com, the first thing that happens is not a connection. It is a question. Your system asks a DNS server what the IP address for example.com is, the server answers, and only then does the real connection begin.

Think of DNS as a phone book. You know the name of the person you want to call. You need the number. DNS looks up the number for you.

That makes DNS one of the most critical services on the internet. If DNS stops working, almost everything else stops too, because almost every connection starts with a name lookup.

Where the phone book came from

The internet did not start with DNS. In the early ARPANET years there was a single text file, HOSTS.TXT, that listed every machine on the network. Every site downloaded a fresh copy from one central machine and hoped it was current.

That worked while the network held a few hundred hosts and fell apart as it grew. Paul Mockapetris designed DNS in 1983 to replace the one big file with a distributed system. No single machine has to hold every name, and no single machine has to be asked about every name.

The old design is still on your disk. /etc/hosts is the direct descendant of HOSTS.TXT, and Linux still reads it, usually before it asks a DNS server at all. That is why one wrong line in /etc/hosts can quietly beat a perfectly healthy DNS server, and why that file is worth opening when a name resolves to something that makes no sense.

The two machines inside your VM

This lab builds two small machines inside your one VM using network namespaces. A network namespace is a private copy of the network stack: its own interfaces, its own addresses, its own routing table. The two here are called client and server, joined by a virtual cable:

  [client]  10.0.1.10  <-------->  10.0.1.20  [dns-server]
  MAC: 02:00:0a:00:01:0a           MAC: 02:00:0a:00:01:14

That 10.0.1.0/24 network exists only inside those two namespaces. The shell you are typing in sits outside them, so a plain command cannot reach 10.0.1.20. To run a command inside the client namespace, put sudo ip netns exec client in front of it:

sudo ip netns exec client dig @10.0.1.20 example.lab

The send-dig and cap helper commands do that for you, so most of the time you can type the short version.

The dns-server runs dnsmasq, a lightweight DNS server, with custom records:

NameResolves to
example.lab10.0.1.20
web.lab10.0.1.20
api.lab10.0.1.20
mail.lab10.0.1.30

Inside the client namespace, /etc/resolv.conf names 10.0.1.20 as the DNS server. When you query a name from there, the client sends the query to the dns-server, and the dns-server responds with the matching IP address.

These command lines are long, so lean on tab completion from the very first one. Type send- and press Tab, and the shell finishes send-dig for you. Type cat /etc/reso and press Tab, and it finishes /etc/resolv.conf. Tab completes both command names and file paths, and it is the difference between typing these lines once and typing them three times with a typo in the middle.

Your progress is tracked automatically. Just type the commands naturally and you will see a checkmark appear when each task completes.

Commit: what the namespace prefix is really doing

Before you type anything, take a position on that prefix. Leaving it off is the single most common way to get nothing back in this lab.

Ask the server yourself

dig, short for Domain Information Groper, is the standard tool for making DNS queries from the command line. It sends a query to a DNS server and prints the whole response, header and all.

Two forms do the same job here. The long one names everything explicitly:

sudo ip netns exec client dig @10.0.1.20 example.lab

The @10.0.1.20 is the part that tells dig which DNS server to ask. Leave off the sudo ip netns exec client part and dig will try to reach 10.0.1.20 from the VM itself, where that network does not exist, and the query will time out.

The helper does the same thing in one word. Before you press Enter, commit to a number: how many answers should come back for a name with a single A record?

send-dig

prompt: student@dns-lab:~$ answer: send-dig ||| send-dig example.lab ||| sudo ip netns exec client dig @10.0.1.20 example.lab output: ; <<>> DiG 9.18.x <<>> @10.0.1.20 example.lab ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: <random query id> ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION: ;example.lab. IN A

;; ANSWER SECTION: example.lab. 0 IN A 10.0.1.20

;; Query time: <milliseconds> ;; SERVER: 10.0.1.20#53(10.0.1.20) (UDP) ;; WHEN: <the date and time the query ran> ;; MSG SIZE rcvd: <size of the response in bytes> hint: The helper that queries from the client namespace is send-dig, or type the long form: sudo ip netns exec client dig @10.0.1.20 example.lab

One question went out and one answer came back. The line that matters is under ;; ANSWER SECTION:, and it says example.lab. has an address of 10.0.1.20.

Everything else on that screen is dig telling you how it got there. Four of those fields change on every single run: the query ID, the query time, the WHEN clock and the message size. They are printed above as labels for exactly that reason. Yours will hold real numbers, and they will be different from anyone else.

Learn to read the rest of it, because when a lookup goes wrong the answer section is the part that is missing and the header is the part that says why. The next three steps walk the response from top to bottom. Making this query and reading the whole response is the first of the five things your lab grades.

The header

The lines beginning with ;; above the question are the header. They describe the message itself, not the name you asked about.

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: <random query id>
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1

The status field is the one to read first when something is wrong. NOERROR with an empty answer section is a very different problem from NXDOMAIN, and the two look identical if all you ever print is the address.

The question and the answer

The QUESTION SECTION is the question that was asked, echoed back by the server so you can be certain it understood:

;example.lab.                   IN      A

Read it as: what is the A record for example.lab? An A record maps a name to an IPv4 address. The IN stands for Internet class, which is the only class used in practice.

The ANSWER SECTION is the reply:

example.lab.            0       IN      A       10.0.1.20

Five fields, left to right: the name, the TTL, the class, the record type, and the value. So example.lab has an A record pointing to 10.0.1.20. The 0 is the TTL (Time To Live) in seconds, which tells caching resolvers how long they can keep this answer before asking again.

Commit: what a TTL of 0 asks for

That TTL is the one field in the answer that is not about this lookup at all. It is an instruction to every machine that handles the answer after you.

The footer

The last four lines are four facts about the exchange itself, not about the name:

;; Query time: <milliseconds>
;; SERVER: 10.0.1.20#53(10.0.1.20) (UDP)
;; WHEN: <the date and time the query ran>
;; MSG SIZE  rcvd: <size of the response in bytes>

Strip it down to the address

The full response is what you want when something is broken. The rest of the time you want one line, and a script wants one line it does not have to parse.

+short throws away everything except the value in the answer. The send-dig helper only takes a name, so for anything with options you type the long form.

Before you run it, decide which single line of the previous screen is going to survive.

sudo ip netns exec client dig @10.0.1.20 example.lab +short

prompt: student@dns-lab:~$ answer: sudo ip netns exec client dig @10.0.1.20 example.lab +short output: 10.0.1.20 hint: The same query as before with the option that trims the output: sudo ip netns exec client dig @10.0.1.20 example.lab +short

Just the IP. Nothing else. No header, no question section, no footer. This is what you reach for in scripts, where you need to grab an address without parsing anything.

Nothing changed on the wire. The same query went out and the same response came back, with the same header, the same flags and the same TTL. +short is a printing option: it decides what dig shows you, not what dig asks for. Hold on to that distinction, because +short will happily print nothing at all and tell you nothing about why.

Trimming the output is the second of the five things your lab grades.

Keep the answer section, drop the rest

There is a second way to trim, and it keeps more. +noall turns every section off, and +answer turns one of them back on, so the pair means show the answer section and nothing else.

sudo ip netns exec client dig @10.0.1.20 example.lab +noall +answer

prompt: student@dns-lab:~$ answer: sudo ip netns exec client dig @10.0.1.20 example.lab +noall +answer output: example.lab. 0 IN A 10.0.1.20 hint: Turn every section off, then turn the answer back on: sudo ip netns exec client dig @10.0.1.20 example.lab +noall +answer

This gives you the name, TTL, class, record type and value, but none of the header, question or footer sections.

Choose between the two forms by what you are doing. +short is for handing a value to another program. +noall +answer is for reading with your own eyes, because it keeps the TTL and the record type, and those two fields answer most of the questions you actually have about a record. Either form satisfies the same graded task.

The server holds a table, not one answer

Four names live on this server and they do not all point at the same place. mail.lab is the one that differs.

sudo ip netns exec client dig @10.0.1.20 mail.lab +short

prompt: student@dns-lab:~$ answer: sudo ip netns exec client dig @10.0.1.20 mail.lab +short output: 10.0.1.30 hint: The same trimmed form, with mail.lab in place of example.lab.

A different address, 10.0.1.30, from the same server on the same port. That is what a DNS server is: a table of names, each with its own answer.

web.lab returns 10.0.1.20, the same as example.lab, because several names are allowed to point at one machine. Now try a name that is not in the table at all, such as doesnotexist.lab, with +short on the end. It prints nothing at all: the server has no record for that name, and +short has no answer to print. Drop the +short on that one and run it again, and the header line tells you why.

Commit: which comes first, the capture or the query

DNS queries and responses are just packets on the network. You can capture them with tshark the same way you captured TCP and ICMP traffic in earlier lessons. DNS uses UDP port 53 by default.

One detail decides whether you see anything at all.

Watch the exchange cross the wire

Order matters, so the work comes in two commands. This one runs in the background: it waits three seconds while tshark gets going, then sends the query.

(sleep 3; send-dig) &

Now start the capture immediately, in the same three-second window. -a duration:8 stops it after eight seconds and hands your prompt back. -Y dns is a display filter: capture everything, show only DNS.

Before you run it, commit to a number: how many packets does one name lookup put on the wire? Packet numbers and timestamps will be whatever your run produces.

cap -a duration:8 -Y dns

prompt: student@dns-lab:~$ answer: cap -a duration:8 -Y dns ||| cap -Y dns -a duration:8 output: 1 0.000000 10.0.1.10 -> 10.0.1.20 DNS Standard query A example.lab 2 0.000234 10.0.1.20 -> 10.0.1.10 DNS Standard query response A example.lab A 10.0.1.20 hint: The cap helper runs tshark inside the client namespace: cap -a duration:8 -Y dns

Two packets, and that is the entire conversation.

The first is the query: the client asks what the A record for example.lab is. The second is the response: the server answers 10.0.1.20. Read the addresses across the two lines and you will see them swap: 10.0.1.10 -> 10.0.1.20 first, then 10.0.1.20 -> 10.0.1.10. That is what a request and a reply always look like from the outside.

Notice the protocol column says DNS, not UDP. tshark recognizes DNS traffic on port 53 and decodes it for you. But underneath, DNS is riding on top of UDP, which is riding on top of IP, which is riding on top of Ethernet. Same layered structure as everything else.

Catching those two packets is the third of the five things your lab grades.

Most DNS queries use UDP because the responses are small and UDP is faster (no handshake needed). DNS switches to TCP when the response is too large for one UDP packet. The original limit was 512 bytes; a client can advertise a bigger buffer using an extension called EDNS, and modern resolvers commonly advertise around 1232 bytes. Zone transfers between DNS servers always use TCP.

Milestone: you have watched a name become a number

Stop and count what you have done. You asked a DNS server a question by hand and read every field of its reply. You trimmed that reply two different ways and looked up a second name that answers differently. Then you put a tap on the wire and caught the two packets that carried the whole exchange.

That is the complete path from a name to an address, seen from both ends: the answer as a program receives it, and the packets as the network moves them. Most people who depend on DNS every day have never seen the second half once.

What is left is the fine detail inside those packets, and the file that decides which server gets asked in the first place.

Every field of the packet

Practice DNS in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.

More lessons in The Application Layer