Files
dhcp-wireshark-course/modules/04-dhcp-relay.md
2026-03-01 18:35:41 -07:00

20 KiB
Raw Permalink Blame History

Module 04: DHCP Relay — Crossing Broadcast Boundaries

Nav: ← Module 3 | Course Home | Module 5 →

Duration ~2 hours
Lab Required CML (or EVE-NG/GNS3) with 2+ VLANs, 1 router, 1 DHCP server
Wireshark Profile dhcp-relay (build during lab)
Prerequisites Modules 0103: DORA flow, options fundamentals

4.1 — Why DHCP Relay Exists

The Broadcast Domain Problem

DHCP's initial discovery relies on a Layer 2 broadcast — the client sends a DHCPDISCOVER to 255.255.255.255 because it has no IP address and no idea where a server lives. Broadcasts, by definition, do not cross router boundaries. Every router interface is a broadcast domain boundary.

In a flat, single-subnet lab this is invisible. In production, it's the central problem:

VLAN 10 (Users)          Router (L3)            VLAN 99 (Servers)
┌─────────────┐         ┌────────┐             ┌──────────────┐
│ PC (no IP)  │───Gi0/1─┤        ├─Gi0/2──────│ DHCP Server  │
│ DISCOVER →  │ ══════╳ │ drops  │             │ 10.99.0.10   │
│ broadcast   │  bcast  │ bcast  │             │              │
└─────────────┘         └────────┘             └──────────────┘

Without intervention, the Discover never reaches the server. You have three choices:

Approach Scalability Security Operational Cost
Place a DHCP server on every subnet Terrible Poor (attack surface) Very high
Use DHCP relay on the gateway Excellent Good Low
Extend VLANs everywhere (flat L2) Dangerous Terrible Medium

DHCP relay is the industry-standard answer. The router intercepts the broadcast Discover, rewrites critical header fields, and unicasts it to a centralized DHCP server on a different subnet. The server replies, the router relays the response back, and the client is none the wiser.

Key insight for packet analysts: The DHCP frames you capture on the client-facing segment look different from the frames on the server-facing segment. Understanding exactly what changes — and why — is the core skill this module teaches.


4.2 — How ip helper-address Works on Cisco IOS

Basic Configuration

On a Cisco IOS or IOS-XE router, relay is enabled per-interface with a single command:

interface GigabitEthernet0/1
 description ** User VLAN 10 Gateway **
 ip address 10.10.0.1 255.255.255.0
 ip helper-address 10.99.0.10

When the router receives a UDP broadcast on Gi0/1 destined for certain well-known ports, it converts that broadcast into a unicast aimed at 10.99.0.10. DHCP (UDP 67/68) is one of those ports, but ip helper-address forwards eight UDP services by default:

Port Service
37 Time
49 TACACS
53 DNS
67 BOOTP/DHCP Server
68 BOOTP/DHCP Client
69 TFTP
137 NetBIOS Name Service
138 NetBIOS Datagram Service

To restrict forwarding to DHCP only (recommended in production):

! Stop forwarding all eight services
no ip forward-protocol udp 37
no ip forward-protocol udp 49
no ip forward-protocol udp 53
no ip forward-protocol udp 69
no ip forward-protocol udp 137
no ip forward-protocol udp 138

! Only UDP 67 and 68 remain active

Multiple Helper Addresses (Redundancy)

You can configure multiple helper addresses on a single interface. The router sends a copy of every relayed Discover to each address:

interface GigabitEthernet0/1
 ip helper-address 10.99.0.10
 ip helper-address 10.99.0.11

This is used for DHCP server redundancy. Both servers receive the Discover; whichever Offers first typically wins. Be sure the servers' scopes don't overlap, or use DHCP failover/split-scope to coordinate.


4.3 — What the Relay Agent Actually Does to the Packet

When the router's relay function processes an inbound DHCP broadcast, it modifies the packet in five specific, observable ways before forwarding. Every one of these changes is visible in Wireshark.

4.3.1 — Sets the giaddr (Gateway IP Address) Field

This is the most critical modification. The relay agent writes its own IP address — the address of the interface where it received the broadcast — into the giaddr field (offset 24 in the BOOTP header).

Before relay:   giaddr = 0.0.0.0
After relay:    giaddr = 10.10.0.1    ← router's Gi0/1 address

Why this matters: The DHCP server uses giaddr to determine which scope (IP pool) to allocate from. If giaddr is 10.10.0.1, the server looks for a scope that includes 10.10.0.0/24. This is how a single centralized server hands out addresses for dozens of different subnets.

4.3.2 — Increments the hops Field

The relay agent increments the hops counter (offset 3 in the BOOTP header) by 1. This prevents infinite relay loops if multiple relays are chained.

Before relay:   hops = 0
After relay:    hops = 1

