Network Fundamentals — Hands-on in a Linux VM
Follow a Name All the Way Down
This lab runs on a VM
On Ubuntu 24.04, systemd-resolved handles name resolution. You set up a second DNS server of your own
with dnsmasq next to it, and move between the two layers to see where a name gets its answer.
dnsmasq is already installed, but its service is in a failed state — finding out why
is step 3.
Goal
Confirm with commands what the stub address in /etc/resolv.conf is, where the real upstream server is,
how to point your own DNS server at only a particular domain, and why /etc/hosts beats DNS.
Why it matters
The first fork of "it doesn't work" is the name. But on modern Linux, name resolution
is not a single layer — the application calls libc's getaddrinfo, which
looks at /etc/hosts first in the order given by nsswitch.conf, then asks the stub in resolv.conf
(127.0.0.53), and the stub looks at the per-link settings and passes the query upstream. dig taps
only the last of these directly. That is why it really happens that dig works but curl does not,
and you can fix it only if you know which layer differs.
Steps
- Save the actual file path that
/etc/resolv.confpoints to and itsnameserverline to/root/dns/resolv.txt. - Save the address of the upstream DNS server that the stub actually queries to
/root/dns/upstream.txtas one line,upstream=<주소>(upstream= followed by the address). - Find out why dnsmasq does not start, and save the
ssoutput showing the process holding port 53 to/root/dns/port53.txt. - Attach
10.53.0.1/24to a dummy interfacedns0, and bring the service up by using/etc/dnsmasq.d/lab.confto make dnsmasq listen only on10.53.0.1and answerapp.lab.internal→10.53.0.10anddb.lab.internal→10.53.0.20. - Save the full output of
dig @10.53.0.1 app.lab.internalto/root/dns/dig.txt. - Use
resolvectlto set the DNS server10.53.0.1and the routing domain~lab.internalon thedns0link so thatgetent hosts app.lab.internalworks, and saveresolvectl status dns0to/root/dns/link.txt. - Put
10.53.0.99 db.lab.internalin/etc/hosts, and save the answers fromgetentanddig @10.53.0.1to/root/dns/hosts.txtas two lines,getent=·dig=. - In
/root/dns/report.md, write three lines,stub=·upstream=·local=, oneapp.lab.internalline from dnsmasq's query log, and the reason/etc/hostsbeats DNS.
Notes
readlink -f /etc/resolv.conf,resolvectl status,resolvectl dns,resolvectl domain.- The process holding the port:
ss -ulpn 'sport = :53'. The reason for the failure is injournalctl -u dnsmasq. - dnsmasq configuration keys:
listen-address=,bind-interfaces,no-resolv,log-queries,address=/이름/주소(that is, address=/name/address). After fixing, runsystemctl restart dnsmasq. - Common mistake 1: leaving out
bind-interfaces. Then dnsmasq tries to take port 53 on every address and collides with the stub again. - Common mistake 2: dropping the
~in front of the domain in step 6. Without~it becomes a search domain and is only appended after short names; it does not send queries to that server.
What resolv.conf really is
Save the actual file path that /etc/resolv.conf points to and its nameserver line to /root/dns/resolv.txt.
readlink -f /etc/resolv.conf gives the real path at the end of the symlink. Look at the stub address with grep nameserver /etc/resolv.conf. Put both in one file.
Where is the real server
Save the address of the upstream DNS server that the stub actually queries to /root/dns/upstream.txt as one line, upstream=<주소> (upstream= followed by the address).
The link entry in resolvectl status has Current DNS Server. It is the value for the default-route interface (enp1s0). resolvectl dns enp1s0 is shorter.
Why dnsmasq won't start
Find out why dnsmasq does not start, and save the ss output showing the process holding port 53 to /root/dns/port53.txt.
systemctl status dnsmasq and journalctl -u dnsmasq show Address already in use. To see who holds it, run ss -ulpn 'sport = :53' — -p shows the process name (you must be root).
Set up your own DNS server
Attach 10.53.0.1/24 to a dummy interface dns0, and bring the service up by using /etc/dnsmasq.d/lab.conf to make dnsmasq listen only on 10.53.0.1 and answer app.lab.internal→10.53.0.10 and db.lab.internal→10.53.0.20.
After ip link add dns0 type dummy, add the address and bring it UP. In the configuration, make it hold only that address with listen-address=10.53.0.1 and bind-interfaces, and pin the names like address=/app.lab.internal/10.53.0.10. no-resolv means you will not use an upstream. When you are done, run systemctl restart dnsmasq and then systemctl is-active dnsmasq.
Ask directly with dig
Save the full output of dig @10.53.0.1 app.lab.internal to /root/dns/dig.txt.
@서버 (@ followed by the server) ignores resolv.conf and asks that server directly. The ANSWER SECTION of the output must show the A record, and the header must show status: NOERROR.
Attach your server to the stub
Use resolvectl to set the DNS server 10.53.0.1 and the routing domain ~lab.internal on the dns0 link so that getent hosts app.lab.internal works, and save resolvectl status dns0 to /root/dns/link.txt.
resolvectl dns dns0 10.53.0.1 and resolvectl domain dns0 '~lab.internal'. A domain with ~ is a routing rule meaning "send queries for this domain to this link's server". To check, run getent hosts app.lab.internal — 10.53.0.10 must come out.
/etc/hosts wins
Put 10.53.0.99 db.lab.internal in /etc/hosts, and save the answers from getent and dig @10.53.0.1 to /root/dns/hosts.txt as two lines, getent=·dig=.
echo '10.53.0.99 db.lab.internal' >> /etc/hosts. Then getent hosts db.lab.internal | awk '{print $1}' and dig @10.53.0.1 db.lab.internal +short. The two values differ — that is the point of this step.
What you learned
In /root/dns/report.md, write three lines, stub=·upstream=·local=, one app.lab.internal line from dnsmasq's query log, and the reason /etc/hosts beats DNS.
stub is the nameserver from step 1, upstream is the value from step 2, and local is your own dnsmasq address. For the log, use journalctl -u dnsmasq --no-pager | grep 'query\[A\] app.lab.internal'. The reason must include the word nsswitch or files.