Skip to content

DHCP 服务器(dhcpd / Kea) ​

静态 IP 适合服务器,但局域网里的笔记本、手机、打印机不可能挨个手工配地址——DHCP(Dynamic Host Configuration Protocol)就是把这层分配自动化的协议:机器插上网线发一嗓子广播,DHCP 服务器应答一个带网关、DNS、租约期限的完整网络参数包,客户机配置即告完成。自建一台 DHCP 的典型场景:家庭或办公室内网想统一管理地址段与 DNS 出口;虚拟化/实验环境里一批虚机频繁增删,手工登记根本追不上;或者现有路由器内置的 DHCP 段要重划、要给打印机做固定地址保留。本页以 ISC dhcpd 为主线(Debian/Ubuntu 的 isc-dhcp-server、RHEL 系的 dhcp-server、Arch 的 dhcp 包),按"为什么 → 安装 → 服务端配置 → 客户端与租约验证 → Kea → 排错"展开;DHCP 下发的 DNS 选项指自建 BIND 时见DNS 服务器,跨网段组网与 NAT 见路由与 NAT。

内容参考自 ISC 官方文档与 Arch Wiki(概念框架参考鸟哥的私房菜),见文末参考资料。

学习目标 ​

  • 讲清 DORA 四步为什么全程广播、租约为什么必须有期限
  • 掌握三发行版安装与 unit 名差异,装完能验证服务真的在监听
  • 独立写出完整可跑的 dhcpd.conf:地址池、选项、固定地址保留
  • 会从两端验证一次成功分配:服务端读租约账本,客户端看实际下参数
  • 了解 ISC DHCP 停维护后的接班人 Kea,知道何时该选它
  • 排查服务起不来、拿到地址没网关、地址冲突等高频坑

1. DHCP 解决什么问题 ​

1.1 DORA:四步全广播的握手 ​

客户机在拿到地址之前没有 IP 可用,所以整个协商只能靠广播完成,这就是 DORA 四步:Discover(客户端广播"这里有 DHCP 服务器吗")、Offer(服务器广播"我提议 192.168.56.101,网关 DNS 如下")、Request(客户端广播"我要用刚才那台提议的地址"——广播是顺便告诉其他服务器"你们的提议我没采纳")、ACK(服务器广播"成交,租约 600 秒")。协议端口是 UDP 67(服务端)与 68(客户端),全程链路层广播,任何一步丢了就靠客户端重试,协议本身没有重传协调者。

步骤谁发报文要点
Discover客户端源地址 0.0.0.0,目标 255.255.255.255,带 MAC 与主机名
Offer服务端提议 IP/掩码/网关/DNS/租期,多台服务器各发各的
Request客户端广播确认选中的服务器与地址,落选者回收提议
ACK服务端正式发放,客户端此后可以配置网卡上线

四步里最容易忽略的是第三步为什么也要广播:内网若有多台 DHCP 服务器(迁移期间很常见),客户端用广播公开"我选了谁",落选的服务器才能把刚才 Offer 的地址放回池子。理解这一点,就明白为什么排错时抓包比看日志直观——DORA 是否走完、走到第几步断了,一次 tcpdump 全部摊开。

1.2 租约:一本会过期的账 ​

DHCP 不是"发完就完",而是租借模型:每个分配出去的地址都记在服务端的租约文件里,带起止时间;租期过半(0.5 × T1)客户端会主动找原服务器续租,租期过 7/8(0.875 × T2)还没续上就广播向任何服务器求救,全部失败则到期退租、地址回池。这本账的类比对象就是包管理器的安装数据库:/var/lib/dhcp/dhcpd.leases 之于 dhcpd,正如 /var/lib/dpkg/status 之于 dpkg——都是"我发出去/装上去的东西"的单一真相源,服务重启不丢、可以离线审阅。租约期限是调优手段而非常量:办公网短租期(如 1 小时)让地址快速回收,适合设备频繁来去;服务器所在的固定段则应该用保留地址而非长租期来兜底——期限解决"回收",保留解决"这台机器永远是这个地址",两件事别混。

