DHCP Web Manager 教程:Flask + ISC DHCP 的 Web 管理实战
局域网里设备一多,DHCP 就成了绕不开的基础服务:谁拿了哪个 IP、打印机和服务器的固定地址怎么管、地址池还剩多少,这些问题靠手改 dhcpd.conf 既慢又容易出错。DHCP Web Manager 就是为了解决这个问题:一个用 Flask 写的轻量 Web 界面,底层直接复用系统自带的 ISC DHCP(isc-dhcp-server),不引入额外的 daemon,所有配置都在浏览器里点几下完成,改配置时自动备份、语法检查、热重载。本文从安装部署到生产运维完整讲一遍。
一、项目简介
DHCP Web Manager(仓库 AI-Agent/dhcp-service)是一个面向 Linux 服务器的 DHCP 管理面板。它不自己实现 DHCP 协议,而是做 isc-dhcp-server 的“遥控器”:你在 Web 界面上的每一次增删改,最终都会落到系统文件 /etc/dhcp/dhcpd.conf,再由真正的 dhcpd 进程对外提供服务。这种设计的优点很明显:DHCP 服务本身的稳定性由久经考验的 ISC DHCP 保证,Web 层只负责“让人少敲命令”。
项目结构非常精简,核心只有两个 Python 文件:app.py 是 Flask 主程序(路由、登录、操作编排),dhcp_ops.py 负责 dhcpd.conf 的读写、租约文件解析和服务控制;用户和操作日志存在 SQLite(instance/dhcp.db),前端是 Jinja2 模板加一份原生 CSS,没有任何前端框架依赖。
dhcp-service/
├── app.py # Flask 主程序
├── dhcp_ops.py # dhcpd.conf 读写 + leases 解析 + 服务控制
├── requirements.txt
├── instance/dhcp.db # SQLite(用户、日志)
├── backups/ # dhcpd.conf 自动备份(最多保留 20 份)
├── templates/ # Jinja2 模板
└── static/style.css
二、功能特性一览
| 功能 | 说明 |
|---|---|
| 子网管理 | 增删改 subnet:网段、掩码、网关、DNS、地址池、租期 |
| 静态绑定 | host 绑定:MAC + 固定 IP + 主机名,打印机、服务器固定地址就靠它 |
| 监听接口配置 | 管理 /etc/default/isc-dhcp-server 里的 INTERFACESv4,指定 dhcpd 监听哪块网卡 |
| 租约查询 | 按 IP / MAC / 主机名搜索租约,附带地址池使用率 |
| 网络扫描 | arp-scan 扫描网段内活跃设备,标记“静态绑定 / 动态分配 / 未知”三类 |
| 服务控制 | 服务状态查看、reload / restart、dhcpd -t 语法检查 |
| 操作日志 | 记录谁在什么时间做了什么操作,方便事后审计 |
| 账号管理 | 用户名密码登录,支持在线改密 |
| 变更安全流程 | 所有写操作自动走“写文件 → 备份 → 语法检查 → 重载服务”流程 |
其中最值得单独拎出来说的是“变更安全流程”。很多手写的管理脚本是直接改文件然后重启服务,配错了整个 DHCP 就挂了,局域网设备拿不到地址。这个项目的每一次写操作都会先备份、再用 dhcpd -t 做语法检查,检查失败就不重载,服务继续按旧配置跑,操作日志里还会记下失败原因,等你修正后重试。
三、适用场景
- 办公室 / 实验室局域网:几十到几百台终端,需要统一分配地址,又不想每次都 SSH 上去改配置文件。
- 测试网络与机房网段:频繁增删测试设备、改地址池,Web 界面比改文件快得多,而且每次改动都有日志可查。
- 固定设备管理:打印机、NAS、摄像头、工控机等需要固定 IP 的设备,用静态绑定集中管理,换人接手也不怕丢配置。
- 多网段管理:一台 DHCP 服务器带多个子网时,在界面里一眼就能看到每个网段的地址池使用率,哪个网段快满了提前发现。
- 不适合的场景:需要 DHCP failover(主备)、动态 DNS 联动、或超大规模(上万客户端)高可用部署的场合,这个轻量工具就不够用了,应当考虑 Kea DHCP 或商业方案。
四、技术架构与关键设计
理解下面四个设计点,后面用起来心里才有底,出了问题也知道从哪查。
4.1 只动“托管区段”,不动你的原有配置
程序改写 /etc/dhcp/dhcpd.conf 时,并不是整文件重写,而是只动它自己管理的区段——通过一对标记注释来界定:
# 原有的通用配置(option domain-name 等)保持不变
option domain-name "example.org";
default-lease-time 600;
max-lease-time 7200;
# >>> DHCP-WEB-MANAGER BEGIN >>>
authoritative;
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
option domain-name-servers 192.168.1.1, 8.8.8.8;
option broadcast-address 192.168.1.255;
default-lease-time 600;
max-lease-time 7200;
}
host printer {
hardware ethernet aa:bb:cc:dd:ee:01;
fixed-address 192.168.1.50;
}
# <<< DHCP-WEB-MANAGER END <<<
标记之外的 option、注释、以及程序不认识的手工配置都会原样保留。每次改动前还会把旧文件备份到 backups/dhcpd.conf.<时间戳>.bak,最多保留 20 份,超出自动清理最旧的。
4.2 变更流程:备份 → 检查 → 重载
所有写操作都走同一个流程(代码在 app.py 的 apply_and_reload):读出现有 subnets/hosts → 追加或替换 → 生成新的 conf 文本 → 先备份到 backups/ → 写到 /etc/dhcp/dhcpd.conf → 跑 dhcpd -t 语法检查 → 通过则 systemctl reload isc-dhcp-server 热重载(不丢现有连接)。语法检查失败时不会重载,服务继续按旧配置运行,操作日志记录失败原因。
4.3 接口绑定变更必须重启
/etc/default/isc-dhcp-server 里的 INTERFACESv4="enp0s31f6" 决定 dhcpd 监听哪块网卡。改子网和静态绑定走 reload 就行,但改监听接口必须 restart,因为接口绑定不支持热加载,程序在保存接口配置时会直接重启服务。
4.4 租约解析:直接读 leases 文件
租约查询不经过 dhcpd 的管理接口,而是直接读 /var/lib/dhcp/dhcpd.leases 并用正则解析 lease 块。有一个小细节:dhcpd 每次写 leases 文件时是先写临时文件再 rename 替换,所以解析瞬间可能读到空内容——这时前端会显示 0 条,不报错,刷新一次就好,不必惊慌。
五、环境准备与安装
部署目标是一台 Debian / Ubuntu 服务器(项目 README 按 Debian 系写的,isc-dhcp-server 的包名和配置文件路径都是 Debian 布局;RHEL 系的包名和路径不同,见文末注意事项)。开始之前确认三件事:服务器有 root 权限(程序必须以 root 运行,因为要写 /etc/dhcp/dhcpd.conf 并控制 systemd 服务)、服务器网卡已接入要服务的局域网、UDP 67 端口没有被其他 DHCP 服务占用。
# 1. 安装后端 DHCP 服务 + 扫描工具
apt-get update
apt-get install -y isc-dhcp-server arp-scan dhcping
# 2. 拉代码
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dhcp-service.git
cd dhcp-service
# 3. 安装 Python 依赖(Debian 12+ 需要 --break-system-packages,或用 venv)
pip3 install -r requirements.txt --break-system-packages
三个系统包各有用处:isc-dhcp-server 是真正的 DHCP 服务端;arp-scan 供“网络扫描”功能发现网段内的活跃设备;dhcping 可用于从命令行测试 DHCP 服务是否正常响应。装完先别急着启动 Web,先确认 isc-dhcp-server 本身能跑起来:
# 先看 dhcpd 能不能通过语法检查(刚装好时默认配置一般是空的,能通过)
dhcpd -t
# 确认没有其他进程占着 UDP 67
ss -unlp | grep ':67'
# 如果有输出,先停掉占用的服务(比如 dnsmasq、其他 DHCP),否则后面会冲突
六、启动:开发模式与生产模式
开发模式适合第一次验证,一条命令就起来,监听 0.0.0.0:5100:
# 开发模式(前台运行,Ctrl+C 停止)
python3 app.py
# 换个端口启动(如果 5100 被占了,先确认 app.py 是否支持改端口,
# 不支持的话用反代或 systemd 的端口映射,见第十二节)
# 验证服务是否起来
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:5100/
生产环境不要用 python3 app.py 裸跑,Flask 自带开发服务器是单进程、没经过生产加固,推荐用 gunicorn:
# 生产模式(2 个 worker,监听 0.0.0.0:5100)
gunicorn --workers 2 --bind 0.0.0.0:5100 'app:app'
# 后台常驻则交给 systemd(完整 unit 见第十二节)
systemctl status isc-dhcp-server
systemctl status dhcp-web-manager
七、首次登录与账号安全
浏览器打开 http://<服务器IP>:5100,默认账号是 admin / admin。第一次登录后立刻去「改密」页面改掉默认密码——这是最重要的一步:这个面板能改 DHCP 配置、重启服务,默认密码等于把局域网的地址分配权敞开给任何人。另外注意两点:
- 不要把 5100 端口暴露到公网。面板本身只有用户名密码一道防线,正确做法是只在内网访问,或者前面加 Nginx 反代 + HTTPS + IP 白名单。
instance/dhcp.db不要提交到 git。这个 SQLite 文件里存着用户账号(密码哈希)和操作日志,提交上去等于把账号信息公开了,项目自带的.gitignore应当已排除它,部署时也确认一下。
八、配置详解:子网、静态绑定与监听接口
登录后主要用四个页面:子网(Subnets)、静态绑定(Hosts)、租约(Leases)、网络扫描(Scan),外加配置(Config)和日志(Logs)。
| 子网参数 | 含义 | 示例 |
|---|---|---|
| 网段 / 掩码 | 这个子网管哪个网段 | 192.168.1.0 / 255.255.255.0 |
| 地址池(range) | 动态分配的地址范围 | 192.168.1.100 – 192.168.1.200 |
| 网关(option routers) | 下发给客户端的默认网关 | 192.168.1.1 |
| DNS(domain-name-servers) | 下发给客户端的 DNS 服务器,可填多个 | 192.168.1.1, 8.8.8.8 |
| 默认租期 / 最大租期 | 租约时长,单位秒 | 600 / 7200 |
- 新增子网:按上表填好网段、掩码、网关、DNS 和地址池,保存后程序自动写配置、备份、语法检查、reload,页面上能看到新子网生效。地址池不要包含网关地址和网段内的固定设备地址,免得动态分配撞上静态绑定。
- 静态绑定:填主机名、MAC 地址、固定 IP。MAC 务必写对,大小写不敏感但分隔符建议用冒号(
aa:bb:cc:dd:ee:01)。绑好的设备每次拿到的都是同一个 IP,适合打印机、NAS、摄像头。 - 监听接口:在配置页改
INTERFACESv4,比如服务器有enp0s31f6(接办公网)和enp0s31f7(接测试网),只想在测试网提供 DHCP,就填enp0s31f7。注意保存后服务会 restart(不是 reload),正在租约续期的客户端会重新申请一次,一般无感,但最好在业务低峰改。 - 租约查询:在租约页按 IP、MAC 或主机名搜索,能看到每个租约的到期时间;配合地址池使用率,可以判断地址池是不是快满了。
- 网络扫描:点扫描后,程序用 arp-scan 扫当前网段,列出所有活跃设备,并标注每个设备是“已有静态绑定”“正在动态租约中”还是“未知设备”。未知设备出现在这里,往往就是有人私自接了设备,或者某个固定设备还没录入绑定。
九、完整示例:从零搭建一个网段的 DHCP 服务
假设场景:办公室新辟了一个测试网段 192.168.10.0/24,网关 192.168.10.1,要给测试设备动态分配 .100–.200,打印机固定到 .50,DNS 用网关加 8.8.8.8。完整流程如下:
# 0. 先确认 dhcpd 本身正常、67 端口空闲
dhcpd -t && echo "config OK"
ss -unlp | grep ':67' || echo "port 67 free"
# 1. 启动 Web 面板(已按第六节部署好的直接访问即可)
# 浏览器打开 http://192.168.10.10:5100,用 admin 登录并改密
# 2. 在 Web 界面完成下面三步(对应生成的配置片段):
# a) 新增子网 192.168.10.0/24,地址池 .100-.200
# b) 新增静态绑定:打印机 MAC aa:bb:cc:dd:ee:01 -> 192.168.10.50
# c) 监听接口填接测试网的那块网卡
# 3. 验证生成的配置(程序写完后可以直接看文件)
cat /etc/dhcp/dhcpd.conf
systemctl status isc-dhcp-server --no-pager
# 4. 从客户端验证:把一台测试机设为自动获取 IP
# 拿到地址后在面板“租约”页搜索它的 MAC,应能看到新租约;
# 再用 dhcping 从服务器本机测一次响应
dhcping -s 192.168.10.10 -h 00:11:22:33:44:55
验证通过后,日常维护就只剩三件事:新设备来了先在“网络扫描”里确认它上线了,再决定是给它动态地址还是做静态绑定;地址池使用率超过 80% 就要考虑扩大地址池或缩短租期;每次大的配置调整前,先去 backups/ 确认自动备份在正常生成。
十、备份与恢复
备份是这个项目最让人放心的地方:每次改动前自动备份,保留最近 20 份。但“自动备份在本地”不等于“备份策略完整”——服务器硬盘坏了,本地备份一样丢。建议把 backups/ 和 instance/dhcp.db 一起纳入你已有的备份计划(rsync 到备份机或定时打包上传)。恢复时手动操作即可:
# 看有哪些自动备份(按时间戳命名)
ls -lt backups/
# 恢复到某一份(以 20260709_120000 这份为例)
cp backups/dhcpd.conf.20260709_120000.bak /etc/dhcp/dhcpd.conf
# 先检查语法,再重载
dhcpd -t && systemctl reload isc-dhcp-server
# 如果连 Web 面板都起不来了,先恢复数据库
# instance/dhcp.db 有备份的话直接拷回来即可(停服务后操作更稳妥)
systemctl stop dhcp-web-manager
cp /path/to/backup/dhcp.db instance/dhcp.db
systemctl start dhcp-web-manager
十一、常见问题
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 保存配置后提示语法检查失败 | 地址池与网段不匹配、IP 格式写错、子网重叠 | 看操作日志里的失败原因,修正后重试;旧配置仍在生效,不用慌 |
| 客户端拿不到 IP | dhcpd 没监听到正确网卡、防火墙拦了 UDP 67、地址池满了 | 先查 INTERFACESv4 是否填对,再查 systemctl status isc-dhcp-server,最后看地址池使用率 |
| 租约页显示 0 条,但客户端明明拿到了地址 | 解析瞬间撞上 dhcpd 正在重写 leases 文件 | 刷新页面即可;持续为 0 再检查 /var/lib/dhcp/dhcpd.leases 是否存在 |
| 改了监听接口后服务起不来 | 填的网卡名不存在,或该网卡没有配 IP | ip addr 确认网卡名和地址,改回正确的接口后重启服务 |
| 扫描不到设备 | arp-scan 需要 root 且只能扫同一二层网络 | 确认程序以 root 运行,确认目标设备与服务器在同一网段、中间没有路由器隔离 |
| 登录提示账号密码错误 | 密码改过后忘了,或数据库被误删重建 | 数据库丢了只能重建账号;平时把 instance/dhcp.db 纳入备份 |
十二、生产部署与运维建议
按项目 README 的惯例,开发仓库放在 /root/dhcp-service/,生产部署在 /root/.openclaw/workspace/dhcp-service/,用 systemd 常驻 gunicorn:
# 1. 把代码复制到生产目录
mkdir -p /root/.openclaw/workspace/dhcp-service
cp -r /root/dhcp-service/{app.py,dhcp_ops.py,requirements.txt,templates,static} \
/root/.openclaw/workspace/dhcp-service/
# 2. systemd unit
cat > /etc/systemd/system/dhcp-web-manager.service << 'EOF'
[Unit]
Description=DHCP Web Manager (Flask)
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/.openclaw/workspace/dhcp-service
ExecStart=/usr/bin/gunicorn --workers 2 --bind 0.0.0.0:5100 'app:app'
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now dhcp-web-manager
systemctl status dhcp-web-manager --no-pager
- 必须以 root 运行:程序要写
/etc/dhcp/dhcpd.conf、控制 systemd 服务,普通用户权限不够。如果安全策略不允许 root 跑 Web 服务,可以考虑前面加 Nginx 反代、把面板只绑定在内网地址上,但服务本身仍需 root。 - 改 subnet/host 走 reload,改接口走 restart:reload 不丢现有租约,适合白天操作;改监听接口会重启服务,尽量安排在低峰。
- 语法检查失败不会回滚配置文件,但也不会重载:失败后磁盘上的
dhcpd.conf可能已经是新内容(但未生效),修正配置后重新保存一次即可,不要手动去改文件再让程序覆盖。 - 定期看操作日志:谁改了哪个子网、什么时候改的,出了问题先查日志再查配置。
- RHEL 系注意:README 按 Debian/Ubuntu 写,RHEL/CentOS 上包名是
dhcp-server、配置文件在/etc/dhcp/dhcpd.conf(路径相同)、服务名是dhcpd而不是isc-dhcp-server,部署前要按发行版调整包名和服务名。
总结:DHCP Web Manager 的定位很清楚——不重复造 DHCP 服务器的轮子,而是把 ISC DHCP 久经考验的稳定性和 Web 操作的便利性拼在一起。对中小规模局域网来说,它用最小的代价解决了“改配置靠手、查租约靠翻文件、改错了全网断网”这三个痛点。按本文的流程部署、改掉默认密码、把备份纳入已有备份计划,就可以放心交给日常运维用了。



