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 格式写错、子网重叠看操作日志里的失败原因,修正后重试;旧配置仍在生效,不用慌
客户端拿不到 IPdhcpd 没监听到正确网卡、防火墙拦了 UDP 67、地址池满了先查 INTERFACESv4 是否填对,再查 systemctl status isc-dhcp-server,最后看地址池使用率
租约页显示 0 条,但客户端明明拿到了地址解析瞬间撞上 dhcpd 正在重写 leases 文件刷新页面即可;持续为 0 再检查 /var/lib/dhcp/dhcpd.leases 是否存在
改了监听接口后服务起不来填的网卡名不存在,或该网卡没有配 IPip 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 操作的便利性拼在一起。对中小规模局域网来说,它用最小的代价解决了“改配置靠手、查租约靠翻文件、改错了全网断网”这三个痛点。按本文的流程部署、改掉默认密码、把备份纳入已有备份计划,就可以放心交给日常运维用了。

发表回复

后才能评论