Most DHCP servers reject packets with hops > 16 (configurable). In a correctly designed network, you should never see hops greater than 2.

4.3.3 — Converts Broadcast → Unicast

The original Discover arrives as:

Ethernet dst:  ff:ff:ff:ff:ff:ff    (broadcast)
IP dst:        255.255.255.255      (broadcast)

The relay agent transmits it as:

Ethernet dst:  <MAC of next-hop to server>   (unicast)
IP dst:        10.99.0.10                     (unicast to server)
IP src:        10.99.0.1                      (router's server-facing interface)

4.3.4 — Rewrites IP Source Address

The source IP changes from 0.0.0.0 (the client has no address) to the router's outbound interface IP — the interface facing the server, not necessarily the interface that received the client broadcast. This is a normal IP routing behavior; the relay agent creates a new IP datagram.

4.3.5 — (Optional) Inserts Option 82

If configured, the relay agent appends Option 82 (Relay Agent Information) to the DHCP options payload. Covered in detail in Section 4.5.

Summary of All Modifications

Field Client Original After Relay
Ethernet Destination ff:ff:ff:ff:ff:ff Server's resolved MAC
IP Source 0.0.0.0 Router's outbound interface IP
IP Destination 255.255.255.255 Helper address (e.g., 10.99.0.10)
UDP Ports 68 → 67 67 → 67
giaddr 0.0.0.0 Router's client-facing interface IP
hops 0 1
Options payload Unchanged Option 82 appended (if configured)

Wireshark checkpoint: The easiest way to confirm relay is active is to look for a non-zero giaddr in any DHCP packet. If giaddr is populated, a relay agent touched that packet.


4.4 — Relay Behavior in Wireshark: Client Side vs. Server Side

When you capture traffic on both sides of the relay agent simultaneously, you see two distinctly different conversations:

Client-Side Capture (VLAN 10, Gi0/1)

No.  Time     Source          Destination       Info
1    0.000    0.0.0.0         255.255.255.255   DHCP Discover
2    0.045    10.10.0.1       255.255.255.255   DHCP Offer
3    0.048    0.0.0.0         255.255.255.255   DHCP Request
4    0.091    10.10.0.1       255.255.255.255   DHCP ACK

The client sees broadcasts from the gateway IP — the router converts the server's unicast replies back into broadcasts (or unicasts to the client's assigned IP, depending on the broadcast flag).

Server-Side Capture (VLAN 99, Gi0/2)

No.  Time     Source          Destination       Info
1    0.003    10.99.0.1       10.99.0.10        DHCP Discover  [giaddr=10.10.0.1]
2    0.042    10.99.0.10      10.99.0.1         DHCP Offer     [giaddr=10.10.0.1]
3    0.051    10.99.0.1       10.99.0.10        DHCP Request   [giaddr=10.10.0.1]
4    0.088    10.99.0.10      10.99.0.1         DHCP ACK       [giaddr=10.10.0.1]

All four messages are unicast and giaddr is populated in every packet. The server sends its replies back to the relay agent (10.99.0.1), not to the client — because the server knows it can't route directly to a client that has no IP address on a remote subnet.

Key Wireshark Filters for Relay Traffic

# Show only packets with a non-zero giaddr (relay was involved)
bootp.ip.relay != 0.0.0.0

# Show only packets relayed through a SPECIFIC gateway
bootp.ip.relay == 10.10.0.1

# Show Option 82 (Relay Agent Information)
dhcp.option.agent_information_option

# Show the Circuit ID sub-option of Option 82
dhcp.option.agent_information_option.agent_circuit_id

# Show the Remote ID sub-option of Option 82
dhcp.option.agent_information_option.agent_remote_id

# Combine: relay traffic for VLAN 10 with Option 82 present
bootp.ip.relay == 10.10.0.1 && dhcp.option.agent_information_option

Pro tip: Create a custom Wireshark column for giaddr — right-click a giaddr field → Apply as Column. This makes relay identification instant when scrolling through large captures.


4.5 — Option 82: Relay Agent Information

What It Is

Option 82 (RFC 3046) is a DHCP option inserted by the relay agent — not by the client. It tells the DHCP server the physical location of the client: which switch, which port, which VLAN. The server can use this to make policy decisions: assign IPs from a specific range, deny service, or log the location for audit.

Sub-Options

Option 82 contains two standard sub-options:

Sub-option ID Contains Example Value
Circuit ID 1 Switch port / VLAN the client is connected to Gi1/0/5, VLAN 10
Remote ID 2 Switch MAC address or hostname aabb.cc00.0100

Cisco IOS Configuration

interface GigabitEthernet0/1
 ip dhcp relay information option

Or globally (applies to all relay interfaces):

ip dhcp relay information option