1.3 authoritative:权威与非权威的姿态 ​

dhcpd.conf 顶层的 authoritative; 声明本服务器是本网段的正式 DHCP。差别体现在"拒绝"上:客户端带着从别的网段带来的旧租约来续租,权威服务器直接回 NAK,逼它重新走一遍 DORA 拿本网段的正确参数;非权威服务器选择沉默,客户端就吊死在旧租约上反复重试,表现为"换了网段后一直上不了网"。只有迁移期间的临时服务器才应该保持非权威姿态,正式环境一律加 authoritative;——这条漏配是新手配置里最经典的"看起来都对就是不通"。

2. 安装(三发行版对照) ​

操作Debian/UbuntuArchRHEL/CentOS/Rocky
安装(服务端)apt install isc-dhcp-serverpacman -S dhcpdnf install dhcp-server
服务单元isc-dhcp-serverdhcpd4dhcpd
主配置/etc/dhcp/dhcpd.conf/etc/dhcpd.conf/etc/dhcp/dhcpd.conf
指定监听网卡/etc/default/isc-dhcp-server 的 INTERFACESv4systemctl edit dhcpd4 覆写 ExecStart/etc/sysconfig/dhcpd 的 DHCPDARGS
租约文件/var/lib/dhcp/dhcpd.leases/var/lib/dhcp/dhcpd.leases/var/lib/dhcpd/dhcpd.leases
客户端工具dhclient(isc-dhcp-client,默认已有)包内自带 dhclientdhclient(dhcp-client 包)

三系的差异集中在包名、unit 名与配置路径三个维度,配置语法完全一致。一个容易混淆的点:Arch 的 dhcp 包同时装进服务端 dhcpd 与 ISC 客户端 dhclient,而 dhcpcd 是另一个独立项目的客户端(Arch 默认联网用的就是它)——名字只差一个字母,前者 dhcpd 是"发地址的",后者 dhcpcd 是"要地址的",排错时先 systemctl status 对准 unit 再往下走,别把客户端的日志当成服务端的问题查。

2.1 Debian/Ubuntu ​

bash
$ sudo apt install isc-dhcp-server
$ systemctl status isc-dhcp-server | head -3
     isc-dhcp-server.service - ISC DHCP IPv4 server
     Active: failed (Result: exit-code) ...

装完直接 status 大概率看到 failed——这不是安装坏了,而是 Debian 的默认配置还没有任何 subnet 声明,dhcpd 拒绝空跑(原因见第 6 节第一个坑)。配置补齐并写好 /etc/default/isc-dhcp-server 里的 INTERFACESv4 后再启动,是 Debian 系的标准顺序:先配后启,而不是装完就 start。

2.2 Arch ​

bash
$ sudo pacman -S dhcp
$ sudo touch /var/lib/dhcp/dhcpd.leases      # 首次运行前确保租约文件存在
$ sudo systemctl enable --now dhcpd4 && systemctl is-active dhcpd4
active

Arch 的配置在上游默认路径 /etc/dhcpd.conf(注意没有 /etc/dhcp/ 这层目录),unit 名 dhcpd4 明确标注只跑 IPv4——想要 IPv6 PD 对应 dhcpd6。首次启动若报租约文件相关错误,就是上面第二行没做:dhcpd 不会自己创建租约文件,这本"账本"必须先落盘。要限定监听某块网卡,用 systemctl edit dhcpd4 写一段 drop-in 覆写 ExecStart,在命令行末尾追加网卡名。

2.3 RHEL/CentOS/Rocky ​

bash
$ sudo dnf install dhcp-server
$ sudo systemctl enable --now dhcpd && systemctl is-active dhcpd
active
$ sudo ss -ulnp | grep :67
udp   UNCONN 0  0  0.0.0.0:67  0.0.0.0:*  users:(("dhcpd",pid=1248,fd=7))

