Topic Overview

DHCP Flow

Learn how DHCP (Dynamic Host Configuration Protocol) automatically assigns IP addresses, subnet masks, gateways, and DNS servers to network devices.

DHCP (Dynamic Host Configuration Protocol) automatically assigns IP addresses and network configuration to devices when they connect to a network. It eliminates the need for manual IP configuration and centralizes network management.


DHCP Process (DORA)

DHCP uses a four-step process called DORA:

  1. DISCOVER: Client broadcasts to find DHCP servers
  2. OFFER: Server offers an IP address
  3. REQUEST: Client requests the offered IP
  4. ACK: Server acknowledges and assigns the IP

Detailed DHCP Flow

Step 1: DISCOVER (Client → Network)

Client broadcasts DHCP DISCOVER
  Source IP: 0.0.0.0 (no IP yet)
  Destination IP: 255.255.255.255 (broadcast)
  Source MAC: Client's MAC address
  DHCP Message: "I need an IP address"

What happens:

  • Client sends broadcast packet (no IP address yet)
  • All DHCP servers on the network receive the request
  • Client doesn't know which server will respond

Step 2: OFFER (Server → Client)

DHCP Server responds with DHCP OFFER
  Source IP: Server's IP
  Destination IP: 255.255.255.255 (broadcast) or client's MAC
  Offered IP: 192.168.1.100
  Subnet Mask: 255.255.255.0
  Gateway: 192.168.1.1
  DNS: 8.8.8.8
  Lease Time: 3600 seconds (1 hour)

What happens:

  • Server reserves an IP address for the client
  • Server sends offer with network configuration
  • Multiple servers may respond (client chooses one)

Step 3: REQUEST (Client → Network)

Client broadcasts DHCP REQUEST
  Source IP: 0.0.0.0
  Destination IP: 255.255.255.255
  Requested IP: 192.168.1.100 (from chosen server)
  Server ID: Server's IP that client chose

What happens:

  • Client accepts one offer (usually first received)
  • Client broadcasts request to inform all servers
  • Other servers release their reserved IPs

Step 4: ACK (Server → Client)

DHCP Server sends DHCP ACK
  Assigned IP: 192.168.1.100
  Subnet Mask: 255.255.255.0
  Gateway: 192.168.1.1
  DNS: 8.8.8.8, 8.8.4.4
  Lease Time: 3600 seconds
  Renewal Time: 1800 seconds (50% of lease)

What happens:

  • Server confirms IP assignment
  • Client configures its network interface
  • Client can now communicate on the network

DHCP Lease Management

Lease States

  1. Allocated: IP assigned to client
  2. Renewing: Client renewing lease (at 50% of lease time)
  3. Rebinding: Client trying different server (at 87.5% of lease time)
  4. Expired: Lease expired, IP available for reassignment

Lease Renewal Process

T=0:     IP assigned (lease = 3600s)
T=1800:  Client sends DHCP REQUEST to renew (50% of lease)
T=3150:  Client broadcasts DHCP REQUEST (87.5% of lease)
T=3600:  Lease expires, client must get new IP

Renewal (T1 = 50% of lease):

  • Client sends unicast REQUEST to original server
  • Server responds with ACK (extends lease)

Rebinding (T2 = 87.5% of lease):

  • If renewal fails, client broadcasts REQUEST
  • Any server can respond with ACK

Expiration:

  • If rebinding fails, lease expires
  • Client must start DORA process again

Examples

DHCP Server Configuration

