# Module 2: DHCP Message Flow (DORA)

**Nav:** [← Module 1](01-wireshark-fundamentals.md) | [Course Home](../README.md) | [Module 3 →](03-dhcp-options.md)

---

## Overview

Every time a device connects to a network and receives an IP address "automatically," a precisely choreographed four-message exchange takes place behind the scenes. This exchange — **Discover, Offer, Request, Acknowledge** — is the DORA process, and it is the beating heart of DHCP (Dynamic Host Configuration Protocol, RFC 2131).

Most engineers know that DHCP "gives out IP addresses." Far fewer can open a packet capture, identify each DORA message, trace the transaction ID across all four packets, and explain exactly which fields changed between the Request and the Acknowledge. That gap is what separates a technician who reboots things from an engineer who fixes them.

In this module, you will build a dedicated CML lab topology with a Cisco IOS DHCP server and a Linux client, capture a complete DORA exchange in Wireshark, and dissect every field in every packet. You will also examine what happens *after* the initial lease — the T1 and T2 renewal timers, the rebinding process, and the DHCP Release message. By the end, you will be able to read a DHCP capture the way a cardiologist reads an EKG: with precision and confidence.

**Estimated time:** ~2 hours (reading + labs)

**Prerequisites:** Module 1 completed. Wireshark installed. CML lab environment accessible.

---

## 2.1 — The DORA Process: Conceptual Overview

DHCP uses a client-server model carried over UDP. The client transmits on **port 68**, and the server listens on **port 67**. Because the client starts with no IP address, the first two messages are broadcast at Layer 3 (`255.255.255.255`) and Layer 2 (`ff:ff:ff:ff:ff:ff`).

The four messages form a predictable sequence:

| Step | Message | Direction | Source IP | Destination IP | UDP Ports |
|------|---------|-----------|-----------|----------------|-----------|
| 1 | **DHCP Discover** | Client → Broadcast | `0.0.0.0` | `255.255.255.255` | 68 → 67 |
| 2 | **DHCP Offer** | Server → Broadcast/Unicast | Server IP | `255.255.255.255` | 67 → 68 |
| 3 | **DHCP Request** | Client → Broadcast | `0.0.0.0` | `255.255.255.255` | 68 → 67 |
| 4 | **DHCP Acknowledge** | Server → Broadcast/Unicast | Server IP | `255.255.255.255` | 67 → 68 |