RHEL 8 起把老 dhcp 包拆成了 dhcp-server 与 dhcp-client,照旧文档敲 dnf install dhcp 会装不上。ss -ulnp | grep :67 是三系通用的最终验收:unit 显示 active 只说明进程活着,UDP 67 真正挂上广播 socket 才说明服务在收包——这与 BIND 验收要看 dig 应答而不是 systemctl 绿灯是同一条纪律。防火墙放行 DHCP 服务(UDP 67/68)不在本页展开,见防火墙篇。

3. 服务端配置 ​

3.1 地址池与全局选项 ​

以下是一份针对 192.168.56.0/24 内网的完整可跑配置,注释逐条对应语义:

text
# /etc/dhcp/dhcpd.conf(Arch 为 /etc/dhcpd.conf)
authoritative;                       # 本机是本网段的正式 DHCP(见 1.3 节)
option domain-name "lan.example.com";
option domain-name-servers 192.168.56.10, 192.168.56.2;
default-lease-time 3600;             # 默认租约 1 小时
max-lease-time 7200;                 # 客户端请求再长也只给 2 小时

subnet 192.168.56.0 netmask 255.255.255.0 {
    option routers 192.168.56.2;     # 网关:跨网段与 NAT 的出口,见路由与 NAT 一页
    range 192.168.56.100 192.168.56.200;   # 动态池:101 个地址可供租借
}

三件事值得停下来想清楚。第一,subnet 块必须与服务器某块网卡的实际网段匹配——dhcpd 靠它判断"这个广播该不该我应答",网卡是 192.168.56.0/24 而配置只写了 10.0.0.0/8,服务直接起不来。第二,domain-name-servers 放在全局而 routers 放在 subnet 里,是刻意示范两种作用域:全局选项对所有 subnet 生效,subnet 内选项只对本段生效——多网段时网关各段不同、DNS 全网统一,正是这个分工的典型用法。第三,range 划定动态池,池外的地址留给保留与静态设备,"池"与"保留区"的边界要在规划时一次画清,后文冲突坑的根源多数是这条边界没画。

服务器插两块网卡、各接一个网段时,两个 subnet 并排声明即可,dhcpd 按广播进入的接口选用对应网段的池,全局选项两侧共享:

text
# 双网卡双网段:第 3.1 节配置之外再追加一段
subnet 192.168.57.0 netmask 255.255.255.0 {
    option routers 192.168.57.2;           # 本段网关与 56 段不同
    range 192.168.57.100 192.168.57.150;   # 池大小按段内设备数估
}

这是"一台服务器服务多个网段"的最简形态(物理直连);网段之间隔着路由器时的中继做法见第 6 节。注意每段各自画清池与保留区的边界,两段的地址空间互不重叠是硬前提。

3.2 固定地址保留(host) ​

打印机、NAS、需要端口映射的内网服务,认 MAC 不认租期:

text
# 追加到 dhcpd.conf —— 保留地址放在 range 之外(.50 不在 .100-.200 内)
host laserjet {
    hardware ethernet 08:00:27:aa:bb:cc;
    fixed-address 192.168.56.50;
    option host-name "laserjet";
}

保留的本质是"永远 Offer 同一个地址"——客户端流程仍是标准 DORA,只是 Offer 被钉死,因此客户端视角这是一台"每次都拿到同一地址的 DHCP 客户端",与手工静态配置有微妙差别(后者根本不参与 DHCP)。保留地址务必划在 range 之外:dhcpd 发 Offer 前默认会 ping 候选地址探活,但目标机器防火墙丢弃 ICMP 时探测失效,池内地址被"Ping 不通但其实有人用"地重复分配,正是保留区与动态池重叠时的经典事故。若内网已自建 BIND,保留地址应同时在 DNS 里登记 A 与 PTR 记录,做到"名字、MAC、地址"三处一致——DHCP 管发放、DNS 管命名,两本账各记各的但要对得上账。

