Version 2 Added config file and IPV6 support and benchmarks with massive performance uplifts.

This commit is contained in:
pj1234678
2026-09-24 15:25:57 -07:00
committed by GitHub
parent 4ec95e9f84
commit 589351f6da
38 changed files with 19067 additions and 26 deletions
+78 -26
View File
@@ -16,6 +16,8 @@ A simple and zero-dependency DHCP server written in Rust, with credit to Richard
- Fast and efficient.
- Cross-platform support and cross-compilation.
- Customizable Leases File: Support for a "leases" file that allows you to define permanent leases, ensuring clients always receive the same IP address.
- Managed DHCPv6 range: stateful DHCPv6 (RFC 8415) address allocation from a configured IPv6 pool, with DUID-based leases.
- Single `dhcp.conf` file: every IPv4 and IPv6 setting in one place, no recompiling to reconfigure.
## Table of Contents
@@ -35,47 +37,81 @@ A simple and zero-dependency DHCP server written in Rust, with credit to Richard
git clone https://github.com/pj1234678/RustyDHCP.git
```
3. Build the server:
3. Build the server:
```bash
cd RustyDHCP
cargo build --release --example server
```
```bash
cd RustyDHCP
cargo build --release
```
## Usage
1. Start the DHCP server:
1. Start the DHCP server (no arguments; it always reads `./dhcp.conf`):
```bash
sudo ./target/release/examples/server
```
```bash
sudo ./target/release/dhcp4r
```
The server will listen on the default DHCP ports (67 and 68) and start serving DHCP requests.
The server will listen on the configured DHCP ports (67 for IPv4, 547 for IPv6 when enabled) and start serving DHCP requests. Every DHCP Request is also printed monitor-style (`timestamp\tmac\tip\thostname\tOnline`).
2. Make DHCP requests from clients, and the server will respond with IP addresses and other configuration details.
### Diagnosing clients
Every handshake step is logged: `DISCOVER -> OFFER/ACK`, `REQUEST -> ACK/NAK (reason)`, `RELEASE -> freed`, pool-exhaustion (`no reply ... used`), ignored packets (`unsupported message type`, `no client ID`, `server_id mismatch`), and malformed datagrams (`ignoring malformed ...: reason`). If a client (e.g. an IoT thermostat) won't connect while dnsmasq works, watch for:
- `INFORM ... ignored` — this server does not answer DHCPINFORM; dnsmasq does.
- `-> NAK ... outside pool range` — the client asks for an address outside `ip_start + lease_num`.
- `no reply (pool exhausted ...)` — all leases are handed out.
- `ignoring malformed ...` — the client's packet fails to decode at all.
- `-> OFFER ... to [192.168.2.255:68, 255.255.255.255:68]` but the client
never sends Request — check for a unicast copy in the list. With the
broadcast flag clear the server unicasts a copy to the offered address
(`..., 192.168.2.x:68`, RFC 2131 §4.1, like dnsmasq) alongside the
broadcasts, for clients/APs that drop every broadcast flavor. If even the
unicast copy never arrives, suspect AP broadcast handling or firewall, and
compare a dnsmasq capture (`tcpdump -i <lan> -e -vvv`).
## Configuration
To configure the server, edit the following fields in the `examples/server.rs` file:
All settings live in a single `dhcp.conf` file using a minimal `key = value` format (no dependencies to parse it). Blank lines and lines starting with `#` or `;` are ignored, keys are case-insensitive, a trailing ` # comment` is stripped, duplicate keys use the last value, and unknown keys are ignored. Any malformed line or invalid value fails loudly with the key and line number. If the file is missing, the server warns and uses the built-in defaults below.
```rust
// Server configuration
const SERVER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1);
const IP_START: [u8; 4] = [192, 168, 2, 2];
const SUBNET_MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
const DNS_IPS: [Ipv4Addr; 1] = [
// Google DNS servers
Ipv4Addr::new(8, 8, 8, 8),
];
const ROUTER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1);
const BROADCAST_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 255);
const LEASE_DURATION_SECS: u32 = 86400;
const LEASE_NUM: u32 = 252;
```
| Key | Default | Description |
| --- | ------- | ----------- |
| `server_ip` | `192.168.2.1` | This server's address (DHCP option 54) |
| `ip_start` | `192.168.2.2` | First address of the managed IPv4 pool |
| `lease_num` | `252` | IPv4 pool size (covers `.2` .. `.253`) |
| `subnet_mask` | `255.255.255.0` | Advertised subnet mask (option 1) |
| `router_ip` | `192.168.2.1` | Advertised gateway (option 3) |
| `dns_ips` | `8.8.8.8` | Advertised DNS servers, comma-separated (option 6) |
| `enable_ntp` | `false` | Master switch for advertising NTP servers (option 42) |
| `ntp_ips` | _(empty)_ | Advertised NTP servers, comma-separated (option 42); sent only when `enable_ntp` is true and the list is non-empty |
| `enable_rapid_commit` | `false` | Master switch for DHCP Rapid Commit (option 80 for IPv4, option 14 for IPv6) |
| `broadcast_ip` | `192.168.2.255` | Where broadcast replies are sent; comma-separated list fans each reply out to every address (e.g. `192.168.2.255, 255.255.255.255`) |
| `extra_broadcast_ips` | _(empty)_ | Additional broadcast destinations, comma-separated; appended to `broadcast_ip` |
| `lease_duration_secs` | `86400` | IPv4 lease lifetime (option 51) |
| `leases_file` | `leases` | Permanent `mac,ip` reservations |
| `listen_addr` | `0.0.0.0:67` | Address to bind for DHCPv4 |
| `enable_dhcpv6` | `false` | Master switch for the DHCPv6 service |
| `server_duid` | `00:03:00:01:02:00:5e:aa:bb:cc` | Server DUID for SERVERID (1..=128 hex bytes) |
| `ipv6_start` | `fd00::100` | First address of the managed IPv6 pool |
| `ipv6_lease_num` | `1000` | IPv6 pool size |
| `ipv6_dns` | `2001:4860:4860::8888` | Advertised DNS servers, comma-separated (option 23) |
| `ipv6_lease_duration_secs` | `86400` | IPv6 preferred + valid lifetimes (IAADDR) |
| `leases_file_v6` | `leases6` | Permanent `duid-hex,ipv6` reservations |
| `listen_addr_v6` | `[::]:547` | Address to bind for DHCPv6 |
You can customize these configuration parameters according to your network requirements.
See the shipped `dhcp.conf` for a fully commented example. Boolean values accept `true/false`, `yes/no`, `on/off`, `1/0` (any case).
### DHCPv6 managed range (RFC 8415)
Set `enable_dhcpv6 = true` and configure `ipv6_start` / `ipv6_lease_num` to serve stateful DHCPv6 on port 547 alongside DHCPv4:
- **Solicit → Advertise**: offers the client's current address, else the first free address of the managed range (round-robin).
- **Request / Renew / Rebind → Reply**: commits the requested IA_NA address; T1/T2 are set to half / seven-eighths of the lease lifetime.
- **Release / Decline → frees** the client's lease (only when the Server ID matches this server).
- Pool exhaustion (or any failure) gets a Reply with Status Code `NoAddrsAvail` (2).
Clients are identified by their exact DUID bytes and leases work like the IPv4 side, with one deliberate difference: infinite (file-reserved) IPv6 leases are never stealable. Malformed lines in `leases6` are skipped without killing the server.
To create a "leases" file with the example permanent lease, you can manually create a file named "leases" in the same directory as the compiled program with the following content:
@@ -83,6 +119,22 @@ f4:5c:19:af:96:8d,192.168.2.90
This lease format specifies the MAC address and the corresponding IP address for the client. The DHCP server will read this file to assign permanent leases based on its contents.
### Client host names (DHCP option 12)
When a client announces a host name (option 12, RFC 2132 §3.14), the server records it against the lease: it is stored when the lease is created, refreshed whenever a later Discover or Request carries a name, and left untouched by packets that omit it (so renewals without the option don't flap the recorded name). Releasing the lease drops the name with it. Malformed (non-UTF-8) names never reach the server logic — the decoder already rejects them. The monitor prints the recorded name per Request (`-` when the client sent none):
```text
2026-01-01T12:00:00 f4:5c:19:af:96:8d 192.168.2.90 laptop Online
```
The IPv6 equivalent is a "leases6" file with `duid-hex,ipv6` lines, for example:
00:03:00:01:02:00:5e:aa:bb:cc,fd00::100
### DHCP Rapid Commit
Set `enable_rapid_commit = true` to allow the 2-message exchange: a Discover (IPv4, RFC 4039 option 80) or Solicit (IPv6, RFC 8415 option 14) carrying the rapid-commit option is answered immediately with an Ack/Reply that commits the lease, skipping Offer/Advertise. Without both the flag and the option on the wire, the server uses the legacy 4-message flow. Rapid-commit selection is Discover-style (current lease, else round-robin); pool exhaustion behaves like the unanswered Discover/Advertise path.
## Contributions