本文实践环境:
Operating System: Ubuntu 20.04.3 LTS
Kernel: 5.4.0-96-generic
Architecture: arm64
补充:部分抓包内容是在 macOS Monterey 12.2 上进行的
前面介绍了 IP 相关基础知识,也谈到如何查看 IP,但 IP 如何配置呢?
这一篇学习笔记,笔者就来梳理一下 IP 的配置和 DHCP 协议,并通过对 DHCP 协议动态分配 IP 的抓包实战,理解其基本原理和操作流程。
这里插个题外话,关于机器的实践环境,可以通过 uname -a
命令获取,其实还可以通过 hostnamectl
拿到,比如本文使用的虚机环境:
root@ubuntu-linux-20-04-desktop:~# hostnamectl
Static hostname: ubuntu-linux-20-04-desktop
Icon name: computer-vm
Chassis: vm
Machine ID: 3787c48ab74047b28638ef2ddc97be6c
Boot ID: 38dd0939bd7547b1846699353d58f44a
Virtualization: parallels
Operating System: Ubuntu 20.04.3 LTS
Kernel: Linux 5.4.0-96-generic
Architecture: arm64
root@ubuntu-linux-20-04-desktop:~#
IP 配置
前面介绍了 net-tools 和 iproute2 两套体系,那么这里笔者就用两种方式来配置一下 IP。
我们先使用 iproute2 来操作一遍,不知道如何配置?没关系,help 命令用起来,ip addr help
结果如下:
root@ubuntu-linux-20-04-desktop:~# ip addr help
Usage: ip address {add|change|replace} IFADDR dev IFNAME [ LIFETIME ]
[ CONFFLAG-LIST ]
ip address del IFADDR dev IFNAME [mngtmpaddr]
ip address {save|flush} [ dev IFNAME ] [ scope SCOPE-ID ]
[ to PREFIX ] [ FLAG-LIST ] [ label LABEL ] [up]
ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]
[ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]
[ label LABEL ] [up] [ vrf NAME ] ]
ip address {showdump|restore}
IFADDR := PREFIX | ADDR peer PREFIX
[ broadcast ADDR ] [ anycast ADDR ]
[ label IFNAME ] [ scope SCOPE-ID ] [ metric METRIC ]
SCOPE-ID := [ host | link | global | NUMBER ]
FLAG-LIST := [ FLAG-LIST ] FLAG
FLAG := [ permanent | dynamic | secondary | primary |
[-]tentative | [-]deprecated | [-]dadfailed | temporary |
CONFFLAG-LIST ]
CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG
CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]
LIFETIME := [ valid_lft LFT ] [ preferred_lft LFT ]
LFT := forever | SECONDS
TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |
bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | lowpan |
gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti |
nlmon | can | bond_slave | ipvlan | geneve | bridge_slave |
hsr | macsec | netdevsim }
root@ubuntu-linux-20-04-desktop:~#
可以看到 ip address
增删改都是齐全的,由于演示环境下 IP 地址默认已经配置好了,笔者就演示一下如何重新修改现有 IP 地址。
从上述结果中,可以看到 ip addr replace
或 ip addr change
比较符合我们的需求,我们使用这两个命令来试试行不行。
ip address {add|change|replace} IFADDR dev IFNAME [ LIFETIME ] [ CONFFLAG-LIST ]
root@ubuntu-linux-20-04-desktop:~# ip addr replace 192.168.31.68/24 broadcast 192.168.31.255 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:bf:0a:cf brd ff:ff:ff:ff:ff:ff
inet 192.168.31.66/24 brd 192.168.31.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.31.68/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet6 fe80::21c:42ff:febf:acf/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu-linux-20-04-desktop:~#
当前 IP 是 192.168.31.66,笔者想通过 ip addr replace
将其改成 192.168.31.68,不过结果有点意外:原来的 IP 并没有变化,而是增加了一个 192.168.31.68 的新地址。
接着,笔者继续使用 ip addr add
命令来试一下,这个还是比较符合预期的,相同的地址不能重复添加。
root@ubuntu-linux-20-04-desktop:~# ip addr add 192.168.31.69/24 broadcast 192.168.31.255 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:bf:0a:cf brd ff:ff:ff:ff:ff:ff
inet 192.168.31.66/24 brd 192.168.31.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.31.68/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet 192.168.31.69/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet6 fe80::21c:42ff:febf:acf/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu-linux-20-04-desktop:~# ip addr add 192.168.31.69/24 broadcast 192.168.31.255 dev eth0
RTNETLINK answers: File exists
root@ubuntu-linux-20-04-desktop:~#
那么,ip addr change
命令能否达到最初的预期呢?
root@ubuntu-linux-20-04-desktop:~# ip addr change 192.168.31.70/24 broadcast 192.168.31.255 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:bf:0a:cf brd ff:ff:ff:ff:ff:ff
inet 192.168.31.66/24 brd 192.168.31.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.31.68/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet 192.168.31.69/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet 192.168.31.70/24 brd 192.168.31.255 scope global secondary eth0
valid_lft forever preferred_lft forever
inet6 fe80::21c:42ff:febf:acf/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu-linux-20-04-desktop:~#
也没有,192.168.31.66 这个地址依然还在,而 192.168.31.70 却是新增的。所以,是不是我们对 ip addr replace/change
两个命令的理解是错的呢?
从这篇答疑[1]中,有小伙伴也遇到了类似的问题,并且给出了 Ta 的思考:
ip addr replace/change
并不是针对网卡进行更新的,而是将 网卡+IP 同时作为判断条件进行修改,即不能改 IP,但可以改其他参数。
如何要换 IP,则需要先删除,再添加。
按照这个思路,笔者测试一下,确实可行:
root@ubuntu-linux-20-04-desktop:~# ip addr del 192.168.31.68/24 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr del 192.168.31.69/24 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr del 192.168.31.70/24 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr del 192.168.31.66/24 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:bf:0a:cf brd ff:ff:ff:ff:ff:ff
inet6 fe80::21c:42ff:febf:acf/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu-linux-20-04-desktop:~# ip addr add 192.168.31.68/24 broadcast 192.168.31.255 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:bf:0a:cf brd ff:ff:ff:ff:ff:ff
inet 192.168.31.68/24 brd 192.168.31.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::21c:42ff:febf:acf/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu-linux-20-04-desktop:~#
这里有两点需要说明:
1)这种通过命令来修改 IP 的方式平时是不怎么使用的,因为网卡重启之后,这个地址会被重置,所以正常情况下都会通过界面或配置文件进行修改;
Ubuntu 下这个配置文件在
/etc/netplan/
目录下,而 CentOS 对应的路径为/etc/sysconfig/network-scripts/
。
比如 Ubuntu 下的配置:root@ubuntu-linux-20-04-desktop:/etc/netplan# pwd /etc/netplan root@ubuntu-linux-20-04-desktop:/etc/netplan# ls 00-installer-config.yaml root@ubuntu-linux-20-04-desktop:/etc/netplan# cat 00-installer-config.yaml # This is the network config written by 'subiquity' #network: # ethernets: # eth0: # dhcp4: true # version: 2 network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no addresses: [192.168.31.66/24] gateway4: 192.168.31.1 nameservers: addresses: [192.168.31.1] root@ubuntu-linux-20-04-desktop:/etc/netplan#
2)通过前面操作,我们可以看到,一个网卡下可以有多个 IP,并且后面添加的 IP 都有一个 secondary 标识,表示这个是备选的。关于 secondary IP,有机会笔者单独再总结一下。
虽然 ifconfig
整个体系已经过气,但使用的人还有很多,所以这里也使用 ifconfig
进行验证:
root@ubuntu-linux-20-04-desktop:~# ifconfig eth0 192.168.31.68/24
root@ubuntu-linux-20-04-desktop:~# ifconfig -a
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.31.68 netmask 255.255.255.0 broadcast 192.168.31.255
inet6 fe80::21c:42ff:febf:acf prefixlen 64 scopeid 0x20<link>
ether 00:1c:42:bf:0a:cf txqueuelen 1000 (Ethernet)
RX packets 63152 bytes 8684191 (8.6 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6633 bytes 849547 (849.5 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 781 bytes 63683 (63.6 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 781 bytes 63683 (63.6 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
root@ubuntu-linux-20-04-desktop:~#
其实不管用哪种方式,修改之后,不一定能联上互联网,因为默认路由可能被修改了:
root@ubuntu-linux-20-04-desktop:~# ip route
192.168.31.0/24 dev eth0 proto kernel scope link src 192.168.31.68
root@ubuntu-linux-20-04-desktop:~# ping www.baidu.com
ping: connect: Network is unreachable
root@ubuntu-linux-20-04-desktop:~#
为此,我们需要添加一条默认路由:
root@ubuntu-linux-20-04-desktop:~# ip route add default via 192.168.31.1 dev eth0
root@ubuntu-linux-20-04-desktop:~# ip route
default via 192.168.31.1 dev eth0
192.168.31.0/24 dev eth0 proto kernel scope link src 192.168.31.68
root@ubuntu-linux-20-04-desktop:~# ping www.baidu.com
PING www.a.shifen.com (14.215.177.39) 56(84) bytes of data.
64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=1 ttl=53 time=26.3 ms
64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=2 ttl=53 time=29.5 ms
...
到这里,笔者通过使用命令行的方式,讲解了如何配置 IP ,但这里有一个问题:你是怎么知道这个 IP 的呢?
对于公司的网络,我们可以询问网络管理员,而家里使用的路由器,则可以登录路由器后台查看。
上面演示的这个例子,最初 192.168.31.66/24 这个 IP 地址是计算机插上网线后,通过 DHCP 协议自动获取的。既然知道了对应的网段,我们便可以将其改成同一个网段中其他还未使用的 IP 了。
那么,DHCP 又是什么,它是如何实现自动分配 IP 的呢?
DHCP
概述
DHCP,全称 Dynamic Host Configuration Protocol,动态主机配置协议。
该协议分为客户端和服务端,一般的路由器(比如家里的无线路由器)就包含了 DHCP Server 的功能,而我们的智能手机,平板,电脑等就是一个个 DHCP Client。
当管理员在 Server 端配置一段共享 IP 网段后,只要 Client 端插上网线或连上无线,就可以获得一个 IP 地址,比如下面两张图分别是笔者的手机和电脑自动获取的 IP (在不同的无线网络下):


与上面手动配置 IP 命令相比,这种方式基本上无需客户端使用人员了解网络的基本概念,十分高效方便。
而路由器 Server 端有如下的设置界面,可以设置路由器 IP、子网掩码、共享IP的起止段、租约时间等。


其实 DHCP 并不是第一个动态分配 IP 的协议,其前身是 BOOTP(Bootstrap Protocol)协议。但 DHCP 比 BOOTP 更强大,更健壮,当然也更复杂。这里有一个简单的比较:dhcp vs bootp[2],可以进一步了解。
DHCP 头结构
这里笔者以 RFC 2131 中的内容作为参考进行说明,如下图所示:

相关参数说明:
- op:消息类型代码,1 代表请求(BOOTREQUEST),2 代表回复(BOOTREPLY)。
- htype:hardware address type,取值如 10mb 以太网,ATM,IEEE02等。
- hlen:hardaware address length,如 10mb 以太网的硬件 MAC 地址长度为 6 个字节。
- hops:跳数,如果有中继代理服务器,则用来寻找 DHCP Server,而客户端都设置为 0。
- xid:transaction ID,一个随机数,用来匹配请求和响应,即该值相同,则属于同一个 DHCP 请求/响应周期。
- secs:客户端从第一次发出请求到当前这个请求/响应所消耗的时间。
- flags:标志位,2 个字节长度,目前只使用第 0 位,后面 15 位(1-15)保留。第 0 位值为 0 表示客户端支持单播,为 1 则表示不支持,下文会有相关描述。
- ciaddr:client ip address,客户端 IP 地址,只有当客户端处于 BOUND,RENEW 或 REBINDING 状态并能响应 ARP 请求时才填写。也就是说,客户端一开始没有 IP 地址的话,填 0.0.0.0。
- yiaddr:your ip address,DHCP Server 提供给客户端的 IP 地址。
- siaddr:server ip address,服务端 IP 地址,在 DHCP Offer 和 DHCP ACK 的响应中返回,下文将会有样例展示。
- giaddr:relay agent ip address,中继代理的 IP 地址,没有中继代理,则填 0.0.0.0。
- chaddr:客户端的硬件地址,以太网填写 MAC 地址。
- sname:服务器的主机名。
- file:DHCP Client 所使用的启动文件,在某些场景下需要从 DHCP Server 端下载文件,然后在本地启动相关程序。
- options:可选参数,用来扩展 DHCP 的数据包,提供更多功能,涉及到的参数笔者会在下文的抓包实战中阐述。
首次租用
DHCP Client 申请 IP 时,一般有四个步骤:
- DHCP Discover:数据包发现
- DHCP Offer:数据包提供
- DHCP Request:数据包请求
- DHCP Acknowledgement:数据包确认
笔者参考《图解 TCP/IP》上的原图,也画了一个流程图:

使用时序图来表示:

下面笔者就通过 Wireshark 抓包,来分析一下整个流程,下图是笔者的 Macbook Pro 笔记本连接 Wifi 时抓的包。

从该截图可以看出以下内容:
- 一共有 5 个包,其中 DHCP Discover 请求发了两次,包中内容一致,唯一的区别是序号为 282 的包 secs 值 2,笔者理解第一个包没有收到响应,所以重发了。
- DHCP Discover 和 DHCP Request 用的广播,因为 Source IP 为 0.0.0.0,而 Destination IP 为 255.255.255.255,而二层的 Destination 地址为 ff:ff:ff:ff:ff:ff。
- DHCP Offer 和 DHCP ACK 用的是单播。
- DHCP 是一个应用层协议,其下面的传输层为 UDP 协议,并且指定了 68(客户端) 和 67(服务端) 两个端口。
结合前面对 DHCP 头结构的描述,我们来看包中的内容就能比较容易理解了,这里的 Message Type
为 1,Hardware Type
为 Ethernet,Bootp flags
为 Unicast(单播),而相关 IP 地址目前客户端都不知道,所以都是 0.0.0.0,但 MAC 地址是有的,这也是 DHCP 服务器唯一能区分的标识了。
再看这张 DHCP Offer 包的截图,跟上面对比,可以发现差别:比如服务器分配的 IP 为 192.168.31.136,其他的字段这里就不再一一赘述了。

在这些通用字段的下方,则是相关扩展字段(Option),下面这张截图是 DHCP Discover 的 Options。

每一个 Option 都有一个编码,比如这里的 DHCP Message Type
为 53,这些编码可以在 RFC 2132 中找到其定义,而 DHCP Message Type
的取值定义如下:
Value Message Type Description
----- ------------ ------------
1 DHCPDISCOVER 客户端发现
2 DHCPOFFER 服务端提供
3 DHCPREQUEST 客户端请求
4 DHCPDECLINE 客户端拒绝(比如冲突的 IP)
5 DHCPACK 服务端响应客户端的请求
6 DHCPNAK 客户端拒绝服务器为其配置的参数
7 DHCPRELEASE 客户端取消租约
8 DHCPINFORM 客户端有 IP 时向服务器请求配置参数

再对比上面这张 DHCP Offer 的 Options 截图,笔者简单梳理一下几个重要的 Option:
- Client identifier (61) :客户端的唯一标识,服务端用该值来作为地址绑定的区分标识。
- Parameter Request List (55) :请求参数列表,即客户端想从服务端获得的配置参数。
- IP Address Lease Time (51):客户端想获得的租期或服务端提供的租期,比如这里服务端提供的租期为 12 小时。
- Renewal Time Value (58):客户端重新续约的时长,即客户端多长时间后要进行续约操作,一般为租期的一半,比如这里的 6 小时。
- Rebinding Time Value (59) :客户端重新绑定的时长,即续约失败,那么过了该时间(比如这里的 10 小时)后,则会重新发起广播,申请新的 IP 地址(与第一次申请流程相同)。
- Requested IP Address (50) :客户端希望得到的 IP 地址,这个在下面的 DHCP Request 包截图中会有展示。
- ......
下面是第三、四步,DHCP Request 和 DHCP Ack 抓包的截图:



续租
前面也提到了续租,续租的意思就是客户端继续租用当前正在使用的 IP 地址。从流程上讲,这里不再需要 DHCP Discover 和 DHCP Offer 两步,即只有后面的 DHCP Request 和 DHCP Ack ,用时序图来表示,即:

这里笔者将路由器中的租约时间改成了 300 秒,然后对当前笔记本进行抓包验证,截图如下:
说明:续租的 IP 地址与上面的不一致,是因为的笔者切换了不同的网络,所以分配的 IP 有变化。


从 Request 中可以看到 Client IP address
为 192.168.31.50,即对该现有 IP 进行续约。
从 Ack 中可以看到,租约为 5 分钟,续约周期为 2 分钟 14秒,而第一次续约与第二次续约的差值正好是 134 秒(即 170 - 36,网络包列表第二列 Time 的耗时),符合预期。
DHCP Offer/Ack 多播
前面的抓包实战中,DHCP Offer/Ack 是单播,但笔者在介绍 DHCP 头部结构时提到,flags 标志位决定了 DHCP Offer/Ack 是单播还是多播。
笔者这里用 Ubuntu 虚拟机的 IP 分配为例,抓包看一下:

咦,这里变成了多播,为啥 MacBook Pro 笔记本是单播,而 Ubuntu 虚拟机变成了多播呢?
要想找到原因,还得回到 RFC 2131 规范文档:
Normally, DHCP servers and BOOTP relay agents attempt to deliver
DHCPOFFER, DHCPACK and DHCPNAK messages directly to the client using
uicast delivery. The IP destination address (in the IP header) is
set to the DHCP 'yiaddr' address and the link-layer destination
address is set to the DHCP 'chaddr' address. Unfortunately, some
client implementations are unable to receive such unicast IP
datagrams until the implementation has been configured with a valid
IP address (leading to a deadlock in which the client's IP address
cannot be delivered until the client has been configured with an IP
address).
A client that cannot receive unicast IP datagrams until its protocol
software has been configured with an IP address SHOULD set the
BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
DHCPREQUEST messages that client sends. The BROADCAST bit will
provide a hint to the DHCP server and BOOTP relay agent to broadcast
any messages to the client on the client's subnet. A client that can
receive unicast IP datagrams before its protocol software has been
configured SHOULD clear the BROADCAST bit to 0. The BOOTP
clarifications document discusses the ramifications of the use of the
BROADCAST bit [21].
A server or relay agent sending or relaying a DHCP message directly
to a DHCP client (i.e., not to a relay agent specified in the
'giaddr' field) SHOULD examine the BROADCAST bit in the 'flags'
field. If this bit is set to 1, the DHCP message SHOULD be sent as
an IP broadcast using an IP broadcast address (preferably 0xffffffff)
as the IP destination address and the link-layer broadcast address as
the link-layer destination address. If the BROADCAST bit is cleared
to 0, the message SHOULD be sent as an IP unicast to the IP address
specified in the 'yiaddr' field and the link-layer address specified
in the 'chaddr' field. If unicasting is not possible, the message
MAY be sent as an IP broadcast using an IP broadcast address
(preferably 0xffffffff) as the IP destination address and the link-
layer broadcast address as the link-layer destination address.
简单来说,就是客户端支不支持,客户端高级一点支持的话就可以单播搞定,而有的客户端不支持,所以只能走多播群发路线。
其他说明
最后再补充两点:
1、因为 DHCP Discover/Request 是多播,按理来说,同一个网段里其他设备上线,那么当前这台笔记本机器上也是能抓包看到的。

理论终归是理论,还需实践来检验,这里笔者通过手机联网来验证:

2、前面提到 DHCP 是 BOOTP 协议的增强版,在 Wireshark 抓包时,除了使用 dhcp 进行包过滤,还可以通过 bootp 过滤,也能得到相同的结果,比如在笔记本电脑上对手机联网的抓包:

参考资料
- 趣谈网络协议,by 刘超
- Wireshark数据包分析实战(第3版),by Chris Sanders
- RFC 2131
- RFC 2132