ea8eb2b5f9
Python 3 + Scapy DHCP server with: - Full DORA handshake (DISCOVER/OFFER/REQUEST/ACK) - Multi-scope support with DHCP relay - Static MAC-to-IP bindings - Lease persistence (JSON) with auto-prune - NACK for invalid requests - Flask web management panel (dark theme, live refresh) - Admin-privileged scapy sniff on UDP 67
224 lines
7.0 KiB
Markdown
224 lines
7.0 KiB
Markdown
# WinDHCPD — Windows 原生 DHCP 服务器
|
||
|
||
基于 **Python 3 + Scapy** 实现的轻量级 DHCP 服务器,可在 Windows 上以管理员权限运行。
|
||
|
||
## 功能特性
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| **完整 DORA 握手** | DISCOVER → OFFER → REQUEST → ACK,符合 RFC 2131 |
|
||
| **多作用域** | 支持多个网段(/24),可通过 DHCP Relay 跨网段服务 |
|
||
| **静态绑定** | MAC → IP 固定分配,优先于动态分配 |
|
||
| **租约管理** | 内存索引 + JSON 持久化,支持释放、续租、清理过期 |
|
||
| **Web 管理面板** | Flask 面板,实时查看租约、作用域、统计(端口 8080) |
|
||
| **NACK 拒绝** | 对不合法请求返回 NACK |
|
||
| **日志记录** | 控制台 + 文件双输出 |
|
||
|
||
## 架构
|
||
|
||
```
|
||
win-dhcpd/
|
||
├── run.py # 入口脚本
|
||
├── dhcpd.yaml.example # 配置文件示例
|
||
├── requirements.txt # Python 依赖
|
||
├── dhcpd/
|
||
│ ├── config.py # 配置加载与校验
|
||
│ ├── lease.py # 租约数据库(内存 + JSON 持久化)
|
||
│ ├── parser.py # DHCP 包解析与构建(Scapy)
|
||
│ ├── server.py # DHCP 服务器主循环(Sniffer + 响应)
|
||
│ └── web/
|
||
│ ├── app.py # Flask Web 面板
|
||
│ └── templates/
|
||
│ └── index.html # 管理界面(深色主题,自动刷新)
|
||
```
|
||
|
||
## 前置条件
|
||
|
||
### 1. 安装 Python 3.8+
|
||
确保 Windows 上已安装 Python 3.8 或更高版本。
|
||
|
||
### 2. 安装 Npcap(scapy 依赖)
|
||
Scapy 在 Windows 上需要 Npcap 来捕获和发送网络数据包:
|
||
- 下载:https://nmap.org/npcap/
|
||
- 安装时 **必须勾选 "Install Npcap in WinPcap API-compatible Mode"**
|
||
- 默认安装即可
|
||
|
||
### 3. 安装依赖
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
依赖包:
|
||
- **scapy** (≥2.5) — 网络数据包捕获与发送
|
||
- **flask** (≥3.0) — Web 管理面板
|
||
- **pyyaml** (≥6.0) — 配置文件解析
|
||
|
||
## 快速开始
|
||
|
||
### 1. 创建配置文件
|
||
```bash
|
||
copy dhcpd.yaml.example dhcpd.yaml
|
||
```
|
||
编辑 `dhcpd.yaml`,修改作用域(网段、地址池、网关、DNS)为实际网络参数。
|
||
|
||
**关键参数说明:**
|
||
|
||
| 参数 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `server.ip` | DHCP 服务器自身 IP | `192.168.1.1` |
|
||
| `scopes[].network` | 网段(CIDR) | `192.168.1.0/24` |
|
||
| `scopes[].start_ip` | 地址池起始 | `192.168.1.100` |
|
||
| `scopes[].end_ip` | 地址池结束 | `192.168.1.200` |
|
||
| `scopes[].router` | 默认网关 | `192.168.1.1` |
|
||
| `scopes[].dns_servers` | DNS 服务器列表 | `[8.8.8.8, 114.114.114.114]` |
|
||
| `scopes[].lease_seconds` | 租期(秒) | `86400` (24小时) |
|
||
| `bindings[].mac` | 静态绑定 MAC | `aa:bb:cc:dd:ee:01` |
|
||
| `bindings[].ip` | 静态绑定 IP | `192.168.1.10` |
|
||
|
||
### 2. 以管理员身份运行
|
||
**必须使用管理员权限**(捕获端口 67 的广播数据包需要特权):
|
||
|
||
```bash
|
||
# Windows 命令行
|
||
python run.py
|
||
|
||
# 指定配置文件
|
||
python run.py dhcpd.yaml
|
||
|
||
# 指定监听网卡
|
||
python run.py dhcpd.yaml -i "以太网"
|
||
|
||
# 禁用 Web 面板
|
||
python run.py dhcpd.yaml --no-web
|
||
|
||
# 指定 Web 端口
|
||
python run.py dhcpd.yaml --web-port 9000
|
||
```
|
||
|
||
### 3. 访问 Web 面板
|
||
打开浏览器访问:
|
||
```
|
||
http://localhost:8080
|
||
```
|
||
|
||
## 配置文件详解
|
||
|
||
```yaml
|
||
# ── 服务器设置 ──
|
||
server:
|
||
ip: "192.168.1.1" # DHCP 服务器 IP(响应包中的 server_id)
|
||
|
||
# ── 作用域 ──
|
||
scopes:
|
||
- name: "Office-LAN" # 作用域名称
|
||
network: "192.168.1.0/24"
|
||
start_ip: "192.168.1.100"
|
||
end_ip: "192.168.1.200"
|
||
subnet_mask: "255.255.255.0"
|
||
router: "192.168.1.1"
|
||
dns_servers:
|
||
- "8.8.8.8"
|
||
- "114.114.114.114"
|
||
lease_seconds: 86400 # 租期 24 小时
|
||
domain: "local"
|
||
enable: true
|
||
|
||
# ── 静态绑定 ──
|
||
bindings:
|
||
- mac: "aa:bb:cc:dd:ee:01"
|
||
ip: "192.168.1.10"
|
||
scope: "Office-LAN"
|
||
hostname: "printer"
|
||
|
||
# ── 其他 ──
|
||
log_file: "dhcpd.log"
|
||
log_level: "INFO" # DEBUG / INFO / WARNING / ERROR
|
||
web_host: "0.0.0.0"
|
||
web_port: 8080
|
||
# interface: "以太网" # 可选:指定监听网卡
|
||
```
|
||
|
||
## 网络拓扑要求
|
||
|
||
### 场景 1:单机 DHCP 服务器
|
||
```
|
||
客户端 ────── 交换机/集线器 ────── WinDHCPD (192.168.1.1)
|
||
↑ ↑
|
||
│ 广播 DISCOVER │ 响应 OFFER/ACK
|
||
└────────────────────────────────┘
|
||
```
|
||
- WinDHCPD 所在的网卡必须是 **混杂模式** 或连接在 Hub/支持组播的交换机上
|
||
- 同一网段 **只能有一个 DHCP 服务器**
|
||
|
||
### 场景 2:跨网段(DHCP Relay)
|
||
```
|
||
子网 A 客户端 ── 路由器(Relay) ── WinDHCPD (192.168.1.1)
|
||
↑
|
||
giaddr = 10.0.2.1
|
||
```
|
||
- 路由器需配置 `ip helper-address <WinDHCPD_IP>`
|
||
- WinDHCPD 通过 `giaddr` 字段确定客户端所在网段,选择对应作用域
|
||
|
||
## 工作原理
|
||
|
||
```
|
||
客户端 服务器
|
||
│ │
|
||
│ DHCPDISCOVER (广播) │
|
||
│ ─────────────────────► │
|
||
│ │ 分配 IP → 发 OFFER
|
||
│ DHCPOFFER (广播) │
|
||
│ ◄────────────────────── │
|
||
│ │
|
||
│ DHCPREQUEST (广播) │
|
||
│ ─────────────────────► │
|
||
│ │ 更新租约 → 发 ACK
|
||
│ DHCPACK (广播) │
|
||
│ ◄────────────────────── │
|
||
│ │
|
||
│ DHCPRELEASE │
|
||
│ ─────────────────────► │ 释放 IP
|
||
```
|
||
|
||
## 停止服务器
|
||
|
||
- 控制台:按 `Ctrl+C`
|
||
- 任务管理器:结束 `python.exe` 进程
|
||
|
||
租约数据自动保存到 `leases.db`,下次启动时自动加载。
|
||
|
||
## 常见问题
|
||
|
||
### Q: 客户端获取不到 IP
|
||
1. **必须以管理员身份运行** — 端口 67 需要特权
|
||
2. **检查 Npcap 是否安装** — 且勾选了 WinPcap 兼容模式
|
||
3. **确认网卡选择** — 用 `-i "网卡名"` 指定正确的网卡
|
||
4. **同一网段不能有另一个 DHCP 服务器** — Windows 自带的 DHCP Server 服务必须关闭
|
||
|
||
### Q: 如何查看日志
|
||
```bash
|
||
# 实时查看
|
||
type dhcpd.log
|
||
|
||
# 修改日志级别(配置文件中)
|
||
log_level: "DEBUG"
|
||
```
|
||
|
||
### Q: 支持 IPv6 吗
|
||
不支持。本服务器仅处理 IPv4 DHCP。
|
||
|
||
### Q: 支持 DHCPv6 吗
|
||
不支持。如需 IPv6 分配,请配置 SLAAC 或单独的 DHCPv6 服务器。
|
||
|
||
## 安全注意事项
|
||
|
||
⚠️ **不要在生产网络中直接部署**,除非你完全了解后果:
|
||
|
||
1. **单点故障** — 这台机器挂了,所有 DHCP 客户端都无法获取 IP
|
||
2. **冲突风险** — 同一网段有两个 DHCP 服务器会导致客户端随机获取 IP
|
||
3. **端口监听** — 服务器会监听 UDP 67 的广播包,其他设备可能无法响应
|
||
4. **建议** — 先用隔离网络测试,确认无误后再部署
|
||
|
||
## 许可
|
||
|
||
MIT License
|