Learn › Linux networking › DNS and application reachability
A real three-machine networking investigation with chapter-specific checks.
Problem: imagine a status page moved to 10.71.20.20, but its name still points to the old address 10.71.20.21. A DNS query can succeed even while the page fails to open. DNS supplies an address, TCP connects to an address and port, and HTTP returns the requested page. You will inspect those three results, repair one chapter-owned DNS record, and verify the page without changing the system resolver.
Prerequisites: Chapters 1 and 2 route and return-path tests, and Chapter 3's distinction between a local listener and Client access through Router. This chapter teaches the DNS and HTTP observations it uses. It starts a fresh three-machine session with routes, forwarding, one explicit Router DNS service and one Server web service already prepared.
Use the named Client, Router and Server terminals. Confirm the terminal name and prompt before every block. Topology shows exercise links, but selecting a diagram node does not move a command to another shell. The current Packets view covers its bounded ICMP capture, not DNS or TCP/HTTP. Read live dig, curl, route and listener results for this problem. Do not use an empty Packets view to claim a DNS query or web request never crossed a link.
Client eth1 is 10.71.10.10/24; Router eth1 is 10.71.10.1/24 and eth2 is 10.71.20.1/24; Server eth1 is 10.71.20.20/24. Router's lab DNS answers explicit questions at 10.71.10.1:53. Server listens for HTTP at 10.71.20.20:8080. The correct A record for app.lab.test is 10.71.20.20, and /health.txt must contain exactly linux-camp-networking-ok followed by a newline. .test names are reserved for tests; this service does not require a public DNS record.
An A record associates a name with an IPv4 address. UDP and TCP are two ways a DNS question can travel to Router port 53; this chapter checks both. The HTTP request uses a separate TCP connection to Server port 8080. dnsmasq is the DNS server program running on Router in this lab.
Keeping the name service on Router and the web service on Server lets you compare the DNS answer with the Server address and a direct web request. That comparison isolates a stale record without changing Client's system resolver or the route to Server.
eth0 carries management. Keep its address, default route, SSH, /etc/resolv.conf, /etc/hosts, unrelated services and unrelated firewall objects unchanged. Do not add a local hosts-file workaround, start a second DNS or web service, or change the Server listener. The chapter's owned additional-hosts record is the only file this guided fault and repair change. The labnet helper permits the chapter's scoped changes; your shell does not need unrestricted sudo.
labnet is a Linux Camp command client, not a standard Linux utility. It sends the proposed record or reload over a local socket to a root-owned service. That service checks this session's DNS file or unit, then runs the real tee or systemctl command. This lab permits only those named changes. Run read commands such as cat, dig and curl directly.
On a Linux machine you administer, a comparable record write is printf '10.71.20.20 app.lab.test\n' | sudo tee /path/to/hosts. For a service that reloads on HUP, the comparable signal is sudo systemctl kill --kill-who=main --signal=HUP name.service. Use your own approved file, service and privileges; the session-owned names in this lab are not general system paths.
On each terminal, confirm the machine and exercise address. Read /etc/lab/chapter.json and require chapterId 4. The descriptor names dnsDir and dnsUnit on Router. These identify this session's DNS file and service; names from an earlier session are invalid. The two assignment commands below use $(...) to save a value in a shell variable. The short Python expression reads one named field from the JSON file; you do not need to write Python or guess the names. Run both assignments in the Router shell and keep using that shell so $LC_DNS_DIR and $LC_DNS_UNIT retain their values.
Read the DNS configuration and record on Router, and inspect the port 53 listener. dns.conf tells this dnsmasq service to read the owned hosts file as an additional source of names and not to read Router's system /etc/hosts. The owned file can change the answer that Client receives from this explicit DNS service. Editing /etc/hosts would affect local name lookup on a machine and would not repair this service's record. The expected DNS listener is restricted to Router's exercise address, 10.71.10.1, rather than management eth0.
hostname
ip -br -4 address
cat /etc/lab/chapter.json
# Router only: assign names from this session's descriptor
LC_DNS_DIR=$(python3 -c 'import json; print(json.load(open("/etc/lab/chapter.json"))["dnsDir"])')
LC_DNS_UNIT=$(python3 -c 'import json; print(json.load(open("/etc/lab/chapter.json"))["dnsUnit"])')
cat "$LC_DNS_DIR/dns.conf"
cat "$LC_DNS_DIR/hosts"
ss -H -lnut '( sport = :53 )'
On Server, ss -H -lnt '( sport = :8080 )' should show its exercise-address listener. If the descriptor, assigned addresses or listeners differ, preserve the output and report that the prepared setup differs. Do not fix a different starting state through this chapter's record action.
machine: client prompt: admin@client:~$ answer: hostname output:
hint: The shell name must match the machine receiving the command.
machine: router prompt: admin@router:~$ answer: LC_DNS_DIR=$(python3 -c 'import json; print(json.load(open("/etc/lab/chapter.json"))["dnsDir"])') output:
hint: Use the descriptor in this Router shell; the name changes with the session.
machine: router prompt: admin@router:~$ answer: LC_DNS_UNIT=$(python3 -c 'import json; print(json.load(open("/etc/lab/chapter.json"))["dnsUnit"])') output:
hint: Use the unit name from this Router session's descriptor.
machine: router prompt: admin@router:~$ answer: cat "$LC_DNS_DIR/hosts" output:
hint: Compare its one A-record line with Server's assigned address.
machine: router prompt: admin@router:~$ answer: ss -H -lnut '( sport = :53 )' output:
hint: Find the UDP and TCP listeners bound to Router's exercise address.
machine: server prompt: admin@server:~$ answer: ss -H -lnt '( sport = :8080 )' output:
hint: The listener should bind Server's 10.71.20.20 exercise address.
DNS maps a name to an address. The A record says which IPv4 address the lab DNS currently associates with app.lab.test; it does not prove that a machine answers there. TCP connects to an address and port; a connection alone does not prove the requested HTTP document is correct. HTTP returns a status and body from the service reached. The three results can disagree.
Before running a request, predict what each test would show if DNS returned the correct address but the web service were stopped. Then predict what would differ if the web service worked at 10.71.20.20 but DNS returned the unassigned 10.71.20.21. The address .21 is reserved and unassigned in this lab; do not use it as a second service.
/health.txt returned the required body>>> The DNS answer is one observation. Compare it with the assigned Server address, then test the selected socket and resource separately.
On Client, ask Router DNS over UDP and TCP. The DNS server named after @ is explicit, so the guest's default resolver is not part of this query. dig +tcp changes the transport of the DNS question to Router port 53; it does not test the web connection to Server port 8080. Keep the status, A answer, queried server and any error. A successful dig process exit does not guarantee the answer is useful.
# Client
dig @10.71.10.1 app.lab.test A +time=2 +tries=1
dig @10.71.10.1 app.lab.test A +tcp +time=2 +tries=1
ip -4 route get 10.71.20.20
curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt
The route lookup names the next hop and interface; it does not prove delivery. The direct-address curl tests the application path without DNS. --noproxy '*' prevents an environment proxy from moving the request elsewhere. The timeout bounds an unavailable target. On Server, request its own exercise address and compare the body with Client's result:
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 app.lab.test A +time=2 +tries=1 output:
hint: Read the DNS status and A answer, not just the command exit.
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 app.lab.test A +tcp +time=2 +tries=1 output:
hint: Check the same name and address with TCP selected explicitly.
machine: client prompt: admin@client:~$ answer: curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt output:
hint: This tests Server's TCP and HTTP path without using DNS.
# Server
ss -H -lnt '( sport = :8080 )'
curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt
machine: server prompt: admin@server:~$ answer: curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt output:
hint: A local success does not by itself prove the Client path.
Now use the A address actually returned by the UDP query in a Client request to http://app.lab.test:8080/health.txt. If the answer is one 10.71.20.20 address, use this worked command:
curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 --resolve app.lab.test:8080:10.71.20.20 http://app.lab.test:8080/health.txt
--resolve makes this one curl request connect to the address you supplied while keeping app.lab.test in the URL. If the observed answer changes, change only the address after --resolve to match it. This tests the destination selected from your explicit DNS observation; curl itself does not ask Router DNS in this command. It does not prove that Client's system resolver was changed or repaired. Save the full DNS response beside the request.
The healthy baseline should give 10.71.20.20 through both DNS transports, the exact HTTP body through direct and DNS-selected requests, and a Server listener on its exercise address. These are predictions until your live outputs confirm them. Run Check chapter after collecting the baseline. Its state feedback is formative and its probes are not your own diagnosis.
On Router, record the current owned hosts line, then change only that file to 10.71.20.21 app.lab.test. This simulates a name left pointing to an old address. The first command below uses printf to produce exactly one record followed by \n, a newline. The pipe (|) sends that line to labnet tee, which replaces the permitted owned file. It does not append another record. The second command sends HUP, a Linux signal or message to a running process, to the main process of this session's DNS unit. For this dnsmasq service, HUP rereads the additional-hosts file; it does not reload every DNS setting or stop the service. Keep /etc/hosts, /etc/resolv.conf, Server, routes and management unchanged.
.21, while direct HTTP to assigned .20 still returns the marker.20 HTTP must return the marker through .21>>> The two requests use different destinations. Keep the direct .20 test to distinguish a bad name answer from a broken service path.
Before the action, predict four outcomes: the explicit DNS answer, the DNS-selected HTTP request, the direct request to Server 10.71.20.20:8080, and Server's local request. Read the exact Router commands, then enter them in Exercises 4-11 and 4-12:
# Router, after assigning LC_DNS_DIR and LC_DNS_UNIT in 4.1
printf '10.71.20.21 app.lab.test\n' | labnet tee "$LC_DNS_DIR/hosts"
labnet systemctl kill --kill-who=main --signal=HUP "$LC_DNS_UNIT.service"
Afterward, repeat those tests and compare with the prediction. If DNS returns exactly one .21 answer, make the name-based request with --resolve app.lab.test:8080:10.71.20.21 and the same URL and timeout options from 4.3. If DNS answers .21 while the direct and local .20 requests succeed, the name points away from the working service. No particular curl error string or neighbor timeout is guaranteed; report the actual exit and output.
If DNS still answers .20, inspect the owned record, named service and its reload evidence before concluding the fault was applied. The HUP signal may take a moment to affect answers; ask again after a short wait. Do not change unrelated configuration to force the expected failure.
machine: router prompt: admin@router:~$ answer: printf '10.71.20.21 app.lab.test\n' | labnet tee "$LC_DNS_DIR/hosts" output:
hint: Use only the current session's owned record file and the reserved unassigned address.
machine: router prompt: admin@router:~$ answer: labnet systemctl kill --kill-who=main --signal=HUP "$LC_DNS_UNIT.service" output:
hint: A reload is needed after the owned additional-hosts file changes.
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 app.lab.test A +time=2 +tries=1 output:
hint: Compare the answer with Topology's assigned Server address.
machine: client prompt: admin@client:~$ answer: curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt output:
hint: A working direct request shows the assigned service is still reachable.
Explain why the two direct .20 successes and the wrong explicit answer support a record repair. On Router, restore the same owned record with Server's assigned address, then signal the same DNS unit to reread that file. Only the address in the printf line changes. Read these commands, then enter the first two in Exercises 4-15 and 4-16. Read the file afterward to confirm it contains one corrected line, then use fresh Client queries to verify the answer changed.
# Router, using the same session variables from 4.1
printf '10.71.20.20 app.lab.test\n' | labnet tee "$LC_DNS_DIR/hosts"
labnet systemctl kill --kill-who=main --signal=HUP "$LC_DNS_UNIT.service"
cat "$LC_DNS_DIR/hosts"
Repeat UDP and TCP queries, the direct request, and a request using the newly observed answer. Query an unlisted name too:
# Client
dig @10.71.10.1 app.lab.test A +time=2 +tries=1
dig @10.71.10.1 app.lab.test A +tcp +time=2 +tries=1
dig @10.71.10.1 absent.lab.test A +time=2 +tries=1
curl --noproxy '*' --fail --connect-timeout 2 --max-time 4 http://10.71.20.20:8080/health.txt
The unlisted name must not resolve to the application by a wildcard workaround. Compare its response status and answer; absence need not mean a transport timeout. Recheck the Server listener, exact body, the original Client management resolver and hosts file, and unchanged exercise routing. Then run Check chapter. Its ten current-state conditions include explicit UDP and TCP DNS, unknown-name behavior, DNS-following and direct HTTP, local service, listener scope, prepared service integrity and preserved network state. A green check does not review your explanation.
machine: router prompt: admin@router:~$ answer: printf '10.71.20.20 app.lab.test\n' | labnet tee "$LC_DNS_DIR/hosts" output:
hint: Match Server's assigned exercise address; do not add another name.
machine: router prompt: admin@router:~$ answer: labnet systemctl kill --kill-who=main --signal=HUP "$LC_DNS_UNIT.service" output:
hint: Verify a fresh DNS answer after the owned service receives HUP.
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 app.lab.test A +time=2 +tries=1 output:
hint: Require one A answer matching Server's assigned exercise address.
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 app.lab.test A +tcp +time=2 +tries=1 output:
hint: Confirm the corrected record over TCP as well as UDP.
machine: client prompt: admin@client:~$ answer: dig @10.71.10.1 absent.lab.test A +time=2 +tries=1 output:
hint: This name must not return the application address.
On a later attempt, consider a changed case where DNS returns the assigned address but Server listens only on loopback. Predict how Client's explicit DNS query, direct request, and Server's listener listing would differ from the wrong-record case. A local request to 127.0.0.1 may work while a request to Server's 10.71.20.20 fails. Changing the correct DNS record would not change where the web process listens. Submit this changed case as a diagnosis plan and label it unexecuted.
Save your real query status and answer, selected destination, Client and Server HTTP results, exact scoped diff and post-repair checks before ending the disposable session. State one limit of each test. In particular, explicit dig and per-request --resolve do not establish how an unrelated program using the system resolver would behave.
References: DNS concepts, RFC 1034, Debian dnsmasq manual, Debian curl manual. Compare the installed guest versions before applying version-specific guidance.
Practice Chapter 4. DNS and application reachability in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.