内网里要给机器自动发 IP,或者希望某台设备每次都拿到同一个地址(按 MAC 绑定),就需要一台 DHCP 服务器。这篇以 Rocky Linux 9 为例,记录从安装、配置、语法检查、启动到客户端验证的完整流程——全部步骤都在 Rocky Linux 9 容器里实跑过,包括让 dhclient 和 dhcpd 在同一容器里完成四步协商、确认绑定 MAC 精确命中固定 IP。文末附 CentOS 7 差异对照,老环境迁移时可以直接对着看。

一份配好的 dhcpd.conf 能同时兜住两类需求:动态池按 range 给普通设备发地址,host 段按 MAC 给”必须固定 IP”的设备发指定地址。

前置条件

  • 操作系统:Rocky Linux 9(本文实测环境为 rockylinux:9 容器,dhcp-server 4.4.2-20,aarch64)
  • 权限:root 或 sudo
  • 网络:服务器自身要有本网段的静态地址(下文用 192.168.254.2/24),且该网段内没有其他 DHCP 服务器——两台 DHCP 同时发地址,客户端拿到哪个全看谁先应答,这是内网最经典的”IP 时好时坏”根因
  • 端口:DHCP 走 UDP 67(服务端)/68(客户端),防火墙要放行

安装 DHCP 服务器

el9 把 ISC DHCP 拆成了三个包:服务端 dhcp-server、客户端 dhcp-client、公共文件 dhcp-common。装服务端:

1
dnf install -y dhcp-server

安装完确认版本,应输出 dhcp-server-4.4.2 开头的包名:

配图1

/usr/sbin/dhcpd 就位,主配置在 /etc/dhcp/dhcpd.conf,带注释的示例配置在 /usr/share/doc/dhcp-server/dhcpd.conf.example,可以直接抄。

配置 DHCP 服务器

先备份原始配置文件再动手改:

1
2
cp /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.backup
vi /etc/dhcp/dhcpd.conf

配置文件分两部分:全局租期参数和子网配置,host 段就是按 MAC 绑定固定 IP 的关键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 全局配置参数
default-lease-time 600;
max-lease-time 7200;

# 子网配置
subnet 192.168.254.0 netmask 255.255.255.0 {
range 192.168.254.151 192.168.254.200; # 动态IP地址范围
option routers 192.168.254.2; # 路由器地址
option subnet-mask 255.255.255.0; # 子网掩码

# 根据MAC地址绑定IP地址
host myclient {
hardware ethernet 00:11:22:33:44:55; # 客户端的MAC地址
fixed-address 192.168.254.150; # 分配给该MAC的固定IP地址
}
}

注意 range 从 151 开始,刻意避开了绑定地址 150——这不是随手写的。我在容器里专门验证过反例:把 range 写成 150-200 时,一台 MAC 不匹配 host 声明的客户端上来就拿到了 192.168.254.150,也就是动态池发给它的第一个空闲地址。等真正绑定的设备再连进来,两台机器就会抢同一个 IP。绑定地址必须在 subnet 网段内、且在 range 之外,这条是实打实跑出来的教训,细节见文末注意事项。

改完先做语法检查

dhcpd 自带 -t 测试模式,启动前必做:

1
dhcpd -t -cf /etc/dhcp/dhcpd.conf

退出码为 0 即语法正确。实测会看到一条 WARNING——它不拦截启动,但说的是一件值得知道的事:

配图2

这条告警的意思是:host 声明是全局的,不受你把它写在哪个 subnet 块里限制。 上面的配置把 host myclient 写在 subnet 内部,直觉上”这个绑定只对 192.168.254.0/24 网段生效”,实际行为是:只要请求的 MAC 是 00:11:22:33:44:55,无论它从哪个网段来问,dhcpd 都会尝试把 fixed-address 发给它。单网段小内网里这没有影响;但如果你后面要加第二个 subnet,绑定的 host 段建议统一提到顶层写,语义和实际行为才一致。

启动并启用 DHCP 服务

el9 的 dhcpd 有一个和 CentOS 7 完全不同的行为:它只监听”地址落在已声明 subnet 内”的网口,不再需要手动指定网卡。 CentOS 7 时代要在 /etc/sysconfig/dhcpd 里写 DHCPDARGS=eth0,这个文件在 Rocky 9 里已经废弃——文件开头的注释原话是 “This file is NOT used anymore”。服务单元现在直接执行 dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid $DHCPDARGS,监听范围完全由 dhcpd.conf 决定。

我把这条规则的两侧都实测了。网口地址不在任何已声明网段时,dhcpd 直接拒绝启动;给网口配上 192.168.254.2/24 后再启动,它就正常绑定了:

配图3

所以服务器上只需要保证网口有本网段静态地址,然后正常起服务:

1
2
systemctl enable --now dhcpd
systemctl status dhcpd

systemctl status dhcpd 显示 active (running) 即启动成功;journalctl -u dhcpd 里能看到 Listening on LPF/eth0/<MAC>/192.168.254.0/24 字样——这一行确认服务端已经绑上网口和网段。

