A working DHCP server written from scratch in Python with Scapy. It sniffs raw DHCP traffic on a real network interface, allocates and tracks leases, and handles the full message set: Discover, Offer, Request, ACK, NAK, Decline, Release and Inform. It serves real clients on a live LAN once the router's DHCP is turned off.
Stress test: concurrent clients handled by the thread pool at 20+ requests/second.
sequenceDiagram
participant C as Client
participant S as Server (sniff → ThreadPool)
C->>S: DHCPDISCOVER (broadcast)
S->>S: pick free IP (ARP+ICMP-verified pool)
S-->>C: DHCPOFFER
C->>S: DHCPREQUEST
alt IP valid & still free
S-->>C: DHCPACK (lease, T1, T2, DNS, domain)
else invalid / taken
S-->>C: DHCPNAK
end
- Packet capture:
scapy.sniffon ports 67/68. Each packet goes to aThreadPoolExecutorwith 20 workers, so a slow handler never blocks capture. - Address pool: built from the host's subnet and gateway (detected automatically). Addresses already in use are found by a combined ARP sweep + multithreaded ICMP scan (100 workers), so the server never hands out an IP another device already holds.
- Lease lifecycle: configurable lease time, renewal (T1) and rebinding (T2). A 10-minute periodic task expires stale leases and rescans the network.
- Persistence: the lease table is written to
DHCP.csvfor tracking and recovery after a restart. - Client simulators:
Client_Ack.py,Client_NAK.py,Client_Decline.pyandClient_Inform.pycraft specific message flows to exercise each server path. - GUI: a small Tkinter front end (
GUI.py) runs the server and streams its log.
DHCP.pcapng is a Wireshark capture of a full DORA exchange against the server.
Requires Python 3, Npcap on Windows, and admin/root privileges (raw sockets).
pip install scapy
sudo python DHCP.py # prompts for lease/T1/T2, two DNS servers, domain nameTurn off your router's DHCP server first so it doesn't answer before this one does. Ethernet is the most reliable interface.
- The lease table is a plain dict shared by the worker threads with no lock. Two simultaneous DISCOVERs could be offered the same IP. I'd add a
threading.Lock(or a single allocator thread with a queue). - Config is global state set from
input(). I'd move it to a config file and CLI flags and wrap the server in a class, so it can be unit-tested without a network.
Computer Networks course project (CSE, Ain Shams, Fall 2024), team of 4. I built the server across Phases 2–4: the Scapy capture loop, the message handlers, the thread pool, ARP/ICMP discovery, lease expiry and CSV persistence. Teammates handled planning and design (Phase 1) and contributed to Phases 2–4. Full report: docs/Networks_Report.pdf.
Python · Scapy · concurrent.futures · Tkinter · Wireshark · RFC 2131/2132
