DHCP 的全称为 Dynamic Host Configuration Protocol, 即动态主机配置协议, 是日常频繁使用的协议之一, 当我们通过 WIFI 或有线方式连接路由器时, 路由器会为设备动态分配一个 IP 地址 (同时也会自动配置子网掩码、网关 IP、DNS 地址等), 这个过程便是由 DHCP 协议实现的, 对用户来说是透明无感知的, 路由器实质上就是一个 DHCP Server, DHCP 可以在完全无人工介入的情况下, 动态、自治地实现协议参数分配, 它是对 Bootstrap 引导协议 [RFC 951] (或简称 BOOTP 协议, 它通常用在无盘工作站上, 无盘工作站使用 BOOTP 协议来初始化网络协议参数, 如 IP 地址、子网掩码、DNS 地址等, 它只有 ROM 用来存放基本的启动数据, 包括操作系统在内都需要通过 BOOTP / TFTP 协议进行获取和引导) 的延伸, 本文全面讨论 DHCP 协议的设计与工作原理

17.1 DHCP 协议概述

DHCP 以 Client / Server 的机制运行, 提供 DHCP 服务的一方称为 DHCP Server, 如路由器, 它维护了当前已连接设备的 Mac 地址与已分配的和当前可用的 IP 地址信息, 而请求 DHCP 服务的一方称为 DHCP Client, 如刚刚连接路由器的手机, 电脑等终端设备, DHCP 支持三种模式的 IP 地址分配, 分别是 automatic allocation, dynamic allocation 以及 manual allocation, 在 automatic allocation 模式下, DHCP Server 为设备分配一个永久恒定的 IP 地址, 在 dynamic allocation 模式下, DHCP 为设备分配一个临时的 IP 地址, 设备 (以下使用 Client 代指请求 DHCP 服务的设备) 可在即将失效前请求 DHCP Server 进行续期, 在 manual allocation 模式下, DHCP Server 自身并不自动化地确定协议参数, 协议参数的确定完全是由人工输入的, DHCP Server 只是将人工输入的参数传递给 Client

使用的最多的模式为 dynamic allocation, 每次它为 Client 分配一个临时的 IP 地址, 当 IP 地址到期或者 Client 主动放弃使用后, DHCP Server 可将分配的 IP 地址进行回收, 然后重新分配给新请求的 Client, DHCP 与 Bootstrap 引导协议 [RFC 951] 完全兼容, 同时 DHCP 可以使用 Bootstrap Relay Agent 在 DHCP Client 和 DHCP Server 之间传递报文消息 (当网络规模比较大的时候, 可以不在每个网络中都设置 DHCP Server, 而是在每个网络中设置一个 Bootstrap Relay Agent, 该 Agent 作为客户端和服务端通信的中继, 这样多个网络可以使用统一的 DHCP Server, 在下面的讨论中, 我们都认为 Relay Agent 存在)

17.2 DHCP 消息结构

DHCP 的消息结构如下:

图中的数字以字节为单位指示各个字段的长度, 消息结构中各字段的语义如下:

  • op, 长度为 1 字节, 代表 DHCP 的操作码, 共有两个取值, 分别是 1 和 2, 当 op = 1 时, 代表这是一个 DHCP Request 报文, 由 Client 发往 Server, 当 op = 2 时, 代表这是一个 DHCP Reply 报文, 由 Server 发往 Client

  • htype, 长度为 1 字节, 指示硬件地址类型, 如 1 代表以太网, 即使用的硬件地址为 48 位的 Mac 地址

  • hlen, 长度为 1 字节, 以字节为单位指示硬件地址的长度, 如以太网 Mac 地址的长度为 6 字节, 则该字段的值为 6

  • hops, 长度为 1 字节, 客户端应设置为 0, 该字段每经过 Relay Agent 时将加一

  • xid, 长度为 4 字节, 由客户端设置的随机数, 用于关联一次 DHCP Request 与 DHCP Reply

  • secs, 长度为 2 字节, 由客户端填充, 指示客户端从获得到 IP 地址开始到现在过去的时间

  • flags, 长度为 2 字节, 共 16 位, 目前只有最高位使用, 其他位都必须设置为 0, 以供将来使用, 最高位为 1 时代表 DHCP Server 将该消息广播发给客户端 (因为 DHCP Client 第一次请求时可能还没有被分配 IP 地址, 因此无法通过 IP 地址将消息回复给它, 只能使用 IP 广播地址, 255.255.255.255 将消息返回), 最高位为 0 时代表 DHCP Server 将该消息以 IP 单播的方式发给客户端 (这可能发生在 DHCP Client 自身有 IP 地址, 只是请求 DHCP Server 进行 DHCP 续期)

  • ciaddr, 长度为 4 字节, 指示客户端的 IP 地址, 若客户端进行 DHCP 请求时没有 IP 地址, 可将该字段设置为 0.0.0.0

  • yiaddr, 长度为 4 字节, 指示客户端分配到的 IP 地址

  • siaddr, 长度为 4 字节, 服务端 IP 地址, 用于 Bootstrap

  • giaddr, 长度为 4 字节, Relay agent 的 IP 地址

  • chaddr, 长度为 16 字节, 指示客户端的硬件地址

  • sname, 长度为 64 字节, 可选的指示 Server 的主机名称, 以 0x00 结尾

  • file, 长度为 128 字节, 引导文件名, 对于一些设备 (例如无盘设备) 可以通过网络来引导操作系统, 称为 PXE (Preboot Execution Environment), 尤其是一些无盘系统可以通过 DHCP 协议获取引导文件路径, 然后通过 TFTP 协议获取引导文件 (@see https://networkengineering.stackexchange.com/questions/60679/what-is-the-boot-file-name-flag-for-in-dhcp-message)

  • Options, 长度可变, 为 DHCP 选项参数列表

17.3 DHCP Client / Server 交互过程

DHCP 是应用层协议, 使用 UDP 作为传输层协议, DHCP Server 使用的端口号为 67, DHCP Client 使用的端口号为 68, DHCP Client 与 Server 的交互模式如下图所示 (当 Client 之前被分配过 IP, 它下次请求 DHCP Server 时可以带上上次被分配的地址, 这时下面的请求过程中的某些环节可以省略, 下图展示的是完整的, 全新的 Client 进行 DHCP 交互的过程):

DHCP Client 首先在本地物理网络上广播发送 DHCPDISCOVER 报文, 在 DHCPDISCOVER 报文中可以携带建议的 IP 地址 (比如它之前曾被分配过 IP 地址, 可以请求继续使用之前分配到的 IP) 以及相应的租期 (lease, 超过租期, 分配到的 IP 地址将被 DHCP Server 回收, 当然客户端可以在使用过程中请求对已分配到的 IP 进行续期), 如果 DHCP Server 和 DHCP Client 不在一个物理网段上, 则 DHCPDISCOVER 报文将由 Relay Agent 转交给 DHCP Server

所有收到请求的 DHCP Server 都可以回复 DHCPOFFER 报文, 在 yiaddr 字段中设置此次分配给客户端的 IP 地址, 还可以包括其它一些选项参数, DHCP Server 在分配 IP 地址之前, 应发送 ICMP [RFC 792] Echo Request 报文 (例如我们经常使用的 ping 命令就是基于 ICMP 协议) 来探测该 IP 地址当前是否被使用

客户端可能收到来自多个 DHCP Server 的 DHCPOFFER 报文, 客户端可自行选择其中的一个, 客户端解析 DHCPOFFER 报文, 并构造 DHCPREQUEST 报文, 其中必须 (MUST) 将 yiaddr 字段设置为所选取的 DHCPOFFER 报文所提供的 IP 地址, 并且必须在 DHCPREQUEST 报文的拓展字段中包含 server identifier 以指示其所选择的 DHCP Server, 之后客户端必须以广播的形式发送 DHCPREQUEST 报文, 因此所有的 DHCP Server 都将受到该报文, 而提供了 DHCPOFFER 而没有被客户端选择的 DHCP Server 也可以因此知道它们的 DHCPOFFER 信息没有最终被客户端使用, 客户端在发送 DHCPDISCOVER 报文之后应设置超时计时器, 如果在设定的时间区间内没有收到 DHCPOFFER 报文, 则应重传 DHCPDISCOVER 报文

服务端收到客户端的 DHCPREQUEST 报文之后, 如果 server identifier 是其自身, 即客户端选择了自己提供的 DHCPOFFER 信息, 若没有错误发生, 此时服务端应将客户端的硬件地址, 分配给其的网络协议参数 (如 IP 地址、子网掩码等信息) 存储到其所维护的存储器中, 并且回复 DHCPACK 报文, 客户端在收到 DHCPACK 报文后, 应最后进行一次检查, 向局域网主机广播发送免费 ARP 请求 (Gratuitous ARP, 免费 ARP 与普通 ARP 请求的区别是, 普通的 ARP 请求是因为不知道某个 IP 地址对应的主机的硬件地址是多少, 所以请求的目标 IP 字段值为所希望获得硬件地址的主机 IP 地址, 而免费 ARP 请求主要是为了检测 IP 地址冲突, 它在 ARP Request 中将目标 IP 地址设置为其自身的 IP 地址, 若收到了 ARP 应答, 则说明该 IP 地址已经有设备使用了, 即发生了 IP 地址冲突), 若没有 ARP 应答, 则说明分配到的 IP 地址是可用的, 此时整个配置过程顺利完成

服务端在收到 DHCPREQUEST 后, 若有错误发生, 比如参数不合法, IP 地址已经被分配给其它 Client 等, 应回复 DHCPNAK 消息, 客户端在收到 DHCPNAK 消息之后应重新发起上述的配置过程

客户端在发送 DHCPREQUEST 消息后, 若在设定的时候内既没有收到 DHCPACK 也没有收到 DHCPNAK 消息, 此时客户端应重新发送 DHCPREQUEST 消息

当客户端不再需要使用分配到的 IP 参数后, 可以发送 DHCPRELEASE 消息放弃其所分配到的 IP 地址, DHCP Server 可以回收该地址给其它申请 IP 地址的 Client 使用

17.4 使用 wireshark 观察 DHCP 交互过程

我们可以使用 wireshark 来直观地观察 DHCP 交互过程, 实验方法很简单, 关闭系统 WIFI, 然后重新打开, 此时设备将会自动发起 DHCP 请求, 由于设备之前曾在网络中分配过 IP 地址, 因此不需要从发送 DHCPDISCOVER 消息开始, 而是直接携带之前分配的 IP 地址发送 DHCPREQUEST 来请求 DHCP Server, 正常情况下, 在 wireshark 中将会看到 3 个 DHCP 消息, 如下所示

实际上只有一次 DHCPREQUEST 和一次 DHCPACK, 而 wireshark 之所以显示了 3 条 DHCP 消息是因为 DHCPREQUEST 是广播发送的, 因而自身也会收到这个广播包, 我们来详细看下 DHCPREQUEST 的结构

Transaction ID 正如我们在前面提到的, 它是由 DHCP Client 生成的随机值, 用于关联一次 DHCP 请求和 DHCP 应答, 在这里该随机值为 0x8fe1ffcd, Client 请求的时候会在 DHCP Option 中声明希望使用的 IP 地址 (192.168.199.117), 同时也会声明该 IP 地址的期望租期 (7776000s, 即 90 days)

再来观察 DHCPACK 的详情, 可以看到 DHCP Server 返回的 DHCPACK 中有很详细的参数, 比如 IP 地址, 路由/网关地址, DNS 地址等, 注意到尽管 DHCPREQUEST 中请求的 IP 租期是 90 days, 而实际上 DHCP Server 在 DHCPACK 中给出的租期为 12hour, 这取决于 DHCP Server 的配置

17.5 总结

更多关于 DHCP 协议的细节可以阅读 RFC 2131, DHCP 极大地简化了繁杂的网络参数配置过程, 尤其是对于大型网络, 如上万人的校园网或企业网, 如果由网络管理员手动为每个终端配置 IP 地址, 子网掩码, DNS 地址等参数, 该项工作的工作量将极为庞大, 而实际上, 连入网络的终端设备本身也是动态的, 设备可能关机或重启, 通过手动方式来管理非常不灵活, 除了节省工作量之外, DHCP 协议还实现了有限资源的合理分配, 例如可供分配的 IP 地址只有 100 个, 但可能有 150 甚至 200 个终端有上网需求, 但由于它们往往不是都同时接入网络, 使用 DHCP 机制可以使终端设备复用有限的 IP 地址, 当终端设备断开网络后可以将分配到的 IP 地址归还, DHCP Server 可将该地址分配给其它有需要的终端使用