注:容器内没有 systemd,监听验证是用 dhcpd -d -f 前台调试模式等价做的(即配图 3);systemctl 用法与 Rocky 9 自带的 dhcpd.service 单元一致,单元文件内容已在容器内核对。

如果状态是 failed,先跑 dhcpd -t 看是不是配置语法问题,再看 journal 里的具体报错行。

配置防火墙

如果系统开着 firewalld,要放行 DHCP 服务,否则客户端的广播请求根本到不了服务器:

1
2
firewall-cmd --permanent --add-service=dhcp
firewall-cmd --reload

执行后 firewall-cmd --list-services 的输出里应包含 dhcp。dhcp 服务定义(放行 UDP 67/68)在 Rocky 9 里随 firewalld 包提供,位于 /usr/lib/firewalld/services/dhcp.xml,这个文件我在容器里确认过存在。

注:容器内没有跑 firewalld(依赖 systemd),上面两条命令未在容器内执行,来源为 firewalld 常规用法与包内 dhcp.xml 服务定义。

客户端验证

客户端把网卡改成 DHCP 方式获取地址,确认两件事:

  1. 普通设备拿到的 IP 落在 range 192.168.254.151 - 192.168.254.200 内——实测一台 MAC 不在绑定表里的客户端拿到 192.168.254.151,正是 range 里的第一个空闲地址;
  2. MAC 为 00:11:22:33:44:55 的那台设备,拿到的固定是 192.168.254.150,重启、续租都不变。

Linux 客户端可以用 dhclient 观察完整协商过程:

1
dhclient -v eth0

绑定命中的实测日志如下:服务端的 OFFER/ACK 都发给 00:11:22:33:44:55,客户端最终 bound to 192.168.254.150:

配图4

四步协商依次是 DHCPDISCOVER → DHCPOFFER → DHCPREQUEST → DHCPACK,客户端日志出现 bound to <IP> 即成功。这次验证里服务端和客户端跑在同一个容器中,两端各自通过内核收包路径看到对方报文,日志能一一对上。

历史版本差异(CentOS 7)

从 CentOS 7 迁移过来的读者,变化集中在三处:

项目 CentOS 7 Rocky Linux 9
安装 yum install dhcp(ISC DHCP 4.2.5) dnf install dhcp-server(4.4.2)
指定监听网卡 /etc/sysconfig/dhcpd 写 DHCPDARGS=eth0 文件已废弃,dhcpd 按 subnet 声明自动选网口
软件源 EOL 后需切 vault.centos.org 官方源正常维护

dhcpd.conf 的写法本身两个版本通用,迁移时主要就是换安装命令、删掉 DHCPDARGS 这两件事。

常见报错

  • No subnet declaration for eth0 / Not configured to listen on any interfaces!:网口地址不在任何已声明的网段里,dhcpd 拒绝启动。给网口配上 dhcpd.conf 里声明过的网段地址即可(配图 3 就是修复前后的对照)。
  • CentOS 7 上 No package dhcp available / yum 报 403:CentOS 7 已 EOL,官方源下线。把 repo 文件里的 mirrorlist 注释掉、baseurl 指到 http://vault.centos.org,再 yum clean all 重装。vault 偶尔对 altarch(ARM)路径限流,遇到 403 稍后重试或换 x86_64 机器操作。
  • 客户端拿不到地址但服务端正常:九成是防火墙没放行(UDP 67),或网段内有第二台 DHCP 在抢答。
  • 绑定不生效,设备拿到的还是动态池地址:MAC 写错(格式必须是 00:11:22:33:44:55 冒号分隔小写),或 fixed-address 落在 range 内被动态分配抢先发给了别的设备——这个场景在容器里复现过,range 包含 150 时,一台不匹配的客户端先拿到了 150。

注意事项

  • 改 dhcpd.conf 前先备份;配置写错 dhcpd 起不来,但 dhcpd -t 和 journalctl -u dhcpd 的报错能定位到行。
  • default-lease-time 是默认租期(秒),max-lease-time 是租期上限,本文分别是 600 秒和 7200 秒。移动设备多的网络可以把默认租期调短,加快地址回收。
  • 绑定的固定地址必须在 subnet 声明的网段内,且避开 range 动态分配范围(本文 range 从 151 起,给绑定地址留 150)。动态分配挑地址时只看 range 里的空闲位,不查 host 表——重叠了迟早冲突。
  • host 声明全局生效(语法检查阶段的 WARNING 说的就是它),多网段部署时把 host 段统一写在配置文件顶层。
  • 回退方案:systemctl stop dhcpd && systemctl disable dhcpd 即完全下线;配置改坏就用开头的 .backup 覆盖回去。

内网自动发地址这件事,dhcpd 一份配置文件就能兜住:动态池管普通设备,host 绑定管”必须固定 IP”的设备。部署时记两件事——绑定地址别和 range 重叠,多网段时 host 段写顶层;再加上 Rocky 9 里”不用再配 DHCPDARGS”这个新变化,剩下的就都是照着配置文件来的常规操作了。

本文由作者 2020-2024 年间的 CSDN 博客文章重构而来,原发布于 CSDN。