3.3 监听网卡与语法检查 ​

dhcpd 默认尝试服务所有广播接口,生产上应显式圈定。三系入口不同但语义一致:

text
# /etc/default/isc-dhcp-server(Debian/Ubuntu)
INTERFACESv4="enp3s0"

# /etc/sysconfig/dhcpd(RHEL 系)
DHCPDARGS="enp3s0"
bash
# 改完配置的固定前奏:语法检查,通过再重启
$ sudo dhcpd -t -cf /etc/dhcp/dhcpd.conf
Internet Systems Consortium DHCP Server 4.4.3-P1
...
If you did not get this output, there may be errors in your config.   # 无 error 即通过
$ sudo systemctl restart isc-dhcp-server    # Arch: dhcpd4;RHEL: dhcpd

dhcpd -t 之于 dhcpd,正是 nginx -t、named-checkconf 之于各自服务:把语法错误拦在重启之前。它检查语法与结构,但不检查"subnet 是否匹配网卡"这类运行时事实——后者只有真正启动时才暴露,所以验收仍以 systemctl is-active 加 ss -ulnp | grep :67 双绿为准。

4. 客户端与租约验证 ​

4.1 服务端视角:读租约账本 ​

bash
$ sudo cat /var/lib/dhcp/dhcpd.leases
lease 192.168.56.101 {
  starts 3 2026/10/01 09:12:44;
  ends 3 2026/10/01 10:12:44;
  binding state active;
  next binding state free;
  hardware ethernet 08:00:27:9f:3a:c1;
  client-hostname "desk-01";
}

binding state active 表示租约在册,starts/ends 是租期窗口,hardware ethernet 是承租人身份——一台新设备上网后最先该来这里确认"它真的从我这里拿过地址、拿到的是哪个"。续租成功时文件的 ends 会前移,排查"地址快到期没续上"就是盯着这本账的时间戳看。

续租在服务端日志里也有清晰足迹,可以现场验证第 1.2 节的 T1 时刻:租期过半时客户端只发单播的 REQUEST(不再 Discover),服务端回 ACK,两步了结:

bash
$ sudo journalctl -u isc-dhcp-server --since "2 hours ago" | grep 9f:3a:c1
... DHCPREQUEST for 192.168.56.101 from 08:00:27:9f:3a:c1 via enp3s0   # 单播续租,无 Discover
... DHCPACK on 192.168.56.101 to 08:00:27:9f:3a:c1 via enp3s0

日志里频繁出现完整 DORA(带 Discover)说明客户端在反复重拿地址——租约总续不上、或客户端网卡在反复重连,这两种病根都要往"环境"而不是 dhcpd 配置里查。

4.2 客户端视角:请求并核对下发参数 ​

bash
# 一次性手动请求(验证场景最直接)
$ sudo dhclient -v -r && sudo dhclient -v enp3s0
Listening on LPF/enp3s0/08:00:27:9f:3a:c1
DHCPDISCOVER on enp3s0 to 255.255.255.255 port 67 interval 1
DHCPOFFER of 192.168.56.101 from 192.168.56.5
DHCPREQUEST of 192.168.56.101 on enp3s0 to 255.255.255.255 port 67
DHCPACK of 192.168.56.101 from 192.168.56.5
$ ip -4 addr show enp3s0 | grep inet
    inet 192.168.56.101/24 brd 192.168.56.255 scope global dynamic enp3s0
$ ip route | head -1
default via 192.168.56.2 dev enp3s0       # option routers 是否生效,看这一行

NetworkManager 管理的桌面机对应做法是 nmcli con mod <连接名> ipv4.method auto && nmcli con up <连接名>(静态/动态切换的完整对照见网络配置基础)。验证不止看"拿到地址":网关、DNS、域名后缀都要逐一核对,因为它们分别对应配置里不同的 option——"有地址没网关"这类半残分配,正是第 6 节的高频坑:

