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:
- DISCOVER: Client broadcasts to find DHCP servers
- OFFER: Server offers an IP address
- REQUEST: Client requests the offered IP
- 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
- Allocated: IP assigned to client
- Renewing: Client renewing lease (at 50% of lease time)
- Rebinding: Client trying different server (at 87.5% of lease time)
- 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:
-
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
-
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
-
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
-
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):
- Client sends unicast DHCP REQUEST to original server
- Server responds with ACK (extends lease)
- If successful, lease extended, process repeats
Rebinding Process (T2):
- If renewal fails, client broadcasts DHCP REQUEST
- Any DHCP server can respond with ACK
- If successful, lease extended with new server
Expiration:
- If rebinding fails, lease expires
- Client must release IP and start DORA again
- 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:
- High Availability: Multiple servers with automatic failover
- Conflict Prevention: Distributed locks, atomic IP allocation
- Lease Management: Centralized lease store with expiration handling
- Consistent Hashing: Predictable server assignment for IP ranges
- Monitoring: Health checks, pool utilization, lease tracking
- 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