3 unstable releases
Uses new Rust 2024
| new 0.2.1 | Feb 23, 2026 |
|---|---|
| 0.2.0 | Feb 2, 2026 |
| 0.1.0 | Feb 2, 2026 |
#1036 in Network programming
240KB
5K
SLoC
dhcplease
A cross-platform DHCP server library and CLI, written in Rust.
Features
- Full DHCP protocol implementation (DISCOVER, OFFER, REQUEST, ACK, NAK, RELEASE, DECLINE, INFORM)
- Client Identifier (Option 61) support for proper client identification
- Relay Agent Info (Option 82) echoing for relay environments
- Configurable IP pool, lease duration, gateway, DNS servers, and more
- Static MAC-to-IP bindings
- Lease persistence across restarts
- Concurrent packet handling with rate limiting
- CLI and library crate for embedding in your own applications
- Async/await with Tokio
- Cross-platform (Linux, macOS, Windows); interface binding on Windows
Quick Start
Add this to your Cargo.toml:
[dependencies]
dhcplease = "0.2.1"
Installation
cargo install dhcplease
Or build from source:
cargo install --path .
Usage
Run the server
# Run with default config (will create config.json if it doesn't exist)
dhcplease run
# Run with custom config
dhcplease -c my-config.json run
# Run with debug logging
dhcplease -l debug run
Note: Binding to port 67 requires administrator privileges. Run from an elevated PowerShell or Command Prompt.
Other commands
# Show current configuration
dhcplease show-config
# List active leases
dhcplease list-leases
# Clean up expired leases
dhcplease cleanup-leases
Configuration
On first run, config.json is created with default values:
{
"server_ip": "192.168.1.1",
"subnet_mask": "255.255.255.0",
"pool_start": "192.168.1.100",
"pool_end": "192.168.1.200",
"gateway": "192.168.1.1",
"dns_servers": ["8.8.8.8", "8.8.4.4"],
"domain_name": null,
"lease_duration_seconds": 86400,
"renewal_time_seconds": null,
"rebinding_time_seconds": null,
"broadcast_address": null,
"mtu": null,
"static_bindings": [],
"leases_file": "leases.json",
"interface_index": null
}
Configuration options
| Option | Description |
|---|---|
server_ip |
IP address of the DHCP server |
subnet_mask |
Subnet mask to provide to clients |
pool_start |
First IP address in the dynamic pool |
pool_end |
Last IP address in the dynamic pool |
gateway |
Default gateway to provide to clients |
dns_servers |
List of DNS servers to provide to clients |
domain_name |
Domain name to provide to clients |
lease_duration_seconds |
How long leases are valid |
renewal_time_seconds |
When clients should attempt renewal (default: half of lease duration) |
rebinding_time_seconds |
When clients should attempt rebinding (default: 7/8 of lease duration) |
broadcast_address |
Broadcast address (calculated from server_ip and subnet_mask if not set) |
mtu |
Interface MTU to provide to clients |
static_bindings |
List of MAC-to-IP static assignments |
leases_file |
Path to the lease persistence file |
interface_index |
Windows network interface index to bind to (optional) |
Static bindings
To always assign a specific IP to a MAC address:
{
"static_bindings": [
{
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.150",
"hostname": "my-device"
}
]
}
Testing with a Real Device
Prerequisites
- Windows host machine running dhcplease
- A second device (laptop, Raspberry Pi, etc.) that needs an IP address
- Direct Ethernet connection between devices via:
- Ethernet cable (direct or through an isolated switch)
- USB-to-Ethernet adapter
Setup Steps
1. Identify your network interface
Open PowerShell and run:
Get-NetAdapter | Format-Table Name, InterfaceIndex, Status
Note the InterfaceIndex of the adapter connected to your test device.
2. Configure a static IP on the Windows host
Set a static IP on the interface you'll use for DHCP:
# Replace <InterfaceIndex> and IPs as needed
New-NetIPAddress -InterfaceIndex <InterfaceIndex> -IPAddress 192.168.1.1 -PrefixLength 24
3. Disable Windows DHCP client on that interface
Set-NetIPInterface -InterfaceIndex <InterfaceIndex> -Dhcp Disabled
4. Configure dhcplease
Create or edit config.json, replacing 12 with your actual interface index:
{
"server_ip": "192.168.1.1",
"subnet_mask": "255.255.255.0",
"pool_start": "192.168.1.100",
"pool_end": "192.168.1.200",
"gateway": "192.168.1.1",
"dns_servers": ["8.8.8.8", "8.8.4.4"],
"lease_duration_seconds": 3600,
"leases_file": "leases.json",
"interface_index": 12
}
5. Disable Windows Firewall for the interface (or add rules)
Either disable the firewall for testing:
Set-NetFirewallProfile -Profile Private -Enabled False
Or add specific rules for DHCP:
New-NetFirewallRule -DisplayName "DHCP Server In" -Direction Inbound -LocalPort 67 -Protocol UDP -Action Allow
New-NetFirewallRule -DisplayName "DHCP Server Out" -Direction Outbound -LocalPort 68 -Protocol UDP -Action Allow
6. Run dhcplease as Administrator
# Open an elevated PowerShell prompt
dhcplease -l debug run
7. Connect and test the client device
On the client device, connect via Ethernet and trigger a DHCP request.
Windows client:
ipconfig /release
ipconfig /renew
Linux client:
sudo dhclient -r eth0 && sudo dhclient eth0
macOS client (see detailed section below):
sudo ipconfig set en6 DHCP
You should see DISCOVER, OFFER, REQUEST, and ACK messages in the dhcplease output.
Testing with a MacBook
This section covers testing dhcplease from a Windows host with a MacBook as the DHCP client.
Hardware Setup
Connect the MacBook to your Windows machine:
- Direct connection: USB-C/Thunderbolt to Ethernet adapter on MacBook, Ethernet cable to Windows
- Through a switch: Both machines connected to an isolated switch (no other DHCP server)
Find the MacBook's Ethernet Interface
On the MacBook, open Terminal and run:
networksetup -listallhardwareports
Look for your USB/Thunderbolt Ethernet adapter. It will show something like:
Hardware Port: USB 10/100/1000 LAN
Device: en6
Ethernet Address: aa:bb:cc:dd:ee:ff
Note the device name (e.g., en6).
Request a DHCP Lease
With the Windows host running dhcplease, run on the MacBook:
# Release any existing lease and request a new one
sudo ipconfig set en6 DHCP
# Or force a renewal
sudo ipconfig set en6 BOOTP && sudo ipconfig set en6 DHCP
Verify the Lease
Check the assigned IP on the MacBook:
ipconfig getifaddr en6
View full DHCP information:
ipconfig getpacket en6
This shows the server IP, lease time, DNS servers, and other options received.
Monitor DHCP Traffic (Optional)
To see the raw DHCP packets on macOS:
sudo tcpdump -i en6 -n port 67 or port 68
Expected Output
On the Windows host running dhcplease -l debug run, you should see:
INFO DISCOVER from aa:bb:cc:dd:ee:ff (0.0.0.0:68)
INFO OFFER 192.168.1.100 to aa:bb:cc:dd:ee:ff
INFO REQUEST from aa:bb:cc:dd:ee:ff (0.0.0.0:68)
INFO ACK 192.168.1.100 to aa:bb:cc:dd:ee:ff (lease: 3600 seconds)
Troubleshooting macOS
| Issue | Solution |
|---|---|
en6 not found |
Run networksetup -listallhardwareports to find correct interface |
| No IP assigned | Check Windows firewall, verify cable connection |
Gets 169.254.x.x |
DHCP failed; check dhcplease is running and interface_index is correct |
| Adapter not recognized | Try unplugging and replugging the USB-Ethernet adapter |
Testing with a Linux Client
Find the Ethernet Interface
ip link show
Look for your Ethernet interface (commonly eth0, enp0s3, or enpXsY for USB adapters).
Request a DHCP Lease
Using dhclient (Debian/Ubuntu):
# Release existing lease
sudo dhclient -r eth0
# Request new lease
sudo dhclient -v eth0
The -v flag shows verbose output including the DHCP exchange.
Using dhcpcd (Arch, Raspberry Pi OS):
# Release and renew
sudo dhcpcd -k eth0
sudo dhcpcd eth0
Using NetworkManager (most desktop distros):
# Restart the connection
nmcli connection down "Wired connection 1"
nmcli connection up "Wired connection 1"
# Or force DHCP renewal
nmcli device reapply eth0
Using systemd-networkd:
sudo networkctl renew eth0
Verify the Lease
# Show IP address
ip addr show eth0
# Show full lease info (dhclient)
cat /var/lib/dhcp/dhclient.leases
# Show lease info (dhcpcd)
cat /var/lib/dhcpcd/dhcpcd-eth0.lease
Monitor DHCP Traffic
sudo tcpdump -i eth0 -n port 67 or port 68
Troubleshooting Linux
| Issue | Solution |
|---|---|
| Interface not found | Check ip link show, interface may have different name |
| Permission denied | Run dhclient/dhcpcd with sudo |
Gets 169.254.x.x |
Link-local fallback means DHCP failed; check server is running |
| NetworkManager conflicts | Stop NetworkManager: sudo systemctl stop NetworkManager |
| No response | Verify firewall on Windows host allows UDP 67/68 |
Testing with a Raspberry Pi
Raspberry Pi makes an excellent isolated test client:
- Connect Pi to Windows host via Ethernet (direct or through switch)
- Boot the Pi (it will automatically try DHCP)
- Or manually trigger:
sudo dhcpcd -n eth0
For headless setup, monitor dhcplease output to see when the Pi gets an IP, then SSH to that address.
Verifying Leases on the Server
dhcplease list-leases
Using as a Library
use dhcplease::{Config, DhcpServer};
#[tokio::main]
async fn main() -> dhcplease::Result<()> {
let config = Config::load_or_create("config.json").await?;
let server = DhcpServer::new(config).await?;
server.run().await
}
See the API documentation for details on programmatic configuration.
License
MIT OR Apache-2.0
Dependencies
~12–30MB
~314K SLoC