dhcplease 0.1.0

A developer-grade DHCP server for Windows
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# dhcplease


[<img alt="github" src="https://img.shields.io/badge/github-matthewjberger/dhcplease-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/matthewjberger/dhcplease)
[<img alt="crates.io" src="https://img.shields.io/crates/v/dhcplease.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/dhcplease)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-dhcplease-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/dhcplease)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/matthewjberger/dhcplease/rust.yml?branch=main&style=for-the-badge" height="20">](https://github.com/matthewjberger/dhcplease/actions?query=branch%3Amain)

A DHCP server for Windows, 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

## Quick Start


Add this to your `Cargo.toml`:

```toml
[dependencies]
dhcplease = "0.1.0"
```

## Installation


```bash
cargo install dhcplease
```

Or build from source:

```bash
cargo install --path .
```

## Usage


### Run the server


```bash
# 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


```bash
# 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:

```json
{
  "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:

```json
{
  "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


1. **Windows host machine** running dhcplease
2. **A second device** (laptop, Raspberry Pi, etc.) that needs an IP address
3. **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:

```powershell
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:

```powershell
# 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


```powershell
Set-NetIPInterface -InterfaceIndex <InterfaceIndex> -Dhcp Disabled
```

#### 4. Configure dhcplease


Create or edit `config.json`, replacing `12` with your actual interface index:

```json
{
  "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:

```powershell
Set-NetFirewallProfile -Profile Private -Enabled False
```

Or add specific rules for DHCP:

```powershell
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


```powershell
# 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:**
```cmd
ipconfig /release
ipconfig /renew
```

**Linux client:**
```bash
sudo dhclient -r eth0 && sudo dhclient eth0
```

**macOS client (see detailed section below):**
```bash
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:

```bash
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:

```bash
# 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:

```bash
ipconfig getifaddr en6
```

View full DHCP information:

```bash
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:

```bash
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


```bash
ip link show
```

Look for your Ethernet interface (commonly `eth0`, `enp0s3`, or `enpXsY` for USB adapters).

#### Request a DHCP Lease


**Using dhclient (Debian/Ubuntu):**

```bash
# 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):**

```bash
# Release and renew

sudo dhcpcd -k eth0
sudo dhcpcd eth0
```

**Using NetworkManager (most desktop distros):**

```bash
# 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:**

```bash
sudo networkctl renew eth0
```

#### Verify the Lease


```bash
# 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


```bash
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:

1. Connect Pi to Windows host via Ethernet (direct or through switch)
2. Boot the Pi (it will automatically try DHCP)
3. 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


```powershell
dhcplease list-leases
```

## Using as a Library


```rust
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](https://docs.rs/dhcplease) for details on programmatic configuration.

## License


MIT OR Apache-2.0