文章目录
背景
- 基于 TCP-IP 协议族构建的互联网,要求其上的通信节点在通信初完成基本信息的配置,包括系统本地名、唯一标识互联网上节点的信息(如 IP 地址)、子网信息、网络服务(比如 DNS)等等。互联网发展早期,网络运维人员通过各式各样的方式获取网络配置信息,并手动完成其配置,这种手工、静态、分散地配置方式随着网络节点规模的增大变得难以实施,配置工作量大、IP 地址冲突频繁、地址利用率低以及设备移动性差等问题逐渐暴露,使用一种自动、动态、集中式地通用网络信息配置机制从根本上解决上述问题的需求愈发显露,DHCP 应势而生。
- DHCP 通过协议实现网络配置信息的自动化获取,引入租约机制实现 IP 地址的动态管理,服务端维护 IP 地址池实现集中式管理。
格式
-
DHCP 是基于 UDP 的应用层协议,其协议栈的分层如下:
+-----------------------+
| DHCP Message | ← 应用层
+-----------------------+
| UDP Header (8 bytes) | ← 传输层(客户端端口68,服务器端口67)
+-----------------------+
| IP Header (20 bytes) | ← 网络层(广播或单播)
+-----------------------+
| Ethernet / ... | ← 链路层
+-----------------------+ -
DHCP 格式分为固定的头部和变长的 TLV 选项两部分,如下:
固定头部
-
DHCP 在 BOOTP 协议基础上,通过对变长的 TLV 选项进行扩展实现。BOOTP 是为解决无盘工作站的网络启动需求而设计的,核心目标是为无盘设备提供基本的三项信息:自己的IP地址、服务器地址、启动文件名,DHCP 完全兼容 BOOTP,因此其固定头部各字段与无盘工作站的网络启动流程相关:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| op (1) | htype (1) | hlen (1) | hops (1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| xid (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| secs (2) | flags (2) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ciaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| yiaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| siaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| giaddr (4) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chaddr (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sname (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| file (128) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| options (variable) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -
DHCP 固定头部字段说明(共 236 字节,不含 options):
字段名 长度(字节) 说明
op 1 消息类型:1 =请求(Request),2 =回复(Reply)
htype 1 硬件地址类型(如 1 =以太网)
hlen 1 硬件地址长度(以太网为 6)
hops 1 中继代理跳数,客户端发送时设为 0
xid 4 事务 ID,由客户端生成的随机数,用于匹配请求与响应
secs 2 客户端启动后经过的秒数,可被服务器参考
flags 2 标志位:最高位(左起第 1 位)为 1 表示要求广播响应
ciaddr 4 客户端 IP 地址(如果客户端已知道自己的 IP)
yiaddr 4 "你的" IP 地址,即服务器分配给客户端的 IP
siaddr 4 下一个启动服务器 IP 地址(通常用于 TFTP 等)
giaddr 4 中继代理 IP 地址(若经过中继,则填写中继的地址)
chaddr 16 客户端硬件地址(如 MAC 地址,以太网为 6 字节,剩余填 0)
sname 64 服务器主机名(可选,不使用时可全为 0)
file 128 启动文件名(可选,不使用时可全为 0)
options 可变 DHCP 选项字段,采用 Type-Length-Value 格式,至少包含 53 号选项(DHCP 消息类型)
TLV 选项
+--------+--------+--------+--------+ ...
| Type | Length | Value |
+--------+--------+--------+--------+ ...
-
DHCP 通过在 file 字段之后、options 变长字段前的 4 字节填入 magic cookie (0x63825363 = 99.130.83.99),标记 BOOTP-based DHCP 报文。以下是 DHCP 报文一些重要选项示例(TLV 格式):
选项名称 Type(十进制) Length(字节) Value 示例(十六进制或说明)
DHCP 消息类型 53 1 1=Discover, 2=Offer, 3=Request, 4=Decline,
5=ACK, 6=NACK, 7=Release, 8=Inform
子网掩码 1 4 例如: ff ff ff 00 (255.255.255.0)
路由器(默认网关) 3 4 例如: c0 a8 01 01 (192.168.1.1)
请求分配的 IP 50 4 例如: c0 a8 01 02 (192.168.1.2)
IP 地址租约时间 51 4 例如: 00 01 51 80 (86400秒 = 1天)
DHCP 服务器标识符 54 4 例如: c0 a8 01 03 (192.168.1.3)
参数请求列表 55 可变 多个Type值,例如 01 03 06 0f
表示请求子网掩码(1)、路由器(3)、域名服务器(6)、域名(15)
主机名 12 可变 例如: "host-01"
域名 15 可变 例如: "example.com"
租约时间(T1,续约时间) 58 4 例如: 00 00 75 30 (30000秒)
租约时间(T2,重绑时间) 59 4 例如: 00 00 93 a0 (37800秒)
客户端标识符 61 可变 例如: 01 00:11:22:33:44:55:66
(01=以太网,后跟MAC地址)
结束标记 255 0 无Value,仅Type=255
基本原理
- DHCP 在客户端启动时工作,此时它没有 IP 地址,也不知道 DHCP 服务器的 IP 地址。因此它必须使用一种不需要预先知道对方 IP 地址的方式与服务器通信,即广播通信,UDP 天然支持广播(255.255.255.255),而 TCP 是面向连接的、点对点的协议,必须先建立连接(基于 IP+Port)才能通信,因此 DHCP 对广播的要求成为其选择 UDP 作为底层协议的核心考量。
- DHCP 目标是实现 IP 地址的集中管理,CS 架构是其自然选择:服务端负责管理 IP 地址和其它网络信息,客户端在需要时请求获取信息。
- DHCP 目标是实现 IP 地址的分配与回收,维护其生命周期成为必要工作,如果服务端来维护 IP 地址生命周期,则需要为每个分发出去的 IP 创建上下文,跟踪其何时回收,容易耗尽其资源,且有状态的服务端设计在不便于横向扩展,在管理大规模 IP 地址时会出现性能瓶颈,从扩展性和健壮性的角度看,无状态设计对服务端来说是更合理的选择,而 IP 地址的生命周期由客户端来维护看来成了一个必选项。
CS 交互
- DHCP 通过 4 个报文实现配置信息在服务器和客户端之间的传递,以下是报文的收发流程:

- DISCOVER 报文:客户端使用 UDP 报文在局域网广播域内广播(IP 源地址 0.0.0.0,目的地址 255.255.255.255),寻找可用的 DHCP 服务器。客户端在广播 DISCOVER 报文前随机生成 transaction id 填入 DHCP 报文固定头部的 xid 字段,标记该报文为自己所发
- OFFER 报文:广播域的一台或多台服务器收到 DISCOVER 报文后,从自己管理的 IP 地址池中取出可用的 IP,封装到 OFFER 报文,同样以广播的方式(IP 源地址为服务端地址,目的地址 255.255.255.255)发出报文。服务端在广播 OFFER 报文前会取出 DISCOVER 报文 xid 字段内容并填入 OFFER 报文中,表明对 DISCOVER 报文的回复
- REQUEST 报文:客户端检查网卡收到的广播报文,可能有多个,通过提取报文中的 xid 并检查是否与自己生成的 DISCOVER xid 相等,来确认 OFFER 是否是对于自己 DISCOVER 报文的回复,如果相同,客户端仍以广播方式发送 REQUEST 报文,正式申请该 IP 地址。这个广播报文有两个意图:对于提供 OFFER 报文且客户端有意选择该 IP 的服务器,收到报文后会在数据库中正式固化该 IP,并准备发送最终的确认报文;对于提供 OFFER 报文且客户端无意选择该 IP 的服务器,收到报文后能够确认"自己提供的 IP" 被拒绝,因此会将 OFFER 报文的 IP 回收到地址池。
- ACK 报文:服务端收到 REQUEST 报文,将本身的 IP 和报文中的 server id(tag 54)对比,同时将提供客户端的 IP 和报文中的 request ip address(tag 50)对比,两者相等说明客户端接收了自己提供的 OFFER IP,服务端广播 ACK 报文以示同意,同时将分配出去的 IP 从自己的可用 IP 池中移除。反之,如果不等说明未接收 OFFER IP,将分配出去的 IP 在可用 IP 池中保留,以备其它客户端使用。
客户端状态机
- DHCP 核心目标之一是高效管理 IP 地址,因此实现 IP 地址的动态分配与释放是其基本工作。前面我讨论了将 IP 地址的状态维护交给客户端客户端设计考量,下图便是 DHCP 规范(RFC 2131)定义的客户端状态机:

- INIT: 客户端节点上电处于 INIT 状态,此时网卡未绑定 IP,客户端广播 DISCOVER 报文,查找局域网内可用的 DHCP 服务
- Selecting: 完成 DISCOVER 广播后,客户端更新状态为 Selecting,等待服务端 OFFER 报文的到达,如果有多个 OFFER 报文,Select 其中一个,通常为先到先被选中
- Requesting: 客户端确定接收 OFFER 报文中的 IP,广播 REQUEST 报文,送达到局域网所有监听 DHCP 端口的服务,发送完广播报文后,客户端更新状态为 Requesting
- Bound: 局域网内所有 DHCP 服务收到 REQUEST 报文后,提取 IP 并匹配,如果和自身 OFFER 报文提供的 IP 相等,说明客户端接受了服务分配的 IP,广播 ACK 报文到局域网,其它提供 OFFER 报文但未收到匹配自身 IP 的报文的 DHCP 服务,在收到 REQUEST 报文后,相当于隐式地接收到了拒绝报文,于是把之前发出的 OFFER 报文中的 IP 回收,以备其它客户端用。客户端收到 ACK 广播报文后,清楚请求的 IP 得到承认,在将 IP 绑定到网卡之后,更新自身状态为 Bound
- Renewing: 客户端仅被允许在服务端提供的租约时间内使用 IP,租约时间到之前如果没有续约成功,DHCP 规范要求客户端终止使用该 IP,为保证 IP 能够被持续连续地使用,客户端通常会提前发送 REQUEST 报文续约,具体实现是设置 T1 时间(约租约时间的一半 1/2),在 T1 时间到之后更新自身状态为 Renewing,然后发送单播的 REQUEST 报文给 DHCP 服务器,如果 DHCP 服务回复 ACK 报文,更新自身状态为 Bound
- Rebinding: 客户端在租约过半但未成功续约的情况下,一直保持着 Renewing 状态,客户端还会设计一个 T2 时间(约租约时间的 7/8),在 T2 时间到之后会更新自身状态为 Rebinding,然后广播 REQUEST 报文,如果 DHCP 回复 ACK,更新自身状态为 Bound。在租约到期后,如果状态仍是 Rebinding,更新自身状态为 INIT,重新发送 DISCOVER 广播请求 IP,处于 INIT 状态的客户端,原则上其 IP 是非法的,如果客户端不终止使用,可能产生 IP 冲突,因此客户端需要释放 IP
- 下图是 RFC 2131 描述的更详细的客户端状态机

实验
状态机实现
- light_dclient 是一个轻量的 DHCP 客户端,按照 RFC 2131 的规范实现了客户端状态机,能够为其所在节点提供动态 IP 请求的功能,与 dhclent 类似。