bash
$ resolvectl status enp3s0 | grep -A2 "DNS Server"
Current DNS Server: 192.168.56.10
       DNS Servers: 192.168.56.10 192.168.56.2
        DNS Domain: lan.example.com      # option domain-name 与 domain-name-servers 都已下发

4.3 抓包看 DORA 与读日志 ​

bash
# 服务端网卡上观察完整的四步(dhcpcd/dhclient 重启触发)
$ sudo tcpdump -i enp3s0 -n port 67 or port 68
09:12:43.112341 IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 08:00:27:9f:3a:c1
09:12:43.114902 IP 192.168.56.5.67 > 255.255.255.255.68: BOOTP/DHCP, Reply from 08:00:27:aa:bb:cc   # Offer
09:12:43.118760 IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 08:00:27:9f:3a:c1
09:12:43.121577 IP 192.168.56.5.67 > 255.255.255.255.68: BOOTP/DHCP, ACK from 08:00:27:aa:bb:cc
$ sudo journalctl -u isc-dhcp-server --since "10 min ago" | grep dhcpd
... dhcpd[1248]: DHCPDISCOVER from 08:00:27:9f:3a:c1 via enp3s0
... dhcpd[1248]: DHCPOFFER on 192.168.56.101 to 08:00:27:9f:3a:c1 (desk-01) via enp3s0
... dhcpd[1248]: DHCPACK on 192.168.56.101 to 08:00:27:9f:3a:c1 (desk-01) via enp3s0

Discover 有、Offer 无,问题在服务端配置或权限;Discover 都没有,问题在链路或客户端;Request 之后没有 ACK,多半是租约账本写入失败——抓包把故障切在三段里的哪一段,比翻日志猜快得多。Debian 系还可 apt install dhcpdump 获得逐字段解码的视图,本质与 tcpdump 相同,只是把 DORA 各字段翻译成了人话。

5. Kea:ISC 的新一代 DHCP ​

ISC 已于 2022 年底宣布 ISC DHCP 结束维护(最后的 4.4.3-P1 之后不再有修复),接班人是同为 ISC 开发的 Kea。 dhcpd 目前各发行版仍在广泛使用、配置生态成熟,存量部署短期没有迁移压力;但新项目值得直接评估 Kea,尤其需要这几样能力时:租约存进 MySQL/PostgreSQL 以支撑大规模与多机共享、内置 HA 双机热备(premium hook)、REST API 管理接口、原生的 IPv6 与前缀委派支持。

bash
$ sudo apt install kea-dhcp4-server       # Debian/Ubuntu;RHEL 系在 EPEL:dnf install kea;Arch 需从 AUR 构建
$ sudo systemctl enable --now kea-dhcp4-server
$ keactrl status
kea-dhcp4: active                         # keactrl 统一管理 kea-dhcp4/-6 各进程状态
json
// /etc/kea/kea-dhcp4.conf —— 与第 3 节等价的最小配置(JSON)
{
  "Dhcp4": {
    "interfaces-config": { "interfaces": ["enp3s0"] },
    "lease-database": { "type": "memfile" },
    "valid-lifetime": 3600,
    "subnet4": [{
      "subnet": "192.168.56.0/24",
      "pools": [{ "pool": "192.168.56.100 - 192.168.56.200" }],
      "option-data": [
        { "name": "routers", "data": "192.168.56.2" },
        { "name": "domain-name-servers", "data": "192.168.56.10, 192.168.56.2" }
      ],
      "reservations": [{
        "hw-address": "08:00:27:aa:bb:cc",
        "ip-address": "192.168.56.50",
        "hostname": "laserjet"
      }]
    }]
  }
}

概念一一对应:pools 是 range,option-data 是 option 行,reservations 是 host 保留,valid-lifetime 是 default-lease-time——换的是载体(文本变 JSON),不变的是 DORA 与租约模型。