What Option 82 Looks Like in Wireshark

Expand the DHCP options tree in Wireshark, and you'll see:

Option: (82) Relay Agent Information
  Length: 22
  Option 82 Suboption: (1) Agent Circuit ID
    Length: 6
    Agent Circuit ID: 00:04:00:0a:00:01     ← VLAN 10, port Gi0/1
  Option 82 Suboption: (2) Agent Remote ID
    Length: 8
    Agent Remote ID: aa:bb:cc:00:01:00:00:00  ← relay switch MAC

Server-Side Behavior

When the DHCP server sees Option 82, it can:

  1. Log it — record which switch port issued the request (invaluable for security teams tracking rogue devices)
  2. Use it for scope selection — assign a specific IP range based on the circuit (e.g., all ports on floor 3 get 10.10.3.x)
  3. Enforce policy — reject requests from unauthorized ports

Warning: Some older DHCP servers drop packets containing Option 82 if they're not configured to expect it. If clients suddenly stop getting addresses after you enable Option 82, check the server's relay information handling policy. On Windows DHCP Server, this works out of the box. On ISC DHCP, you may need stash-agent-options and agent-store directives.


4.6 — Multi-VLAN Relay Scenarios

Architecture: One Router, Many VLANs, One Server

In a real campus network, you'll configure relay on every SVI (VLAN interface):

ip dhcp relay information option

interface Vlan10
 description ** Users - Floor 1 **
 ip address 10.10.0.1 255.255.255.0
 ip helper-address 10.99.0.10

interface Vlan20
 description ** Users - Floor 2 **
 ip address 10.20.0.1 255.255.255.0
 ip helper-address 10.99.0.10

interface Vlan30
 description ** IoT Devices **
 ip address 10.30.0.1 255.255.255.0
 ip helper-address 10.99.0.10

interface Vlan99
 description ** Server VLAN **
 ip address 10.99.0.1 255.255.255.0

Server Scope Mapping

The DHCP server needs a scope for every subnet that sends relayed traffic. It matches on giaddr:

giaddr Received Server Scope Range
10.10.0.1 VLAN10-Users-F1 10.10.0.100 10.10.0.254
10.20.0.1 VLAN20-Users-F2 10.20.0.100 10.20.0.254
10.30.0.1 VLAN30-IoT 10.30.0.100 10.30.0.254

If the server has no scope matching the giaddr, the Discover is silently dropped. This is the #1 cause of relay failures and produces zero error messages on the server in many implementations — only the absence of an Offer in your Wireshark capture reveals the problem.

Wireshark Analysis: Identifying Which VLAN a Packet Belongs To

In a multi-VLAN environment captured at the server, every Discover looks similar. The differentiator is giaddr:

# See only VLAN 10 relay traffic at the server
bootp.ip.relay == 10.10.0.1

# See only VLAN 30 (IoT) traffic
bootp.ip.relay == 10.30.0.1

4.7 — CML Lab: DHCP Relay Across VLANs

Topology

┌───────────┐      Gi0/1 (.1)  ┌──────────┐  Gi0/2 (.1)     ┌─────────────┐
│  Client   ├──── VLAN 10 ─────┤  Router  ├──── VLAN 99 ────┤ DHCP Server │
│ (no IP)   │   10.10.0.0/24   │  (relay) │   10.99.0.0/24  │  10.99.0.10 │
└───────────┘                  └──────────┘                  └─────────────┘

Step 1 — Router Configuration

hostname RELAY-RTR
!
interface GigabitEthernet0/1
 ip address 10.10.0.1 255.255.255.0
 ip helper-address 10.99.0.10
 no shutdown
!
interface GigabitEthernet0/2
 ip address 10.99.0.1 255.255.255.0
 no shutdown
!
ip dhcp relay information option

Step 2 — DHCP Server Configuration (ISC dhcpd Example)

# /etc/dhcp/dhcpd.conf

# Authoritative for our network
authoritative;

# Shared network — both subnets the server needs to know about
shared-network CAMPUS {

  # Server's own subnet (required even if no allocations here)
  subnet 10.99.0.0 netmask 255.255.255.0 {
    option routers 10.99.0.1;
  }

  # Remote subnet served via relay
  subnet 10.10.0.0 netmask 255.255.255.0 {
    range 10.10.0.100 10.10.0.200;
    option routers 10.10.0.1;
    option domain-name-servers 10.99.0.5;
    option domain-name "lab.local";
    default-lease-time 3600;
    max-lease-time 7200;
  }
}

Restart the service: sudo systemctl restart isc-dhcp-server

Step 3 — Start Wireshark Captures

You need two simultaneous captures to see the full relay transformation:

