# Module 04: DHCP Relay — Crossing Broadcast Boundaries **Nav:** [← Module 3](03-dhcp-options.md) | [Course Home](../README.md) | [Module 5 →](05-advanced-wireshark.md) | | | |---|---| | **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 01–03: 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: ```cisco 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): ```cisco ! 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: ```cisco 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: (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 ```wireshark # 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 ```cisco interface GigabitEthernet0/1 ip dhcp relay information option ``` Or globally (applies to all relay interfaces): ```cisco 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): ```cisco 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`: ```wireshark # 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 ```cisco 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) ```conf # /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 ```bash # 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 ```cisco ! 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 - [x] Explain why broadcasts don't cross routers and why DHCP relay exists - [x] Configure `ip helper-address` on Cisco IOS for single and multi-VLAN environments - [x] Identify all five packet modifications a relay agent performs - [x] Use Wireshark to compare client-side and server-side captures of the same DORA transaction - [x] Read and interpret Option 82 (Relay Agent Information) in Wireshark - [x] 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](03-dhcp-options.md) | [Course Home](../README.md) | [Module 5 →](05-advanced-wireshark.md) --- *Module 04 — DHCP Relay · Packet Inspector: DHCP Deep Dive with Wireshark · v1.0*