维度ISC dhcpdKea
维护状态2022 年底停止维护,存量广泛ISC 现役项目,持续更新
配置载体纯文本 dhcpd.confJSON,机器友好
租约后端本地文件memfile / MySQL / PostgreSQL
高可用主备各自为政,需外部协调HA hook(premium)原生双机
API/自动化无REST 控制接口(CA)
IPv6 前缀委派支持,配置繁琐原生支持

选型口径:学习与小规模内网继续用 dhcpd,资料多、排错直观;地址量上千、要数据库租约后端或 API 自动化时上 Kea;两者都是 ISC 出品,DORA 报文层面客户端无感知,迁移可以逐网段灰度进行。

6. 排错与常见坑 ​

服务起不来:No subnet declaration。 journalctl -u dhcpd -e 看到 No subnet declaration for enp3s0 (no address yet.) 或 Not configured to listen on any interfaces!,根因是配置里没有任何 subnet 与网卡实际网段匹配——网卡还没拿到地址(本机自己也在等 DHCP)、或 subnet 写错了网段。处置顺序:ip addr 确认网卡已有静态地址 → 核对 dhcpd.conf 的 subnet/netmask 与之一致 → dhcpd -t 过了再起。给 DHCP 服务器本机配静态地址是前置条件,一台自己都没有稳定地址的机器没法给别人发租约。

客户端拿到地址却没有网关/DNS。 地址有了、ping 8.8.8.8 不通或域名解析失败,先 ip route 看 default 是否存在、resolvectl status 看 DNS 是否下发。九成是 option routers / option domain-name-servers 漏配或写错了作用域(subnet 内拼写笔误时全局选项不会兜底)。对照第 3.1 节检查两个 option 的位置与拼写即可;抓包看 ACK 报文里实际带了哪些 option,是最终裁决。

IP 地址冲突。 症状是两台机器间歇性抢线、MAC 地址在交换机表里来回跳。先查 range 是否与手工静态配置的机器或 host 保留区重叠——dhcpd 对池内地址默认有 ping 探活,但目标防火墙丢 ICMP 时形同虚设,边界规划(池外保留、静态区单独划段)才是根治。已经冲突的地址在租约文件里删掉对应 lease 块并重启 dhcpd 可强制回收,再让受害机器重新 DORA。

跨网段客户端拿不到地址。 DHCP 广播过不了路由器,中央 DHCP 要服务远端网段,需在远端网段放一台中继:dhcrelay -i <内网口> <DHCP 服务器 IP>,它把广播转成单播发给服务器、应答再转回广播,报文里的 giaddr 字段告诉服务器"该从哪个 subnet 的池子里挑地址"。没有中继就只有两个选择:每个网段本地一台 DHCP,或三层交换机上配 DHCP relay——拓扑决定方案,配置只是执行。

内网出现第二台 DHCP 抢答。 症状是同一台客户端时快时慢地换地址段、网关 DNS 跟着漂移,租约账本里出现"不属于你规划"的地址。家庭/实验室最常见的来源:路由器自带 DHCP 与你新起的服务器并存、虚拟化软件(VirtualBox/VMware host-only、libvirt 默认网络)给桥接段又架了一套。处置先取证后拆弹:两端同时抓 67/68 端口,看 Offer 各来自哪个 MAC,确认抢答者后关掉路由器内置 DHCP 或把 libvirt 网络改成 forward dev=... 的隔离模式,让每个广播域只有一个应答者——DORA 的第三步本来就靠广播"落选声明",但它只协调协议内并发,管不住两套互不知情的配置。

改了配置不生效。 三步自查:dhcpd -t 是否真的跑过(语法红会直接起不来);是否 systemctl restart(dhcpd 没有 reload 语义,改配置必须整进程重启);客户端是否还攥着旧租约(租期未到不会重新 DORA,验证时手动 dhclient -r && dhclient -v 或断开重连)。与 BIND"改完必须 rndc reload"同一条纪律:配置生效链路上每一环都要亲眼看到绿灯。

参考资料 ​

相关页面 ​

基于 Apache License 2.0 许可发布