# Simple DHCP server simulation
class DHCPServer:
    def __init__(self, network="192.168.1.0/24"):
        self.pool = self.create_pool(network)
        self.leases = {}  # MAC -> Lease info
        self.lease_time = 3600  # 1 hour
    
    def create_pool(self, network):
        """Create IP address pool"""
        import ipaddress
        net = ipaddress.IPv4Network(network)
        # Exclude network, broadcast, gateway
        return [str(ip) for ip in net.hosts()][:100]  # First 100 IPs
    
    def handle_discover(self, client_mac):
        """Handle DHCP DISCOVER"""
        # Find available IP
        ip = self.get_available_ip()
        if not ip:
            return None
        
        # Reserve IP
        self.reserve_ip(ip, client_mac)
        
        return {
            'ip': ip,
            'subnet_mask': '255.255.255.0',
            'gateway': '192.168.1.1',
            'dns': ['8.8.8.8', '8.8.4.4'],
            'lease_time': self.lease_time
        }
    
    def handle_request(self, client_mac, requested_ip):
        """Handle DHCP REQUEST"""
        if self.is_ip_available(requested_ip, client_mac):
            lease = {
                'ip': requested_ip,
                'mac': client_mac,
                'expires_at': time.time() + self.lease_time,
                'renewal_time': time.time() + (self.lease_time * 0.5)
            }
            self.leases[client_mac] = lease
            return {'status': 'ACK', 'lease': lease}
        else:
            return {'status': 'NAK'}  # IP not available
    
    def get_available_ip(self):
        """Get next available IP from pool"""
        for ip in self.pool:
            if not self.is_leased(ip):
                return ip
        return None

DHCP Client Implementation

import socket
import struct

class DHCPClient:
    def __init__(self):
        self.mac = self.get_mac_address()
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        self.socket.bind(('0.0.0.0', 68))  # DHCP client port
    
    def discover(self):
        """Send DHCP DISCOVER"""
        message = self.create_dhcp_message('DISCOVER')
        self.socket.sendto(message, ('255.255.255.255', 67))  # DHCP server port
        
        # Wait for offers
        offers = []
        self.socket.settimeout(5)
        try:
            while True:
                data, addr = self.socket.recvfrom(1024)
                offer = self.parse_dhcp_message(data)
                if offer['type'] == 'OFFER':
                    offers.append(offer)
        except socket.timeout:
            pass
        
        return offers
    
    def request(self, offer):
        """Send DHCP REQUEST for chosen offer"""
        message = self.create_dhcp_message('REQUEST', offer)
        self.socket.sendto(message, ('255.255.255.255', 67))
        
        # Wait for ACK
        self.socket.settimeout(5)
        data, addr = self.socket.recvfrom(1024)
        ack = self.parse_dhcp_message(data)
        
        if ack['type'] == 'ACK':
            self.configure_interface(ack)
            return ack
        else:
            raise Exception("DHCP REQUEST failed")
    
    def renew(self, lease):
        """Renew DHCP lease"""
        if time.time() < lease['renewal_time']:
            return  # Not time to renew yet
        
        # Send REQUEST to original server
        message = self.create_dhcp_message('REQUEST', lease)
        self.socket.sendto(message, (lease['server_ip'], 67))
        
        # Wait for ACK
        data, addr = self.socket.recvfrom(1024)
        ack = self.parse_dhcp_message(data)
        
        if ack['type'] == 'ACK':
            lease['expires_at'] = time.time() + ack['lease_time']
            lease['renewal_time'] = time.time() + (ack['lease_time'] * 0.5)
            return ack

DHCP Relay Agent

class DHCPRelay:
    """Relay DHCP messages between networks"""
    
    def __init__(self, server_ip):
        self.server_ip = server_ip
        self.interfaces = []
    
    def relay_discover(self, packet, received_interface):
        """Relay DISCOVER to DHCP server"""
        # Modify packet to include relay agent IP
        packet['giaddr'] = received_interface.ip  # Gateway IP
        packet['hops'] += 1
        
        # Forward to DHCP server
        self.forward_to_server(packet)
    
    def relay_offer(self, packet):
        """Relay OFFER back to client"""
        # Extract client MAC from packet
        client_mac = packet['chaddr']
        
        # Find interface where client is located
        interface = self.find_interface_for_client(client_mac)
        
        # Forward to client
        interface.send_broadcast(packet)

