Build a DHCP Server Lab
Run a DHCP server in Linux namespaces and capture the DORA handshake.
Introduction
30 Second Summary
Every time you connect to WiFi, your device silently asks the network for an IP address. A DHCP server answers that request, handing out addresses, gateways, and DNS settings so you never have to configure anything manually.
In this project, you will build a DHCP server inside an isolated virtual network running entirely on your Ubuntu VM. You'll configure address pools and options, watch a client go from zero network config to fully connected, and capture the entire four-message handshake in Wireshark to see exactly how devices get their network identity.
What You'll Build
By the end of this lab, you can type one command in your client namespace and watch it go from no IP address to fully configured with an address, gateway, and DNS server, then open a packet capture that shows every message the server and client exchanged to make it happen.
By the end of this project, you'll have:
- A virtual network with two isolated hosts connected by a veth pair, where you can run a DHCP server that hands out addresses from a configured scope to a client that starts with nothing.
- A fully configured DHCP server with options for gateway, DNS, and domain name, plus a MAC-based reservation that guarantees a specific client always gets the same IP.
- A .pcap capture file showing all four DHCP messages (Discover, Offer, Request, ACK) dissected in Wireshark, proving you understand the protocol on the wire.
- Secret Mission: Simulate a rogue DHCP server and observe what happens when two servers compete on the same network.
Are there any prerequisites?
You need an Ubuntu 24.04 VM (this project uses Parallels, but any hypervisor works) and basic comfort with Linux terminal commands like cd, nano, and running commands with sudo. All tools used in this project are free and installed from Ubuntu's default package repositories.
Before We Start
Before we dive in, let's get clear on what you're about to build and why it matters to your networking journey. This commitment gives every command in the lab a purpose.
Set Up Your Lab Tools
To build a working DHCP lab, you need four pieces of software: a DHCP server, a DHCP client, a packet capture tool, and a packet analyzer.
In this step, you will install all four tools from the Ubuntu repositories and configure permissions so you can analyze captured traffic later in Wireshark.
In this step, get ready to:
- Install the DHCP server, client, and packet capture tools.
- Configure Wireshark permissions for your user account.
- Verify all four tools are accessible from the terminal.
Install the lab packages
Your Ubuntu VM ships with none of the DHCP or capture tools pre-installed. The dnsmasq-base package provides the dnsmasq binary without starting a system-wide daemon, which is exactly what you need for a controlled lab.
- Update your package lists and install all four packages by running these commands:
sudo apt update
sudo apt install -y dnsmasq-base isc-dhcp-client tcpdump wireshark
What do these packages do?
- dnsmasq-base provides the DHCP server binary without installing a system daemon that conflicts with systemd-resolved.
- isc-dhcp-client provides dhclient, the command-line tool for requesting DHCP leases.
- tcpdump captures network packets from the terminal and writes .pcap files.
- wireshark provides a GUI for inspecting captured packets in detail.
During installation, you will see a blue/pink dialog asking about Wireshark capture permissions. The next substep covers how to respond.
Seeing errors during apt install?
If you see a "Unable to locate package" error, your package lists may be stale. Run sudo apt update again, then retry the install command.
Help me fix apt install errors on Ubuntu 24.04
Configure Wireshark capture permissions
Wireshark uses a helper program called dumpcap to capture packets. By default, only root can capture. Adding your user to the wireshark group grants capture access without running as root.
- When the blue/pink dialog appears during installation asking "Should non-superusers be able to capture packets?", select Yes and press Enter.
- After installation completes, add your user to the wireshark group by running this command:
sudo usermod -aG wireshark $USER
Why add yourself to the wireshark group?
Selecting Yes in the dialog configures dumpcap so that members of the wireshark group can capture packets. You still need to manually add your user to that group.
In this lab, you will use tcpdump inside network namespaces (with sudo) for capture, and Wireshark only to open .pcap files. The group membership avoids permission warnings when you launch Wireshark later. The change takes effect on your next login session.
Missed the dialog?
If you accidentally selected No, or the dialog did not appear, reconfigure it by running:
sudo dpkg-reconfigure wireshark-common
Select Yes this time, then re-run the sudo usermod -aG wireshark $USER command.
Help me reconfigure Wireshark permissions
Verify all four tools are installed
Before you build anything, confirm that every tool responds to a version check. If any command fails, you know exactly which package to reinstall.
- Check that dnsmasq is available by running:
dnsmasq --version
Before you look at the output, what version number do you expect to see based on the Ubuntu 24.04 package?
You should see Dnsmasq version 2.90 at the top of the output, followed by a list of compile-time options.
- Check dhclient, tcpdump, and wireshark by running these commands:
dhclient --version
tcpdump --version
wireshark --version
Each command prints its version number and exits. As long as none of them return "command not found", your tools are ready.
Seeing "command not found"?
If dnsmasq is not found, the dnsmasq-base package may not have installed correctly. Re-run sudo apt install dnsmasq-base and watch for errors.
If dhclient is not found, install it explicitly with sudo apt install isc-dhcp-client.
Help me fix command not found errors
Your toolbox is ready. Next up, you will build the virtual network that connects your DHCP server and client.
Build the Virtual Network
Your lab tools are installed and ready. Now you need two isolated "machines" that can talk to each other before you can test DHCP.
Linux network namespaces give you completely separate network stacks without needing extra VMs. Each namespace has its own interfaces, routing table, and firewall rules. You will connect two namespaces with a virtual Ethernet cable (a veth pair) so one can act as your DHCP server and the other as a client.
In this step, get ready to:
- Create two network namespaces connected by a veth pair.
- Test connectivity between namespaces with a ping.
- Verify that each namespace is fully isolated from the host.
Create and run the lab setup script
Instead of typing each command one by one, you will write a setup script that builds the entire virtual network in one run. This makes the lab repeatable and easy to tear down and rebuild.
- Create a project directory and move into it by running these commands:
mkdir ~/dhcp-lab
cd ~/dhcp-lab
- Confirm you are inside the new directory by running:
pwd
You should see /home/<your-username>/dhcp-lab in the output.
- Create the setup script by running:
nano setup-lab.sh
- Paste the following script into the nano editor:
#!/bin/bash
# DHCP Lab Setup Script
# Creates two network namespaces connected by a veth pair
set -e
echo "Creating network namespaces..."
sudo ip netns add server-ns
sudo ip netns add client-ns
echo "Creating veth pair..."
sudo ip link add veth-server type veth peer name veth-client
echo "Moving interfaces into namespaces..."
sudo ip link set veth-server netns server-ns
sudo ip link set veth-client netns client-ns
echo "Configuring server namespace..."
sudo ip netns exec server-ns ip addr add 10.0.100.1/24 dev veth-server
sudo ip netns exec server-ns ip link set veth-server up
sudo ip netns exec server-ns ip link set lo up
echo "Bringing up client interface (no IP)..."
sudo ip netns exec client-ns ip link set veth-client up
sudo ip netns exec client-ns ip link set lo up
echo "Lab network ready!"
echo "Server namespace: server-ns (10.0.100.1/24 on veth-server)"
echo "Client namespace: client-ns (veth-client, no IP yet)"
- Save the file by pressing Ctrl+O, then Enter to confirm the filename.
- Exit nano by pressing Ctrl+X.
What does this script do?
The script creates two network namespaces: server-ns (where your DHCP server will run) and client-ns (the machine requesting an IP address).
It then creates a veth pair, which is a virtual Ethernet cable with two ends. One end (veth-server) goes into the server namespace and gets a static IP of 10.0.100.1/24. The other end (veth-client) goes into the client namespace with no IP. The client will get its IP from DHCP later.
- Make the script executable and run it:
chmod +x setup-lab.sh
sudo bash setup-lab.sh
You should see each step printed as it completes, ending with "Lab network ready!" and a summary of both namespaces.
Seeing "RTNETLINK answers: File exists"?
This means the namespaces or interfaces already exist from a previous run. Delete them first, then re-run the script:
sudo ip netns del server-ns 2>/dev/null || true
sudo ip netns del client-ns 2>/dev/null || true
sudo bash setup-lab.sh
Still stuck? Help me troubleshoot the setup-lab.sh script error.
Test connectivity between namespaces
The virtual cable is in place, but you have not proven that traffic can actually flow across it. The client side has no IP address yet, so you will assign a temporary one just to run a quick connectivity test.
- Assign a temporary IP to the client interface by running:
sudo ip netns exec client-ns ip addr add 10.0.100.99/24 dev veth-client
Before you run the next command, predict: will the client namespace be able to reach the server at 10.0.100.1?
- Ping the server namespace from the client by running:
sudo ip netns exec client-ns ping -c 2 10.0.100.1
You should see two successful replies with round-trip times. This proves the veth pair is working as a virtual cable between your two namespaces.
- Now remove the temporary IP so the client starts clean for the DHCP test later:
sudo ip netns exec client-ns ip addr del 10.0.100.99/24 dev veth-client
Why remove the temporary IP?
The whole point of DHCP is that the client starts with no IP address and receives one automatically from the server. By removing the temporary address, you are resetting the client to its "blank" state so DHCP can do its job in the next step.
Ping says "Network is unreachable"?
Confirm both interfaces are up by running:
sudo ip netns exec server-ns ip link show veth-server
sudo ip netns exec client-ns ip link show veth-client
Both should show state UP in the output. If either shows state DOWN, re-run your setup-lab.sh script after cleaning up the namespaces.
Still not working? Help me debug why my namespaces cannot ping each other.
Verify namespace isolation from the host
Network namespaces are truly isolated from each other and from your host. Each one sees only its own interfaces. Confirming this proves that your DHCP traffic will stay contained inside the lab.
- Check what the server namespace can see by running:
sudo ip netns exec server-ns ip addr
You should see only two interfaces: lo (loopback) and veth-server with 10.0.100.1/24.
- Now check what the client namespace sees:
sudo ip netns exec client-ns ip addr
You should see lo and veth-client with no IP address assigned.
- Compare with what the host sees:
ip addr
The host shows its own interfaces (like enp0s5 or eth0) but has no visibility into veth-server or veth-client. Each network stack is completely separate.
Why does this matter for DHCP testing?
This isolation guarantees that your lab DHCP server cannot accidentally hand out addresses to other devices on your real network. The broadcast traffic stays trapped inside the namespace pair. This is the same isolation primitive that Docker uses to separate container networks.
✔️ Awesome, I've got everything!
Great. Double check you have saved setup-lab.sh and that both namespaces are running.
ⓧ I'd like to double check the full code
Here is the complete setup-lab.sh script. Make sure yours matches:
#!/bin/bash
# DHCP Lab Setup Script
# Creates two network namespaces connected by a veth pair
set -e
echo "Creating network namespaces..."
sudo ip netns add server-ns
sudo ip netns add client-ns
echo "Creating veth pair..."
sudo ip link add veth-server type veth peer name veth-client
echo "Moving interfaces into namespaces..."
sudo ip link set veth-server netns server-ns
sudo ip link set veth-client netns client-ns
echo "Configuring server namespace..."
sudo ip netns exec server-ns ip addr add 10.0.100.1/24 dev veth-server
sudo ip netns exec server-ns ip link set veth-server up
sudo ip netns exec server-ns ip link set lo up
echo "Bringing up client interface (no IP)..."
sudo ip netns exec client-ns ip link set veth-client up
sudo ip netns exec client-ns ip link set lo up
echo "Lab network ready!"
echo "Server namespace: server-ns (10.0.100.1/24 on veth-server)"
echo "Client namespace: client-ns (veth-client, no IP yet)"
Your virtual network is live. Two isolated namespaces are connected by a veth pair, and you have proven they can communicate. Next up, you will start a DHCP server inside the server namespace and watch the client request its first IP address automatically.
Start the DHCP Server (Without Options)
Your virtual network is connected and ready for traffic. Now you need something to actually hand out IP addresses on that network.
In this step, you will configure dnsmasq as the simplest possible DHCP server: just an address pool with no extras. The client will get an IP address, but it will have no gateway and no DNS server. That gap is intentional.
In this step, get ready to:
- Create a minimal dnsmasq configuration file with only a DHCP address pool.
- Start dnsmasq inside the server namespace and confirm it is listening.
- Request a lease from the client namespace and observe the missing gateway and DNS.
Create the dnsmasq configuration file
A DHCP server needs to know which range of IP addresses it can hand out, which interface to listen on, and how long each lease lasts. You will define all of this in a configuration file.
- Create the configuration file by running:
nano ~/dhcp-lab/dnsmasq-basic.conf
- Paste the following configuration into the file:
# Basic DHCP server config - pool only, no options
# This deliberately omits gateway and DNS to demonstrate the limitation
interface=veth-server
bind-interfaces
# Disable DNS functionality (DHCP only)
port=0
# DHCP scope: pool of addresses to hand out
dhcp-range=10.0.100.50,10.0.100.200,255.255.255.0,2m
# Lease file location
dhcp-leasefile=/tmp/dnsmasq-lab.leases
# Log DHCP transactions
log-dhcp
What does this configuration do?
- interface=veth-server and bind-interfaces tell dnsmasq to listen only on the server-side veth interface inside the namespace.
- port=0 disables DNS functionality entirely so dnsmasq runs as a pure DHCP server.
- dhcp-range defines the pool of addresses (10.0.100.50 through 10.0.100.200) with a 2-minute lease time.
- log-dhcp makes dnsmasq log every DHCP transaction so you can see what is happening.
- Save the file with Ctrl+O, press Enter, then exit nano with Ctrl+X.
- Confirm the file was created by running:
ls ~/dhcp-lab/dnsmasq-basic.conf
You should see the file path printed back to you with no errors.
File not found?
- Check that you saved the file in the correct location. The path should be ~/dhcp-lab/dnsmasq-basic.conf.
- If nano created it elsewhere, move it with mv or delete it and re-create in the correct folder.
Help me find my dnsmasq-basic.conf file
✔️ Awesome, I've got everything!
Great. Double check you have saved the file before moving on.
ⓧ I'd like to double check the full code
Your dnsmasq-basic.conf should look exactly like this:
# Basic DHCP server config - pool only, no options
# This deliberately omits gateway and DNS to demonstrate the limitation
interface=veth-server
bind-interfaces
# Disable DNS functionality (DHCP only)
port=0
# DHCP scope: pool of addresses to hand out
dhcp-range=10.0.100.50,10.0.100.200,255.255.255.0,2m
# Lease file location
dhcp-leasefile=/tmp/dnsmasq-lab.leases
# Log DHCP transactions
log-dhcp
Start dnsmasq in the server namespace
The configuration file is ready. Now you will launch dnsmasq inside the server namespace so it starts listening for DHCP requests on the virtual interface.
- Start dnsmasq inside the server namespace by running:
sudo ip netns exec server-ns dnsmasq -C ~/dhcp-lab/dnsmasq-basic.conf
What does this command do?
sudo ip netns exec server-ns runs the following command inside the server-ns namespace. The -C flag tells dnsmasq to use your custom configuration file instead of the system default.
dnsmasq daemonizes (forks into the background) automatically, so your terminal prompt returns immediately.
- Verify dnsmasq is listening on UDP port 67 by running:
sudo ip netns exec server-ns ss -ulnp
You should see a line showing port 67 in the output with dnsmasq listed as the process. The state will show as UNCONN because UDP is connectionless.
No output or dnsmasq not listed?
- If you see an error about the config file not being found, check the path matches ~/dhcp-lab/dnsmasq-basic.conf exactly.
- If dnsmasq exited silently, run it with the -d flag (debug/foreground mode) to see error output: sudo ip netns exec server-ns dnsmasq -C ~/dhcp-lab/dnsmasq-basic.conf -d.
- If port 67 is already in use, kill any existing dnsmasq processes first: sudo ip netns exec server-ns pkill dnsmasq.
Why is dnsmasq not showing on port 67?
Request a lease from the client namespace
The DHCP server is running. Now you will act as the client and request an IP address from it.
- Request a DHCP lease in the client namespace by running:
sudo ip netns exec client-ns dhclient veth-client
What does this command do?
dhclient is a DHCP client. It sends a broadcast Discover message on veth-client, waits for an Offer from dnsmasq, then completes the Request/Acknowledge handshake to obtain a lease.
- Before you check the result, predict: will the client have a default gateway?
- Check the IP address assigned to the client by running:
sudo ip netns exec client-ns ip addr show veth-client
You should see an inet line showing an IP address in the 10.0.100.50 to 10.0.100.200 range. The DHCP server handed the client an address from its pool.
- Now check the routing table by running:
sudo ip netns exec client-ns ip route show
You will see a route for the 10.0.100.0/24 subnet, but there is no default via line. The client has no default gateway.
This is the designed limitation. Your DHCP server only defines an address pool. It never told the client where to route traffic beyond its local subnet or which DNS server to use.
Why is there no gateway or DNS?
DHCP can deliver much more than just an IP address. Options like option 3 (default gateway) and option 6 (DNS server) tell the client how to reach the internet and resolve domain names.
Your current configuration deliberately omits these options. In the next step, you will add them and see the client's routing table change.
Your DHCP server is handing out addresses, but the client is stranded on its local subnet. Next up, you will add DHCP options and a reservation to give the client everything it needs: a gateway, a DNS server, and a guaranteed IP.
Add DHCP Options and a Reservation
Your DHCP server handed out an IP address to the client in the last step. But look at what's missing: the client has no default gateway and no DNS server. It can talk to the server across the veth link, but it has no idea how to reach anything beyond that subnet.
This happened because the basic configuration only defined an address pool. DHCP can deliver much more than just IP addresses. In this step, you'll add DHCP options that supply a gateway, DNS server, and domain name. You'll also create a reservation that guarantees a specific client always receives the same IP.
In this step, get ready to:
- Stop the current DHCP server and create a new configuration with DHCP options 3, 6, and 15.
- Add a MAC-based reservation that pins the client to a fixed IP address.
- Release and renew the client lease to verify the options and reservation are applied.
Stop dnsmasq and create the options configuration
The current dnsmasq process is running with the basic config that only hands out addresses. You need to stop it before launching a new instance with updated settings.
- Stop the running dnsmasq process by running this command:
sudo ip netns exec server-ns pkill dnsmasq
- Confirm it stopped by checking for processes listening on UDP port 67:
sudo ip netns exec server-ns ss -ulnp | grep 67
You should see no output. This means nothing is listening on the DHCP server port anymore.
Now create the new configuration file with DHCP options that the basic config was missing.
- Create dnsmasq-full.conf by opening a new file in nano:
nano ~/dhcp-lab/dnsmasq-full.conf
- Paste the following configuration:
# Full DHCP server config with options and reservation
interface=veth-server
bind-interfaces
# Disable DNS functionality (DHCP only)
port=0
# DHCP scope: pool of addresses to hand out
dhcp-range=10.0.100.50,10.0.100.200,255.255.255.0,60s
# DHCP Options
dhcp-option=3,10.0.100.1
dhcp-option=6,8.8.8.8
dhcp-option=15,lab.local
# Lease file location
dhcp-leasefile=/tmp/dnsmasq-lab.leases
# Log DHCP transactions
log-dhcp
What do these DHCP options do?
- dhcp-option=3,10.0.100.1 sets option 3 (Router). The client uses this as its default gateway for reaching other networks.
- dhcp-option=6,8.8.8.8 sets option 6 (DNS Server). The client uses this to resolve domain names into IP addresses.
- dhcp-option=15,lab.local sets option 15 (Domain Name). The client appends this when resolving short hostnames.
- The lease time changed from 2m to 60s so you can observe renewal behavior within a minute.
- Save the file with Ctrl+O, press Enter, then exit with Ctrl+X.
- Confirm the file was created by running:
ls ~/dhcp-lab/dnsmasq-full.conf
You should see the filename printed back, confirming the file exists.
File not found?
- Make sure you saved with Ctrl+O (letter O, not zero) before exiting nano.
- Check that you're in the right directory. The file should be at ~/dhcp-lab/dnsmasq-full.conf.
- help me find my dnsmasq-full.conf file
Find the client MAC and add a reservation
A DHCP reservation maps a specific MAC address to a fixed IP. This guarantees the client always gets the same address, regardless of what else is in the pool.
- Find the client interface's MAC address by running:
sudo ip netns exec client-ns ip link show veth-client
You'll see output containing a line like:
link/ether 5a:3b:7c:1d:9e:4f brd ff:ff:ff:ff:ff:ff
The MAC address is the value after link/ether. Copy this value. Yours will be different from the example above.
- Open the configuration file to add the reservation:
nano ~/dhcp-lab/dnsmasq-full.conf
- Find the line that says # Lease file location. Add these three lines ABOVE it:
# Reservation: pin client MAC to fixed IP
# Replace aa:bb:cc:dd:ee:ff with your actual client MAC address
dhcp-host=[[CLIENT_MAC="aa:bb:cc:dd:ee:ff"]],10.0.100.10,lab-client
- Replace aa:bb:cc:dd:ee:ff with the actual MAC address you copied from the previous command.
- Add a blank line after the dhcp-host line to keep the file readable.
- Save with Ctrl+O, press Enter, then exit with Ctrl+X.
What does dhcp-host do?
The dhcp-host directive maps a MAC address to a fixed IP (10.0.100.10) and an optional hostname (lab-client). Whenever a device with this MAC requests a lease, dnsmasq always offers 10.0.100.10 instead of picking a random address from the pool.
- Start dnsmasq in the server namespace with the updated config by running:
sudo ip netns exec server-ns dnsmasq -C ~/dhcp-lab/dnsmasq-full.conf
- Verify it's listening by running:
sudo ip netns exec server-ns ss -ulnp | grep 67
You should see a line showing dnsmasq listening on UDP port 67. This confirms the server is ready to hand out leases with the new options.
dnsmasq won't start?
- If you see "address already in use", a previous dnsmasq process is still running. Kill it with sudo ip netns exec server-ns pkill dnsmasq and try again.
- If you see a config file parse error, open dnsmasq-full.conf in nano and check for typos in the dhcp-option and dhcp-host lines. Each line must have no spaces around the equals sign or commas.
- help me fix my dnsmasq startup error
Release and renew the client lease
The client still holds its old lease from the basic config. You need to release it and request a fresh one to pick up the new options and reservation.
- Release the old lease by running:
sudo ip netns exec client-ns dhclient -r veth-client
Before you renew, what IP do you think the client will receive? Will it be a random address from the pool, or something specific?
- Request a new lease by running:
sudo ip netns exec client-ns dhclient veth-client
Now verify everything worked. Check three things: the assigned IP, the routing table, and the DNS setting.
- Check the assigned IP by running:
sudo ip netns exec client-ns ip addr show veth-client
You should see 10.0.100.10/24 assigned to the interface. This is the reserved IP from your dhcp-host line, not a random pool address.
- Check the routing table by running:
sudo ip netns exec client-ns ip route show
You should see a default via 10.0.100.1 line. This is DHCP option 3 in action, providing the client with a gateway.
- Confirm DNS was provided by checking the lease file:
sudo ip netns exec client-ns cat /var/lib/dhcp/dhclient.leases
Look for the option domain-name-servers 8.8.8.8 line. This proves option 6 was delivered to the client.
What changed from the basic config?
In the previous step, the client had an IP but no gateway and no DNS. Now it has all three: a reserved IP (10.0.100.10), a default gateway (10.0.100.1), and a DNS server (8.8.8.8).
DHCP options turned a bare IP assignment into a complete network configuration. This is exactly what happens when you connect your laptop to a corporate network or your home Wi-Fi.
Client still showing an old IP?
- Make sure you released the lease first with dhclient -r veth-client before requesting a new one. Without the release, the client may try to renew its old address.
- Verify dnsmasq is running with the full config. Run sudo ip netns exec server-ns ss -ulnp | grep 67 to confirm it's listening.
- Check that your MAC address in dnsmasq-full.conf matches the output of ip link show veth-client exactly. Even one wrong character will prevent the reservation from matching.
- help me troubleshoot my DHCP reservation
✔️ Awesome, I've got everything!
Great. Double check you've saved dnsmasq-full.conf and that your client is showing IP 10.0.100.10 with a default gateway.
ⓧ I'd like to double check the full code
Here is the complete dnsmasq-full.conf file. Your MAC address on the dhcp-host line will be different from aa:bb:cc:dd:ee:ff.
# Full DHCP server config with options and reservation
interface=veth-server
bind-interfaces
# Disable DNS functionality (DHCP only)
port=0
# DHCP scope: pool of addresses to hand out
dhcp-range=10.0.100.50,10.0.100.200,255.255.255.0,60s
# DHCP Options
dhcp-option=3,10.0.100.1
dhcp-option=6,8.8.8.8
dhcp-option=15,lab.local
# Reservation: pin client MAC to fixed IP
# Replace aa:bb:cc:dd:ee:ff with your actual client MAC address
dhcp-host=aa:bb:cc:dd:ee:ff,10.0.100.10,lab-client
# Lease file location
dhcp-leasefile=/tmp/dnsmasq-lab.leases
# Log DHCP transactions
log-dhcp
Your DHCP server now delivers a complete network configuration: IP address, gateway, DNS, and domain name. Next up, you'll capture the entire DORA handshake on the wire and dissect it in Wireshark.
Capture and Analyze the DORA Handshake
In the previous step, you configured DHCP options and a reservation so the client receives a complete network configuration. Now it is time to prove the protocol actually works the way the textbooks describe it.
Seeing the four DHCP messages on the wire proves you understand the protocol itself, not just the configuration files that drive it. In this step, you will capture live DHCP traffic with tcpdump and dissect each packet in Wireshark.
In this step, get ready to:
- Capture DHCP traffic into a .pcap file using tcpdump inside the server namespace.
- Trigger a fresh DORA exchange by releasing and renewing the client lease.
- Open the capture in Wireshark and identify all four DORA messages with their options.
Start the packet capture
DHCP uses UDP port 67 (server) and port 68 (client). By filtering for these ports, tcpdump records only DHCP traffic and ignores everything else on the wire.
- Start a packet capture inside the server namespace by running this command:
sudo ip netns exec server-ns tcpdump -i veth-server -n "udp port 67 or udp port 68" -w /tmp/dhcp-capture.pcap
What does this command do?
- ip netns exec server-ns runs the following command inside the server namespace so we capture from the DHCP server's perspective.
- -i veth-server listens on the server-side veth interface where all DHCP traffic passes.
- -n skips DNS lookups on captured addresses, keeping the output clean.
- "udp port 67 or udp port 68" filters for DHCP traffic only.
- -w /tmp/dhcp-capture.pcap writes raw packets to a file that Wireshark can open.
You should see a message like listening on veth-server confirming the capture is active. This terminal is now occupied by tcpdump. Leave it running.
Seeing "No such device" or permission denied?
- Make sure dnsmasq is still running in server-ns. Run sudo ip netns exec server-ns ss -ulnp and confirm port 67 is listed.
- Confirm the namespace exists with sudo ip netns list. If it is missing, re-run your setup-lab.sh script.
- help me troubleshoot tcpdump not finding the veth-server interface in my namespace
Trigger a fresh DORA exchange
The client currently holds a lease from the previous step. To see all four DORA messages, you need to release the existing lease and request a brand new one.
- Open a second terminal window in your Ubuntu VM.
- Release the client's current lease by running:
sudo ip netns exec client-ns dhclient -r veth-client
- Request a new lease to trigger the full DORA handshake by running:
sudo ip netns exec client-ns dhclient veth-client
What is happening on the wire?
The dhclient -r command sends a DHCP Release message, telling the server to reclaim the IP. The second dhclient command starts the four-message DORA sequence from scratch: Discover, Offer, Request, Acknowledge.
Because your reservation maps the client MAC to 10.0.100.10, the server will offer that same IP again.
- Switch back to your first terminal where tcpdump is running.
- Press Ctrl+C to stop the capture.
You should see a summary line reporting the number of packets captured (at least 4 or more).
Seeing 0 packets captured?
- Make sure you ran the dhclient commands in the second terminal AFTER starting tcpdump in the first. The capture must be active before the DORA exchange happens.
- Confirm you used veth-client (not veth-server) in the dhclient commands.
- help me figure out why tcpdump captured zero packets during my DHCP exchange
Analyze the capture in Wireshark
The .pcap file contains raw packets exactly as they traveled on the virtual wire. Wireshark decodes them into a human-readable format and lets you inspect every field inside each DHCP message.
Before you open the file, think about this: what four packet types do you expect to see, and what source/destination IP addresses should each one use?
- Open the capture file in Wireshark by running:
wireshark /tmp/dhcp-capture.pcap
- In the Wireshark display filter bar at the top, type dhcp and press Enter.
Older Wireshark versions
If the dhcp filter shows no results, try bootp instead. Older versions of Wireshark used the legacy protocol name.
You should now see four DHCP packets listed in the packet list pane. Here is what each one means:
- DHCP Discover - Source 0.0.0.0, destination 255.255.255.255. The client has no IP yet, so it broadcasts to find any DHCP server on the network.
- DHCP Offer - The server responds with an offered IP (10.0.100.10 from your reservation). Expand the DHCP options section to see the gateway, DNS, and lease time.
- DHCP Request - The client formally requests the offered IP. This is still broadcast so other DHCP servers (if any) know this IP was claimed.
- DHCP ACK - The server confirms the lease. The client can now use the IP address with all configured options.
- Click on the DHCP Offer packet in the list.
- In the packet details pane below, expand Dynamic Host Configuration Protocol and then expand Option: (3) Router.
- Confirm the value is 10.0.100.1 (the gateway you configured in dnsmasq-full.conf).
- Expand Option: (6) Domain Name Server and confirm it shows 8.8.8.8.
- Expand Option: (15) Domain Name and confirm it shows lab.local.
Why does this matter?
Every option you configured in dnsmasq-full.conf is now visible inside the actual network packets. This is exactly what a network engineer inspects when troubleshooting why a client got the wrong gateway or DNS server.
The DORA sequence is the foundation of every DHCP transaction on every network. You have now seen it happen in real time and can point to each message in a capture file.
Not seeing all four packets?
- If you see fewer than four DHCP packets, your capture may have started too late or stopped too early. Close Wireshark, restart the tcpdump command, release and renew again, and recapture.
- If you see extra packets (Release, or additional Discovers), that is normal. Focus on the four DORA messages in sequence.
- help me figure out why my Wireshark capture doesn't show the full DORA sequence
Secret mission
Simulate a Rogue DHCP Server
Your legitimate DHCP server works perfectly. But what happens when an unauthorized server appears on the same network? In this challenge, you will spin up a rogue DHCP server that races against your legitimate one, observe which server wins, and document why this is a serious security threat.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally inside your Ubuntu VM, so there are no ongoing costs.
Resources you used:
- Network namespaces (server-ns, client-ns, rogue-ns).
- Linux bridge (br-dhcp) and veth pairs.
- Running dnsmasq processes inside namespaces.
- Project files in ~/dhcp-lab/ (scripts and config files).
- Temporary lease and capture files in /tmp/.
- Installed packages (dnsmasq-base, isc-dhcp-client, tcpdump, wireshark).
Keep everything running
No action needed. Choose this if you want to continue experimenting with DHCP options, lease behaviour, or the rogue server topology.
- Your namespaces, bridge, and dnsmasq processes remain active until you reboot or manually delete them.
- You can re-run dhclient in the client namespace at any time to request a new lease.
- The installed packages (dnsmasq-base, tcpdump, wireshark) stay on your system and are useful for future networking labs.
Pause - I'll come back to this later
Stop the running dnsmasq processes to free up memory, but keep the namespace topology and project files for later.
- Stop the dnsmasq processes by running:
sudo ip netns exec server-ns pkill dnsmasq 2>/dev/null || true
sudo ip netns exec rogue-ns pkill dnsmasq 2>/dev/null || true
- Your namespaces and bridge remain intact. When you come back, restart dnsmasq inside the server namespace using your config files in ~/dhcp-lab/.
- Note that namespaces do not survive a reboot. If you restart your VM, re-run the setup script (setup-rogue-lab.sh or setup-lab.sh) to rebuild the topology.
Delete - I don't want to use this again
Remove all project resources and restore your system to its original state.
- Run the rogue lab cleanup script to remove all namespaces, the bridge, dnsmasq processes, and temporary files:
cd ~/dhcp-lab
bash cleanup-rogue-lab.sh
- Remove the project directory containing all scripts and config files:
rm -rf ~/dhcp-lab
- If you want to remove the installed packages as well, run:
sudo apt remove --purge dnsmasq-base isc-dhcp-client tcpdump wireshark -y
sudo apt autoremove -y
- Confirm the namespaces are gone by running:
sudo ip netns list
- You should see no output, confirming all namespaces have been deleted.
Nice Work!
Nice Work!
You just built a complete DHCP lab from scratch using nothing but your Ubuntu VM. You deployed a server, configured scopes and reservations, and watched the entire protocol handshake happen on the wire.
You've learned how to:
- Build an isolated virtual network using Linux network namespaces and veth pairs, the same primitives Docker uses for container networking.
- Configure a dnsmasq DHCP server with a scope, options (gateway, DNS, domain), and a MAC-based reservation that guarantees a specific IP for a known device.
- Capture and dissect the full DORA handshake in Wireshark, identifying each message (Discover, Offer, Request, ACK) and its role in assigning an address.
- Secret Mission: Simulated a rogue DHCP server attack and documented organizational defenses including DHCP snooping, port security, and 802.1X.
Ready to quiz yourself?