Capture Point Interface Filter
Client-side Router Gi0/1 (or client's NIC) udp port 67 or udp port 68
Server-side Router Gi0/2 (or server's NIC) udp port 67 or udp port 68

In CML, right-click each link → Capture → open in Wireshark.

Step 4 — Trigger DHCP from the Client

# Linux client
sudo dhclient -r eth0 && sudo dhclient -v eth0

# Windows client
ipconfig /release && ipconfig /renew

Step 5 — Analyze the Captures

Client-side Discover (packet #1):

  • Ethernet dst: ff:ff:ff:ff:ff:ff
  • IP src: 0.0.0.0, IP dst: 255.255.255.255
  • giaddr: 0.0.0.0
  • hops: 0
  • Option 82: absent

Server-side Discover (packet #1):

  • Ethernet dst: server's MAC (unicast)
  • IP src: 10.99.0.1, IP dst: 10.99.0.10
  • giaddr: 10.10.0.1
  • hops: 1
  • Option 82: present (Circuit ID + Remote ID)

Lab deliverable: Screenshot both Discover packets side-by-side and annotate every field that changed. This is the single most important exercise in this module.


4.8 — Troubleshooting DHCP Relay Failures

Systematic Debugging Workflow

Client gets no IP
        │
        ▼
  ┌─ Capture on client side ──── Do you see a Discover? ── No → Client issue
  │                                        │ Yes
  │                                        ▼
  ├─ Capture on server side ──── Does Discover arrive? ──── No → Relay issue
  │                                        │ Yes
  │                                        ▼
  │                              Does server send Offer? ── No → Server/scope issue
  │                                        │ Yes
  │                                        ▼
  └─ Capture on client side ──── Does Offer reach client? ─ No → Return path issue

Common Misconfigurations and Fixes

# Symptom Root Cause Fix
1 Server receives Discover but sends no Offer No scope matching giaddr Create a scope/subnet declaration for the relayed subnet
2 Discover never reaches server ip helper-address on wrong interface Configure helper on the client-facing interface (SVI or physical)
3 Discover never reaches server ACL blocking UDP 67/68 Add permit udp any any eq 67 and eq 68 to the interface ACL
4 Discover never reaches server No route from router to server Verify show ip route 10.99.0.10 shows a valid path
5 Server receives Discover, sends Offer, but client never gets it Firewall on server blocking return traffic Ensure server allows UDP 67 outbound to the relay agent
6 Intermittent failures Duplicate IP / scope exhaustion Check show ip dhcp binding on server; expand the pool
7 Option 82 rejection Server drops packets with Option 82 Configure server to accept relay agent info, or disable Option 82 on the relay

Essential IOS Debug and Show Commands

! Verify helper is configured correctly
show ip interface GigabitEthernet0/1 | include helper

! Watch relay operations in real time (use sparingly in production)
debug ip dhcp server packet
debug ip dhcp relay

! Check if the router is forwarding UDP broadcasts
show ip forward-protocol

! Verify routing to the DHCP server
show ip route 10.99.0.10

The debug ip dhcp relay Output Decoded

DHCPD-RELAY: forwarding BOOTREQUEST for 0050.7966.6800 to 10.99.0.10
DHCPD-RELAY: setting giaddr to 10.10.0.1
DHCPD-RELAY: forwarding BOOTREPLY to client 0050.7966.6800 via 10.10.0.1

If you see the first line but not the second, the relay agent is failing to set giaddr — which typically indicates the interface has no IP address assigned. If you see both forward lines but the client still fails, the return path (server → relay → client) is the problem — capture at the server to confirm.


4.9 — Module Summary

What You Should Now Be Able To Do

  • Explain why broadcasts don't cross routers and why DHCP relay exists
  • Configure ip helper-address on Cisco IOS for single and multi-VLAN environments
  • Identify all five packet modifications a relay agent performs
  • Use Wireshark to compare client-side and server-side captures of the same DORA transaction
  • Read and interpret Option 82 (Relay Agent Information) in Wireshark
  • Systematically troubleshoot relay failures using captures and IOS debug output

Key Wireshark Filters — Quick Reference

Filter Purpose
bootp.ip.relay != 0.0.0.0 All relayed DHCP traffic
bootp.ip.relay == 10.10.0.1 Traffic relayed via a specific gateway
dhcp.option.agent_information_option Packets containing Option 82
bootp.hops > 0 Packets touched by at least one relay
udp.port == 67 && ip.dst == 10.99.0.10 Relay-to-server unicasts

What's Next

In Module 5, we take your Wireshark skills to the next level: custom dissectors, advanced coloring rules for DHCP anomalies, Tshark automation for bulk PCAP analysis, and building a DHCP monitoring dashboard from capture data.


Nav: ← Module 3 | Course Home | Module 5 →


Module 04 — DHCP Relay · Packet Inspector: DHCP Deep Dive with Wireshark · v1.0