Common Pitfalls

  • DHCP exhaustion: Running out of available IP addresses. Fix: Monitor pool usage, reduce lease time, expand pool size
  • Multiple DHCP servers: Conflicting IP assignments. Fix: Ensure only one DHCP server per network segment
  • Lease expiration: Clients losing IP addresses. Fix: Implement proper renewal, monitor lease times
  • DHCP relay misconfiguration: Clients not getting IPs across subnets. Fix: Configure relay agent IP (giaddr) correctly
  • Static IP conflicts: Manually assigned IPs conflicting with DHCP pool. Fix: Exclude static IPs from DHCP pool
  • Not handling NAK: Client not handling negative acknowledgment. Fix: Implement retry logic, fallback to new DISCOVER
  • Security: Rogue DHCP servers. Fix: Use DHCP snooping on switches, authenticate DHCP servers

Interview Questions

Beginner

Q: Explain the DHCP process. What are the four steps?

A:

DHCP DORA Process:

  1. DISCOVER (Client → Network)

    • Client broadcasts: "I need an IP address"
    • Source: 0.0.0.0, Destination: 255.255.255.255
    • Client has no IP yet
  2. OFFER (Server → Client)

    • Server responds: "I can offer you 192.168.1.100"
    • Includes: IP, subnet mask, gateway, DNS, lease time
    • Multiple servers may respond
  3. REQUEST (Client → Network)

    • Client broadcasts: "I accept the offer for 192.168.1.100"
    • Informs all servers which offer was chosen
    • Other servers release their reserved IPs
  4. ACK (Server → Client)

    • Server confirms: "192.168.1.100 is yours"
    • Client configures network interface
    • Client can now communicate

Why broadcast?

  • Client has no IP address initially
  • Client doesn't know server's IP
  • Multiple servers may be available

Intermediate

Q: How does DHCP lease renewal work? What happens if renewal fails?

A:

Lease Renewal Timeline:

Lease Time: 3600 seconds (1 hour)
T1 (Renewal): 1800 seconds (50% of lease)
T2 (Rebinding): 3150 seconds (87.5% of lease)
Expiration: 3600 seconds

Renewal Process (T1):

  1. Client sends unicast DHCP REQUEST to original server
  2. Server responds with ACK (extends lease)
  3. If successful, lease extended, process repeats

Rebinding Process (T2):

  1. If renewal fails, client broadcasts DHCP REQUEST
  2. Any DHCP server can respond with ACK
  3. If successful, lease extended with new server

Expiration:

  1. If rebinding fails, lease expires
  2. Client must release IP and start DORA again
  3. Client may get same or different IP

Example:

# At T1 (50% of lease)
if time.time() >= lease['renewal_time']:
    ack = client.renew(lease)  # Unicast to original server
    if ack:
        lease['expires_at'] = time.time() + ack['lease_time']
    else:
        # Renewal failed, wait for T2

# At T2 (87.5% of lease)
if time.time() >= lease['rebind_time']:
    ack = client.rebind(lease)  # Broadcast to any server
    if not ack:
        # Rebinding failed, lease expires
        client.release_ip()
        client.discover()  # Start DORA again

Senior

Q: Design a distributed DHCP system for a cloud provider that needs to handle millions of devices across multiple data centers. How do you ensure high availability, prevent IP conflicts, and handle lease management?

A:

class DistributedDHCPSystem {
  private servers: DHCPServer[];
  private ipPool: IPPoolManager;
  private leaseStore: DistributedLeaseStore;
  private healthMonitor: HealthMonitor;
  
  constructor() {
    // Multiple DHCP servers for redundancy
    this.servers = [
      new DHCPServer('dc1'),
      new DHCPServer('dc2'),
      new DHCPServer('dc3')
    ];
    
    // Centralized IP pool management
    this.ipPool = new IPPoolManager({
      network: '10.0.0.0/8',
      allocationStrategy: 'consistent-hashing'
    });
    
    // Distributed lease storage (Redis cluster)
    this.leaseStore = new DistributedLeaseStore({
      backend: 'redis-cluster',
      replication: 3
    });
  }
  
  // 1. High Availability
  async handleDiscover(clientMac: string, clientInfo: ClientInfo): Promise<DHCPOffer> {
    // Try primary server first
    try {
      return await this.servers[0].handleDiscover(clientMac, clientInfo);
    } catch (error) {
      // Failover to secondary servers
      for (const server of this.servers.slice(1)) {
        try {
          return await server.handleDiscover(clientMac, clientInfo);
        } catch (e) {
          continue;
        }
      }
      throw new Error("All DHCP servers unavailable");
    }
  }
  