> **Why is the Request broadcast?** The client may have received Offers from multiple DHCP servers. By broadcasting its Request (which includes the chosen server's identifier), it simultaneously accepts one Offer and implicitly declines all others. Every server on the segment sees the Request and knows whether it was selected.

### Transaction ID (xid) — The Thread That Ties DORA Together

Every DORA exchange is correlated by a 32-bit **Transaction ID** (`xid`). The client generates a random `xid` in the Discover message, and all four packets in that exchange carry the same value. When you are troubleshooting in a busy capture with dozens of clients, filtering on `xid` is how you isolate a single client's conversation. We will do exactly this in Lab 2.2.

---

## 2.2 — CML Lab Setup

### Topology

Build the following topology in Cisco Modeling Labs. This is the same base topology from Module 1, with DHCP-specific configuration added.

```
┌─────────────────┐          ┌──────────────┐          ┌─────────────────┐
│   DHCP-Server   │──────────│   Switch-1   │──────────│   Linux-Client  │
│  (IOSv / CSR)   │  Gi0/1   │  (IOSvL2)    │  Gi0/2   │  (Ubuntu/Alpine)│
│  10.1.1.1/24    │          │              │          │  DHCP Client    │
└─────────────────┘          └──────────────┘          └─────────────────┘
                                    │
                              (SPAN / Mirror
                               to Wireshark)
```

You need three nodes:
1. **DHCP-Server** — An IOSv or CSR1000v router acting as the DHCP server.
2. **Switch-1** — An IOSvL2 switch connecting server and client.
3. **Linux-Client** — An Ubuntu or Alpine Cloud node configured to use DHCP.

> **Wireshark capture point:** Configure a SPAN session on Switch-1, or run Wireshark directly on the Linux client's interface. In CML, the simplest method is capturing on the Linux client's `ens2` (or `eth0`) interface using `tcpdump`, then opening the `.pcap` in Wireshark.

### DHCP Server Configuration (IOSv)

```
! Enter global config
configure terminal

! Define the DHCP pool
ip dhcp pool LAB-POOL
 network 10.1.1.0 255.255.255.0
 default-router 10.1.1.1
 dns-server 8.8.8.8 8.8.4.4
 lease 0 2 0
 ! Lease: 0 days, 2 hours, 0 minutes (short for lab observation)

! Exclude the server's own address from the pool
ip dhcp excluded-address 10.1.1.1 10.1.1.10

! Assign the server interface
interface GigabitEthernet0/1
 ip address 10.1.1.1 255.255.255.0
 no shutdown

end
write memory
```

> **Lab tip:** We set a 2-hour lease intentionally. Short leases let you observe renewal behavior (T1 at 50% = 1 hour, T2 at 87.5% = 1 hour 45 minutes) within a single lab session.

### Linux Client Configuration

On the Ubuntu/Alpine client, release any existing lease and force a new DORA exchange:

```bash
# Release current lease (if any)
sudo dhclient -r ens2

# Request a new lease (triggers full DORA)
sudo dhclient -v ens2
```

The `-v` (verbose) flag prints each DHCP message as it is sent/received — useful for correlating CLI output with your Wireshark capture.

### Starting the Capture

On the Linux client (or via CML's packet capture feature):

```bash
# Start capture on the client interface, write to file
sudo tcpdump -i ens2 -w /tmp/dhcp-dora.pcap port 67 or port 68
```

Then trigger the DORA by running `dhclient`. Stop the capture with `Ctrl+C` and transfer the `.pcap` to your workstation for analysis in Wireshark.

---

## 2.3 — DHCP Packet Structure: Field-by-Field Breakdown

Every DHCP message rides inside a UDP datagram, which rides inside an IP packet, which rides inside an Ethernet frame. The DHCP payload itself follows the BOOTP message format (DHCP is built on top of BOOTP, RFC 951). Here is the structure:

### BOOTP/DHCP Header Fields

| Field | Size | Description | Example Value (Discover) |
|-------|------|-------------|--------------------------|
| **op** | 1 byte | Operation code. `1` = BOOTREQUEST (client → server), `2` = BOOTREPLY (server → client) | `1` |
| **htype** | 1 byte | Hardware address type. `1` = Ethernet (10 Mb) | `1` |
| **hlen** | 1 byte | Hardware address length. `6` for MAC addresses | `6` |
| **hops** | 1 byte | Relay agent hop count. Set to `0` by client, incremented by each relay agent | `0` |
| **xid** | 4 bytes | Transaction ID. Random value chosen by client, used to match requests and replies | `0x3903F326` |
| **secs** | 2 bytes | Seconds elapsed since client began the address acquisition process | `0` |
| **flags** | 2 bytes | Bit 0 = Broadcast flag. If set, server must broadcast its reply | `0x0000` |
| **ciaddr** | 4 bytes | Client IP address. Only filled if client is in BOUND, RENEW, or REBINDING state | `0.0.0.0` |
| **yiaddr** | 4 bytes | "Your" IP address. The address the server is assigning to the client | `0.0.0.0` |
| **siaddr** | 4 bytes | Next server IP address (used in BOOTP/PXE boot scenarios) | `0.0.0.0` |
| **giaddr** | 4 bytes | Relay agent IP address. Set by relay agents, not by clients or servers directly | `0.0.0.0` |
| **chaddr** | 16 bytes | Client hardware (MAC) address. Padded with zeros to 16 bytes | `00:50:79:66:68:01` |
| **sname** | 64 bytes | Optional server hostname | (empty) |
| **file** | 128 bytes | Boot filename (PXE). Often empty in standard DHCP | (empty) |
| **options** | Variable | DHCP options (Option 53 = message type, Option 55 = parameter request list, etc.) | See Section 2.4 |

> **Key insight for captures:** In a Discover, `ciaddr`, `yiaddr`, `siaddr`, and `giaddr` are all `0.0.0.0`. Watch these fields change as you move through Offer → Request → Acknowledge. The `yiaddr` in the Offer is the server's proposed address. The `yiaddr` in the ACK is the confirmed assignment.

---

## 2.4 — Dissecting Each DORA Message in Wireshark

Open your `dhcp-dora.pcap` in Wireshark. Apply this display filter to isolate DHCP traffic:

```
dhcp || bootp
```

> **Filter note:** In older Wireshark versions (< 3.0), DHCP traffic is decoded under the `bootp` protocol name. In modern Wireshark (3.x+), the dissector is labeled `dhcp`, but `bootp` still works as an alias. Throughout this course, we use both interchangeably, and we will call out version-specific filters where they differ.

You should see exactly four packets. Let's walk through each one.

---

### Message 1: DHCP Discover

**What the client is saying:** *"I need an IP address. Is anyone out there?"*

**Wireshark display filter:**
```
dhcp.option.dhcp == 1
```

**Key fields to examine:**

| Field | Value | Why It Matters |
|-------|-------|---------------|
| Ethernet Dst | `ff:ff:ff:ff:ff:ff` | Layer 2 broadcast — client has no idea where the server is |
| IP Src | `0.0.0.0` | Client has no IP address yet |
| IP Dst | `255.255.255.255` | Layer 3 limited broadcast |
| UDP Src Port | `68` | DHCP client port |
| UDP Dst Port | `67` | DHCP server port |
| op | `1` (BOOTREQUEST) | Client-to-server direction |
| xid | `0x3903F326` (example) | Note this value — it ties all four packets together |
| ciaddr | `0.0.0.0` | Client has no current address |
| yiaddr | `0.0.0.0` | Server hasn't proposed one yet |
| chaddr | `00:50:79:66:68:01` | Client's MAC address |
| Option 53 | `1` (DHCP Discover) | Message type identifier |
| Option 55 | `1, 3, 6, 15, 28, 51` | Parameter Request List — what the client wants |
| Option 61 | Client MAC | Client identifier (used by server to track leases) |

**In the Wireshark packet detail pane**, expand `Dynamic Host Configuration Protocol (Discover)` and inspect each option. Option 55 tells the server which parameters the client is requesting (subnet mask, default gateway, DNS servers, domain name, broadcast address, lease time).

---

### Message 2: DHCP Offer

**What the server is saying:** *"I have 10.1.1.11 available for you. Here are the details."*

**Wireshark display filter:**
```
dhcp.option.dhcp == 2
```

**Key fields to examine:**

| Field | Value | Why It Matters |
|-------|-------|---------------|
| Ethernet Dst | `ff:ff:ff:ff:ff:ff` | Broadcast (unless Broadcast flag = 0 and server supports unicast to MAC) |
| IP Src | `10.1.1.1` | The DHCP server's address |
| IP Dst | `255.255.255.255` | Broadcast reply |
| op | `2` (BOOTREPLY) | Server-to-client direction |
| xid | `0x3903F326` | **Same xid** as the Discover — confirms this is a response to our request |
| yiaddr | `10.1.1.11` | The IP address being **offered** to the client |
| siaddr | `10.1.1.1` | Next server (DHCP server address in this case) |
| Option 53 | `2` (DHCP Offer) | Message type |
| Option 1 | `255.255.255.0` | Subnet Mask |
| Option 3 | `10.1.1.1` | Default Gateway (Router) |
| Option 6 | `8.8.8.8, 8.8.4.4` | DNS Servers |
| Option 51 | `7200` (seconds) | Lease Time (2 hours = 7200 seconds) |
| Option 54 | `10.1.1.1` | Server Identifier — critical for the Request message |

> **Important:** The client has NOT accepted this address yet. The Offer is a *proposal*. The client must explicitly Request it in the next step.

---

### Message 3: DHCP Request

**What the client is saying:** *"I accept the offer from 10.1.1.1. Please confirm 10.1.1.11 for me."*

**Wireshark display filter:**
```
dhcp.option.dhcp == 3
```

**Key fields to examine:**

| Field | Value | Why It Matters |
|-------|-------|---------------|
| IP Src | `0.0.0.0` | Client still does not own the address — not until the ACK |
| IP Dst | `255.255.255.255` | **Broadcast** — so all DHCP servers see this |
| op | `1` (BOOTREQUEST) | Client-to-server direction |
| xid | `0x3903F326` | Same transaction ID |
| ciaddr | `0.0.0.0` | Client still has no confirmed address |
| Option 53 | `3` (DHCP Request) | Message type |
| Option 50 | `10.1.1.11` | Requested IP Address — the address from the Offer |
| Option 54 | `10.1.1.1` | Server Identifier — which server's Offer the client is accepting |

> **Why Option 54 matters:** If multiple DHCP servers sent Offers, the Server Identifier in the Request tells every server on the segment whether it was selected. Non-selected servers return their offered addresses to their pools.

---

### Message 4: DHCP Acknowledge (ACK)

**What the server is saying:** *"Confirmed. 10.1.1.11 is yours for 7200 seconds. Here is your full configuration."*

**Wireshark display filter:**
```
dhcp.option.dhcp == 5
```

**Key fields to examine:**

| Field | Value | Why It Matters |
|-------|-------|---------------|
| IP Src | `10.1.1.1` | Server's IP |
| IP Dst | `255.255.255.255` | Broadcast (or unicast if Broadcast flag was not set) |
| op | `2` (BOOTREPLY) | Server-to-client |
| xid | `0x3903F326` | Same transaction — all four match |
| yiaddr | `10.1.1.11` | The **confirmed** IP address assignment |
| Option 53 | `5` (DHCP ACK) | Message type |
| Option 51 | `7200` | Lease Time |
| Option 58 | `3600` | **T1 Renewal Time** (50% of lease) |
| Option 59 | `6300` | **T2 Rebinding Time** (87.5% of lease) |
| Option 1 | `255.255.255.0` | Subnet Mask |
| Option 3 | `10.1.1.1` | Default Gateway |
| Option 6 | `8.8.8.8, 8.8.4.4` | DNS Servers |

After receiving the ACK, the client applies the IP configuration to its interface and transitions to the **BOUND** state. The lease clock starts ticking.

---

## 2.5 — Essential Wireshark Display Filters for DHCP

Mastering filters is the difference between drowning in packets and finding the needle in the haystack. Here are the filters you need for DHCP analysis:

### Core Filters

| Filter Expression | What It Shows |
|-------------------|---------------|
| `dhcp` | All DHCP traffic |
| `bootp` | Same as above (legacy alias) |
| `udp.port == 67 \|\| udp.port == 68` | DHCP by port number (works even if dissector fails) |
| `dhcp.option.dhcp == 1` | Discover messages only |
| `dhcp.option.dhcp == 2` | Offer messages only |
| `dhcp.option.dhcp == 3` | Request messages only |
| `dhcp.option.dhcp == 5` | ACK messages only |
| `dhcp.option.dhcp == 6` | NAK messages only |
| `dhcp.option.dhcp == 7` | Release messages only |
| `dhcp.option.dhcp == 8` | Inform messages only |

### Targeted Analysis Filters

| Filter Expression | Use Case |
|-------------------|----------|
| `dhcp.id == 0x3903F326` | Isolate a single DORA exchange by transaction ID |
| `dhcp.hw.mac_addr == 00:50:79:66:68:01` | All DHCP traffic for a specific client (by MAC) |
| `dhcp.option.requested_ip_address == 10.1.1.11` | Requests for a specific IP address |
| `dhcp.option.dhcp_server_id == 10.1.1.1` | Traffic involving a specific DHCP server |
| `dhcp.option.ip_address_lease_time` | Packets containing lease time information |
| `bootp.option.type == 53` | Filter by the presence of the DHCP Message Type option |
| `dhcp.option.dhcp == 6` | DHCP NAK — something went wrong |

### Combination Filters

```
# Show only Discover and Offer (first half of DORA)
dhcp.option.dhcp == 1 || dhcp.option.dhcp == 2

# Show all traffic for a specific client's full DORA exchange
dhcp.hw.mac_addr == 00:50:79:66:68:01 && (dhcp.option.dhcp == 1 || dhcp.option.dhcp == 2 || dhcp.option.dhcp == 3 || dhcp.option.dhcp == 5)

# Show NAKs and Declines (trouble indicators)
dhcp.option.dhcp == 6 || dhcp.option.dhcp == 4
```

---

## 2.6 — Lease Lifecycle: Renewal, Rebinding, and Release

The DORA exchange is only the beginning of a lease's life. After the client enters the BOUND state, three timers govern what happens next.

### The Three Timers

| Timer | Default | Trigger | Behavior |
|-------|---------|---------|----------|
| **T1** (Renewal) | 50% of lease | Client enters RENEWING state | Client sends unicast Request directly to the leasing server |
| **T2** (Rebinding) | 87.5% of lease | Client enters REBINDING state | Client broadcasts Request to any server (original may be down) |
| **Lease Expiration** | 100% of lease | Lease dies | Client must stop using the IP and restart with Discover |

For our 2-hour (7200s) lab lease:
- **T1 fires at 1 hour** (3600s) → client unicasts a renewal Request to `10.1.1.1`
- **T2 fires at 1h 45m** (6300s) → if T1 renewal failed, client broadcasts a Request
- **Lease expires at 2 hours** (7200s) → client releases the address and goes back to Discover

### Renewal (T1) in Wireshark

When T1 fires, you will see a **two-packet exchange** (not four):

1. **DHCP Request** (unicast from client to server)
2. **DHCP ACK** (unicast from server to client)

Key difference from the initial DORA Request:
- `ciaddr` is now **populated** with the client's current IP (`10.1.1.11`) — because the client is in BOUND state and already owns the address.
- The Request is **unicast**, not broadcast.
- There is no Discover or Offer — the client already knows which server it wants.

**Filter to isolate renewals:**
```
dhcp.option.dhcp == 3 && ip.dst != 255.255.255.255
```
This catches Request messages sent as unicast — which are renewals, not initial requests.

### Rebinding (T2) in Wireshark

If the T1 renewal fails (server down, unreachable), the client enters the REBINDING state at T2. The rebinding Request looks identical to a renewal Request, except:
- It is **broadcast** (`255.255.255.255`), not unicast.
- `ciaddr` is still populated (client still holds the lease).
- Any DHCP server on the segment can respond.

### DHCP Release

When a client gracefully disconnects (shutdown, `dhclient -r`, `ipconfig /release`), it sends a **DHCP Release** message:

```
dhcp.option.dhcp == 7
```

| Field | Value |
|-------|-------|
| op | `1` (BOOTREQUEST) |
| ciaddr | `10.1.1.11` (the address being released) |
| Option 53 | `7` (Release) |
| Option 54 | `10.1.1.1` (which server granted the lease) |

The Release is **unicast** directly to the server. After sending it, the client immediately deconfigures its interface. The server returns the address to the pool.

> **Troubleshooting note:** Not all clients send a Release on disconnect. If a laptop is closed or a VM is destroyed, the lease simply expires at the server after the full lease duration. This is normal behavior, not a bug.

### DHCP NAK (Negative Acknowledge)

If the server cannot honor a Request — for example, the client is requesting an address from a different subnet after being moved — the server responds with a **DHCP NAK** (Option 53 = 6). The client must discard its current address and restart the DORA process from Discover.

**Filter:**
```
dhcp.option.dhcp == 6
```

Seeing NAKs in a capture is always worth investigating. Common causes:
- Client moved to a different subnet but is requesting its old address.
- DHCP pool has been reconfigured or deleted.
- Lease was manually cleared from the server.

---

## 2.7 — Verifying DHCP on the Server (IOS CLI)

After completing the DORA exchange, verify the lease on the server side:

```
! Show all active DHCP bindings
show ip dhcp binding

! Expected output:
! IP address       Client-ID/              Lease expiration        Type
!                  Hardware address
! 10.1.1.11        0100.5079.6668.01       Mar 01 2026 04:00 PM   Automatic

! Show DHCP server statistics
show ip dhcp server statistics

! Show pool configuration
show ip dhcp pool LAB-POOL

! Debug DHCP events in real time (use carefully)
debug ip dhcp server events
```

Cross-reference the `show ip dhcp binding` output with your Wireshark capture. The MAC address in the binding table should match the `chaddr` in your DORA packets.

---

## Hands-On Labs

### Lab 2.1 — Capture a Full DORA Exchange

**Objective:** Capture all four DORA messages and verify the transaction ID is consistent.

**Steps:**

1. Start Wireshark (or `tcpdump`) on the Linux client's network interface.
2. Release any existing lease:
   ```bash
   sudo dhclient -r ens2
   ```
3. Request a new lease:
   ```bash
   sudo dhclient -v ens2
   ```
4. Stop the capture.
5. Open the capture in Wireshark and apply the filter: `dhcp`
6. Verify you see exactly four packets: Discover → Offer → Request → ACK.
7. For each packet, expand the DHCP layer and note the `xid` value. **Confirm all four share the same `xid`.**

**Deliverable:** Screenshot of the Wireshark packet list showing all four DORA messages with the `xid` column visible.

> **Adding the xid column:** Right-click any column header → Column Preferences → Add → Title: `Transaction ID`, Fields: `dhcp.id`, Type: Custom. This adds a permanent column that makes DORA correlation effortless.

---

### Lab 2.2 — Transaction ID Correlation

**Objective:** Use the transaction ID to isolate one client's exchange in a noisy capture.

**Steps:**

1. If you have multiple clients in your lab, start captures on all of them simultaneously and request DHCP leases on each.
2. Open the merged capture file. You will see interleaved DORA exchanges.
3. Click on any DHCP Discover packet. In the packet detail pane, find `Transaction ID` under the DHCP section. Note the hex value (e.g., `0x3903F326`).
4. Apply the display filter:
   ```
   dhcp.id == 0x3903F326
   ```
5. Verify that only four packets remain — the complete DORA exchange for that one client.
6. Repeat with a different `xid` to isolate another client's exchange.

**Deliverable:** Two filtered views, each showing a clean four-packet DORA sequence for different clients.

---

### Lab 2.3 — Field Comparison Across DORA Messages

**Objective:** Build a comparison table showing how key fields change across the four DORA messages.

**Steps:**

1. Using your DORA capture, fill in the following table by inspecting each packet:

| Field | Discover | Offer | Request | ACK |
|-------|----------|-------|---------|-----|
| op | ? | ? | ? | ? |
| xid | ? | ? | ? | ? |
| ciaddr | ? | ? | ? | ? |
| yiaddr | ? | ? | ? | ? |
| siaddr | ? | ? | ? | ? |
| giaddr | ? | ? | ? | ? |
| chaddr | ? | ? | ? | ? |
| Option 53 | ? | ? | ? | ? |
| Option 50 | ? | ? | ? | ? |
| Option 54 | ? | ? | ? | ? |

2. Answer these questions:
   - In which message does `yiaddr` first get populated?
   - Which messages have `op = 1` and which have `op = 2`?
   - Why is `ciaddr` still `0.0.0.0` in the Request?
   - When does the client first have a usable IP address?

**Expected answers:**
- `yiaddr` is first populated in the **Offer** (message 2).
- `op = 1` in Discover and Request (client → server). `op = 2` in Offer and ACK (server → client).
- `ciaddr` is `0.0.0.0` in the Request because the client does not yet own the address — the ACK has not been received.
- The client can use the IP address only **after receiving the ACK** (message 4).

---

### Lab 2.4 — Observe a Lease Renewal

**Objective:** Capture the T1 renewal exchange.

**Steps:**

1. With the 2-hour lease from Lab 2.1, start a long-running capture:
   ```bash
   sudo tcpdump -i ens2 -w /tmp/dhcp-renewal.pcap port 67 or port 68
   ```
2. Wait approximately 1 hour (or set a shorter lease, e.g., `lease 0 0 10` for a 10-minute lease with T1 at 5 minutes).
3. After the renewal triggers, stop the capture.
4. Open in Wireshark and apply:
   ```
   dhcp.option.dhcp == 3 && ip.dst != 255.255.255.255
   ```
5. You should see the unicast renewal Request. Find the matching ACK.
6. Compare the renewal Request to the initial DORA Request:
   - Is `ciaddr` populated?
   - Is the packet unicast or broadcast?
   - Is Option 50 (Requested IP) present?

> **Shortcut for lab environments:** To avoid waiting, set the lease to 10 minutes: `lease 0 0 10`. T1 will fire at 5 minutes, giving you a quick capture cycle.

---

### Lab 2.5 — Trigger and Capture a DHCP Release

**Objective:** Capture and analyze a DHCP Release message.

**Steps:**

1. Start a capture on the client interface.
2. Release the lease:
   ```bash
   sudo dhclient -r ens2
   ```
3. Stop the capture and open in Wireshark.
4. Apply the filter: `dhcp.option.dhcp == 7`
5. Inspect the Release packet:
   - What is `ciaddr`?
   - What is Option 54 (Server Identifier)?
   - Is this packet unicast or broadcast?
6. Verify on the server:
   ```
   show ip dhcp binding
   ```
   The released address should no longer appear (or will show as expired).

---

## DHCP State Machine Summary

The client moves through these states during and after DORA:

```
        ┌──────────┐
        │   INIT   │ ─── Client starts, sends Discover
        └────┬─────┘
             │
             ▼
      ┌──────────────┐
      │  SELECTING   │ ─── Waiting for Offers; picks one, sends Request
      └──────┬───────┘
             │
             ▼
      ┌──────────────┐
      │  REQUESTING  │ ─── Sent Request, waiting for ACK/NAK
      └──────┬───────┘
             │ ACK received
             ▼
      ┌──────────────┐
      │    BOUND     │ ─── Client has a valid lease. Timers running.
      └──┬───────┬───┘
         │       │
    T1   │       │ T2
         ▼       ▼
   ┌──────────┐  ┌─────────────┐
   │ RENEWING │  │ REBINDING   │
   │ (unicast)│  │ (broadcast) │
   └────┬─────┘  └──────┬──────┘
        │ ACK           │ ACK
        └──────┬────────┘
               ▼
        ┌──────────┐
        │  BOUND   │ ─── Lease renewed, timers reset
        └──────────┘
```

If no ACK is received by lease expiration → back to **INIT** → full DORA restart.

---

## Module Summary

| Concept | Key Takeaway |
|---------|-------------|
| DORA sequence | Discover → Offer → Request → ACK. Always in this order for new leases. |
| Transaction ID (xid) | Links all four messages. Use `dhcp.id == 0xVALUE` to filter. |
| op code | `1` = client → server, `2` = server → client. |
| yiaddr | Empty in Discover/Request, populated in Offer/ACK. |
| ciaddr | Empty during initial DORA, populated during renewals. |
| T1 / T2 timers | Renewal at 50%, rebinding at 87.5%. Key to understanding lease lifecycle. |
| Release | Unicast message from client to server. Not always sent (ungraceful disconnects). |
| NAK | Server rejects the Request. Client must restart with Discover. |

---

## What's Next

In **Module 3: DHCP Options**, we go deeper into the options field — the most powerful and flexible part of the DHCP message. You will learn how to decode Option 43 (vendor-specific), Option 82 (relay information), Option 150 (TFTP server for IP phones), and how to use Wireshark to identify misconfigured or missing options that cause real-world failures.

---

**Nav:** [← Module 1](01-wireshark-fundamentals.md) | [Course Home](../README.md) | [Module 3 →](03-dhcp-options.md)
