Exercise 4: Dynamic DNS (DDNS) with DHCP and BIND9

Building on Exercises 1–3, extend srv1 so that every client that receives a lease from the DHCP service is automatically registered in DNS. Nobody should have to edit a zone file by hand when a new machine joins the LAN segment, and nobody who is not srv1’s DHCP service should be able to change DNS records.

1. BIND9: Accept Secured Dynamic Updates

Configure BIND9 on srv1 so that the forward zone mydomain.intern and the reverse zone for 172.30.0.0/16 accept dynamic DNS updates (RFC 2136).

  • Updates must be authenticated with a TSIG key (shared secret). Updates without a valid key must be rejected. Restricting updates by source IP address alone is not sufficient.
  • Generate a dedicated key for DDNS and store it so that only the services that need it can read it.
  • Only the two zones above may be updated. Do not open up anything else.
  • Both zones must still load without errors, and all existing lookups from Exercises 1–3 (srv1, ns1, reverse lookup of 172.30.0.1, forwarder) must keep working.
  • Think about the static client1 records you added in Exercise 2. client1 now gets its address via DHCP, so decide how those records fit into a zone that is managed dynamically.

Hint

  • tsig-keygen (package bind9-utils) creates a key in BIND syntax. Keep it in its own file and include it from your BIND configuration.
  • allow-update or update-policy in the zone definitions.
  • For a /16 network the reverse zone is usually named 30.172.in-addr.arpa. Use the name you already set up in Exercise 1.
  • BIND stores dynamic changes in a journal file (.jnl) next to the zone file. The named process must be allowed to write there. On Debian, AppArmor restricts where named may write, so check journalctl / dmesg for denials if updates fail with permission errors.
  • Once a zone is dynamic, do not edit its zone file while named is running without rndc freeze / rndc thaw.
  • named-checkconf and named-checkzone validate your changes before you restart the service.

2. DHCP Server: Send DDNS Updates

Extend the isc-dhcp-server configuration from Exercise 3 so that for every lease:

  • a forward record (A) and a reverse record (PTR) are created automatically in the correct zones,
  • the updates are sent to BIND9 on srv1 and signed with the TSIG key from step 1,
  • the hostname is taken from what the client announces in its DHCP request, and the domain part is mydomain.intern.

The key secret is now part of your DHCP configuration as well, so make sure it is not readable by unprivileged users.

Hint

  • Relevant dhcpd.conf directives: ddns-updates, ddns-update-style, ddns-domainname, ddns-rev-domainname, key, and zone blocks with primary and key.
  • Zone names in dhcpd.conf must match the BIND zone names exactly (mind the trailing dot).
  • The primary address must be one that named actually listens on (check listen-on in the BIND options).
  • dhcpd -t -cf /etc/dhcp/dhcpd.conf checks the configuration syntax.
  • Both services log update attempts and failures: journalctl -u bind9 and journalctl -u isc-dhcp-server.

3. New Client VM: client2

Create a third VM that joins the network without any manual DNS or address configuration on srv1.

  • Debian 13 (Trixie) minimal installation (no GUI), hostname client2.
  • Two network cards: Interface 1 (WAN) via DHCP (NAT), Interface 2 (LAN) attached to the same host-only network as srv1 and client1.
  • The LAN interface must obtain its configuration via DHCP (no static address).
  • IPv4 only, same as the other VMs.

Hint

  • The DDNS update can only use a hostname if the client sends one in its DHCP request. How this is configured depends on the DHCP client in use (e.g. dhcpcd, dhclient, systemd-networkd, NetworkManager). Check in the dhcpd log which hostname the server actually received.
  • If you clone an existing VM, make sure MAC address, hostname and machine-id are unique. Otherwise the DHCP server may treat the clone as the same client and hand out the same lease.

4. Verification & Proof

Test and document the following:

  1. Before joining: Before the LAN interface of client2 is brought up for the first time, confirm that client2.mydomain.intern does not resolve (e.g. NXDOMAIN from dig).
  2. Lease and update: Confirm client2 received an IPv4 address from the DHCP pool and that srv1 shows the corresponding lease. Show the log lines on srv1 that prove the DDNS update was sent and accepted.
  3. Forward lookup: Resolve client2.mydomain.intern with dig and nslookup from client2, client1 and srv1. The result must be the address client2 received via DHCP.
  4. Reverse lookup: Resolve the IP address of client2 back to its hostname with dig -x and nslookup, again from all three machines.
  5. What else is in the zone? Look at which additional record(s) the DHCP server stored next to the A record of client2 and explain their purpose. (e.g. dig for different record types, or named-journalprint on the journal file)
  6. Security — unauthorized update: Try to add or change a record with nsupdate from client2 without a key and with a wrong key. Both attempts must fail. Document the error and the corresponding BIND log entry.
  7. Security — authorized update: From srv1, perform a manual update with the correct key (nsupdate -k), confirm it works with dig, and delete the test record again.
  8. Security — key handling: Show the file permissions of every file that contains the TSIG secret, and show that the BIND zone configuration does not allow updates from any or from an IP address alone.
  9. Persistence: Restart BIND9 on srv1 and confirm the dynamic records of client2 are still resolvable afterwards.
  10. Existing client: Renew the lease on client1 (or reboot it) and confirm that its records are now consistent (one correct A record, one correct PTR record, no stale static leftovers). Repeat the forward and reverse lookup.

Optional

Temporarily set a short lease time on srv1. Release the lease on client2 (or shut it down and wait for expiry) and observe what happens to its DNS records.