  // 2. IP Conflict Prevention
  async allocateIP(clientMac: string, network: string): Promise<string> {
    // Use distributed lock to prevent conflicts
    const lock = await this.leaseStore.acquireLock(`ip:${network}`);
    
    try {
      // Check if IP is available across all servers
      const availableIPs = await this.ipPool.getAvailableIPs(network);
      
      for (const ip of availableIPs) {
        // Check lease store (distributed)
        const existing = await this.leaseStore.getLease(ip);
        
        if (!existing || this.isExpired(existing)) {
          // Allocate IP atomically
          const lease = {
            ip,
            mac: clientMac,
            allocatedAt: Date.now(),
            expiresAt: Date.now() + 3600000, // 1 hour
            server: this.getServerForIP(ip) // Consistent hashing
          };
          
          await this.leaseStore.setLease(ip, lease);
          return ip;
        }
      }
      
      throw new Error("IP pool exhausted");
    } finally {
      await lock.release();
    }
  }
  
  // 3. Lease Management
  async renewLease(clientMac: string, requestedIP: string): Promise<Lease> {
    const lease = await this.leaseStore.getLease(requestedIP);
    
    if (!lease || lease.mac !== clientMac) {
      throw new Error("Lease not found or invalid");
    }
    
    if (this.isExpired(lease)) {
      throw new Error("Lease expired");
    }
    
    // Extend lease
    lease.expiresAt = Date.now() + 3600000;
    lease.renewalCount = (lease.renewalCount || 0) + 1;
    
    await this.leaseStore.setLease(requestedIP, lease);
    
    return lease;
  }
  
  // 4. Lease Cleanup
  async cleanupExpiredLeases(): Promise<void> {
    const expired = await this.leaseStore.getExpiredLeases();
    
    for (const lease of expired) {
      // Release IP back to pool
      await this.ipPool.releaseIP(lease.ip);
      await this.leaseStore.deleteLease(lease.ip);
    }
  }
  
  // 5. Consistent Hashing for Server Assignment
  getServerForIP(ip: string): DHCPServer {
    // Use consistent hashing to assign IPs to servers
    // Ensures same server handles same IP range
    const hash = this.hashIP(ip);
    const serverIndex = hash % this.servers.length;
    return this.servers[serverIndex];
  }
  
  // 6. Health Monitoring
  async monitorHealth(): Promise<HealthStatus> {
    const checks = await Promise.all(
      this.servers.map(server => server.healthCheck())
    );
    
    const healthy = checks.filter(c => c.healthy);
    
    if (healthy.length < 2) {
      // Alert: Less than 2 healthy servers
      this.alert("DHCP redundancy at risk");
    }
    
    return {
      totalServers: this.servers.length,
      healthyServers: healthy.length,
      ipPoolUtilization: await this.ipPool.getUtilization(),
      leaseCount: await this.leaseStore.getLeaseCount()
    };
  }
}

Features:

  1. High Availability: Multiple servers with automatic failover
  2. Conflict Prevention: Distributed locks, atomic IP allocation
  3. Lease Management: Centralized lease store with expiration handling
  4. Consistent Hashing: Predictable server assignment for IP ranges
  5. Monitoring: Health checks, pool utilization, lease tracking
  6. Scalability: Horizontal scaling, distributed storage

Key Takeaways

  • DORA process: DISCOVER → OFFER → REQUEST → ACK for IP assignment
  • Broadcast communication: Client broadcasts because it has no IP initially
  • Lease management: IPs assigned for a limited time (lease), must be renewed
  • Renewal timing: T1 (50% of lease) for renewal, T2 (87.5%) for rebinding
  • DHCP relay: Forwards DHCP messages between network segments
  • High availability: Multiple servers, failover, distributed lease storage
  • Conflict prevention: Use distributed locks, atomic allocation, consistent hashing
  • Lease expiration: Cleanup expired leases, release IPs back to pool
  • Security: DHCP snooping, authentication, rogue server detection

About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.