聊到网络问题,我印象最深的一次是:大中午办公室突然有人喊“整个楼层只有我不能上网”,过去一看,右下角网络图标黄色感叹号,命令行里敲ipconfig,地址显示的是169.254.7.23。我第一反应不是查网线,而是下意识说了句:DHCP又出事了。这种场景,做过网络运维的人应该都不陌生。
DHCP(Dynamic Host Configuration Protocol)就是自动给设备发IP地址的协议,听起来很基础,但真正把它的报文、租约、中继、安全机制全部吃透的人并不多。这篇文章不照抄协议文档,我按自己实际排查和配置的经验,把DHCP从原理到报文、从配置到避坑完整拆一遍。适合刚入行的网络工程师、运维同学,以及那些“被DHCP坑过但不知道从哪下手”的开发朋友。
1. 先弄懂DHCP在干什么:不是“自动发IP”那么简单
1.1 为什么需要DHCP:手工填IP的日子有多痛
在没有DHCP的年代,每台电脑都需要手工写入IP地址、子网掩码、默认网关和DNS。看起来也不难,但问题在于:整个办公室几十台设备,规划好的IP段一旦变了一小段,所有人就得挨个改;打印机、门禁、摄像头这些固定设备还得单独记地址表。更要命的是,如果手一抖把IP填成了别人的,马上就是地址冲突,两个设备互相抢网络,表现就是一会儿能上网一会儿掉线。
DHCP解决的是“让设备自己问该用什么参数”的问题。它工作在应用层,基于UDP,客户端用68端口,服务器用67端口。通过广播和单播结合的方式,把IP、掩码、网关、DNS等参数一次性发给终端。注意,DHCP不只发IP,还会下发租约时间、域名、启动文件等,这也是后面PXE无盘启动能跑起来的基础。理解这一点,才能理解为什么很多时候“IP能拿到但上不了网”,问题可能出在网关或DNS选项上。
1.2 四步握手:你至少要能背出字母顺序
DHCP客户端和服务器之间的标准交互是四步:DISCOVER、OFFER、REQUEST、ACK。为了方便记忆,我通常把它说成“喊一嗓子—有人应答—我再确认—最后点头”。
第一步,客户端开机,源IP是0.0.0.0,目的IP是255.255.255.255,发一个广播DISCOVER,意思是“这个网段里有DHCP服务器吗?谁有IP给我用”。第二步,所有收到广播的服务器都会回一个OFFER,告诉它“我这有个地址,你要不要”。第三步,客户端可能同时收到多个OFFER,它会按某种规则选一个,然后再次广播REQUEST,告诉所有人“我决定用某个服务器给的地址”。这一步为什么还是广播?因为要通知其他服务器“我没选你们,你们把预留的地址收回去吧”。第四步,被选中的服务器收到REQUEST后,回复ACK确认。如果服务器觉得参数有问题,或者不想分配,会回NAK,客户端就得重新从DISCOVER开始。
这里有个容易被忽略的细节:DNS、网关这些参数,是在OFFER和ACK阶段带过去的,不是DISCOVER阶段。所以抓包时只盯IP是不够的,要把“选项”字段也一起看。
1.3 租约不是永久:T1和T2是怎么续的
DHCP分配出去的地址是有租期的。默认租期可以是几小时,也可以是一天,完全看服务器配置。客户端会在租期到达50%的时候,也就是T1时刻,向当初分配地址的服务器发单播REQUEST,尝试续租。如果没收到回复,它不会坐等,而是等到租期87.5%的时候,也就是T2时刻,重新发广播REQUEST,继续续租。再不行,租期过期后只能重新走一遍四步握手。
这个机制保证了IP资源不会永远被已经离开的设备占着,但也给排查留下了很多迷惑点。比如一个设备明明刚拿过IP,为什么过一会又发DISCOVER?可能是租期太短。一个楼层突然网络很卡,广播包占满链路?可能是全网设备同时进入了续租周期。所以租期配置并不是越大越好,也不是越小越安全,要根据终端的移动频率和地址池容量来权衡。
2. 拆开报文看细节:DHCP解析的抓包实操
2.1 在什么位置抓包才能抓到完整流程
我建议优先在客户端侧抓包,因为这样能同时看到客户端发出的DISCOVER/REQUEST,以及服务器返回的OFFER/ACK。用虚拟机是最方便的:把虚拟机网卡设为NAT模式或者桥接到一个独立VLAN,启动前先开始抓包。抓包工具我用得最多的是Wireshark,过滤条件直接写bootp或dhcp都可以,因为它把DHCP归到了Bootp协议族里。
如果想看到更原始的UDP交互,也可以写成udp.port == 67 or udp.port == 68。这两个端口一个是服务器监听口,一个是客户端监听口,能覆盖所有DHCP报文。要小心的是,如果电脑上跑着多个网卡,或者后台有某些软件占用68端口,抓包结果会非常乱。物理机上抓包时,防火墙有可能拦掉广播报文,建议先将防火墙临时关闭,或者用普通用户权限下能抓到的包来做参考,不必追求全部字段都完整。我踩过这个坑:一开始怎么抓都只有DISCOVER,没有OFFER,后来发现是被本机防火墙过滤了,白白折腾半小时。
2.2 一份实际包的关键字段,逐行看
这里拿一个典型的DISCOVER包来举例。以太网头里目标MAC是ff:ff:ff:ff:ff:ff,说明是广播。IP层里,源地址0.0.0.0,目的地址255.255.255.255。UDP层里,源端口68,目的端口67。这些都很直观。
进入DHCP层之后,有几个字段是排障核心。
第一个是Transaction ID,也就是xid。它是客户端生成的一个随机数,作用是关联整个四步流程。后面OFFER、REQUEST、ACK里的xid必须和DISCOVER一致,客户端才会认。如果抓包看到某个包的xid突然变了,说明它其实是另一轮交互。
第二个是Client MAC address。可能有人觉得以太网头里已经有MAC了,为什么报文内部还要带一遍?因为DHCP中继场景下,广播帧经过三层设备转发后,二层源MAC可能被改写,但DHCP报文内部的Client MAC字段原样保留,服务器要靠它来识别客户端身份。
第三个是Option 53,也就是DHCP Message Type。这是判断报文类型最权威的字段,值1是Discover,2是Offer,3是Request,4是Decline,5是Ack,6是Nak。看抓包时别只信Wireshark的显示名称,最终要以Option 53为准。
第四个是Option 50,Requested IP Address。客户端可能在这个字段里夹带“上次用过的地址”,表示希望继续用。如果服务器同意,就会在ACK里返回同样的地址;如果不同意,会在地址池里另选一个。
第五个是Option 55,Parameter Request List。这是客户端向服务器“点菜”,希望服务器下发哪些配置项。常见选项值:1是子网掩码,3是默认网关,6是DNS,15是域名,51是租约时间。服务器会尽量按照这个列表返回。
再看OFFER包和ACK包,关键字段变成“Your (client) IP Address”,也就是服务器准备分配的地址。OFFER里会带Option 51(租约时间)和Option 54(服务器标识)。ACK和OFFER的内容很接近,但消息类型不同。最容易踩坑的地方是:如果ACK里带的子网掩码、网关和实际网络规划不一致,客户端同样会“自认为拿到IP”,但业务完全不通。所以拿到IP不是终点,必须把ACK里的每个选项都对照一遍。
2.3 通过报文特征快速定位故障
抓包之后判断问题,说穿了就是“看第几个包丢了”。
如果只有DISCOVER,没有OFFER,要么这个链路里根本没有DHCP服务器,要么服务器虽然收到请求,但因为VLAN隔离、DHCP Snooping丢弃等原因,OFFER没回来。如果能看到OFFER,但客户端一直不发出REQUEST,那多半是客户端认为OFFER里的参数不对,比如分配的IP和它本地认为的网段冲突。如果REQUEST发出去了,但始终没有ACK,那问题一般在服务器端:地址池满了、服务器检测到IP冲突、或者中继回程路由不通。如果整个流程看起来完整,但下一秒客户端又重新从DISCOVER开始,那很可能是租约太短,或者服务器下发的参数让客户端立即判定无效。
我把这套规律写在笔记本上,排查时逐个对照,比漫无目的地重启设备高效得多。
3. 配置与实战:从单网段到跨网段
3.1 最小可用DHCP服务器配置长什么样
Linux下常见的开源DHCP服务主要有ISC dhcpd和Kea,二者配置风格不同,但核心思路一致。下面是一个最小配置文件示例:
default-lease-time 600; max-lease-time 7200; option domain-name-servers 223.5.5.5, 1.1.1.1; option subnet-mask 255.255.255.0; subnet 192.168.10.0 netmask 255.255.255.0 { range 192.168.10.100 192.168.10.200; option routers 192.168.10.1; }这里default-lease-time 600表示默认租约600秒,max-lease-time 7200表示最大租约7200秒。测试环境把租约调短,可以更快看到续租过程;生产环境一般建议至少几小时。range指定可分配的地址池,option routers下发默认网关。DNS可以在全局区写,也可以在子网里覆盖,子网内的优先级更高。
启动服务后,不要急着说“配好了”,一定要看日志。在systemd系统上,用journalctl -u dhcpd,或者直接看/var/log/messages,会看到类似这样的记录:DHCPDISCOVER from ...、DHCPOFFER on ...、DHCPACK to ...。如果日志里没有任何DISCOVER,说明请求根本没到服务器,问题在链路或中继;如果只有DISCOVER和OFFER,没有ACK,那往往是客户端没接受地址。
3.2 交换机/路由器上的地址池配置与中继
企业网络里,DHCP服务通常不会落在终端侧,而是放在核心交换机或路由器上。各家厂商的CLI略有差异,但逻辑是一样的:创建地址池、指定网段、排除保留地址、配置网关和DNS,最后启用服务。
这里必须提一个重要概念:DHCP Relay。DHCP的DISCOVER是广播包,广播不能跨三层转发。如果客户端和服务器不在同一个VLAN,网关设备就必须充当“中间人”。配置方式一般是在客户端所在VLAN的三层接口上,设置一个ip helper-address或者dhcp relay指向服务器地址。
转发过程是这样的:客户端发广播DISCOVER,网关收到后,把自己的接口地址作为源地址,单播转给DHCP服务器。服务器回复OFFER,先回给网关,网关再转回客户端的MAC。注意,中继过程中,网关会把报文里的giaddr(Gateway IP Address)字段改成自己接口的IP,这样服务器才能知道该从哪个子网的地址池里挑地址。所以如果看到服务器分配的IP网段完全不对,十有八九是Relay地址配错了,或者giaddr字段被奇怪的配置覆盖了。
3.3 地址规划时最容易犯的错
地址池、保留地址、静态绑定这三样东西必须分开。我的习惯是:先把网关、交换机管理IP、打印机、门禁这类固定设备放进保留段,然后用range给动态终端分配。很多人在一个子网里既想给手机分地址,又想给服务器留固定IP,结果把所有地址都塞进range里,导致固定设备无穷无尽地冲突。
更隐蔽的错误是地址池范围和子网掩码不一致。比如子网明明是/24,range却写了/23里的地址。客户端可能确实拿到了IP,也能通过DHCP报文看到网关和掩码,但ARP和路由会因为“不在同一子网”产生奇怪的间歇性故障。我遇到过类似案例:某办公室的所有终端都能拿到IP,但就是无法访问打印机,查到最后才发现DHCP的range跨到了另一个子网,把网关地址也给分配出去了。配置顺序上,先写排除地址,再写range,是一个能明显降低事故率的习惯。
4. 那些年踩过的坑:常见故障与排查
4.1 看到169.254.x.x,先别慌
Windows系统在DHCP失败后,会自动给自己分配一个169.254.x.x的APIPA地址,这表示“本地链路可用,但没有DHCP响应”。这个地址不是运营商分配的,也不是服务器故障标志,它只是操作系统的一种自我保护。
遇到这种情况,重点排查方向是:客户端到DHCP服务器之间链路是否通;服务器作用域是否激活;中继配置是否正确;服务器日志里有没有收到DISCOVER。注意,不要一上来就重启电脑,因为重启会让DISCOVER过程一闪而过,抓包更容易漏。正确做法是先执行地址释放:在命令行里运行ipconfig /release,再运行ipconfig /renew,观察完整流程。如果命令提示“操作失败,需要提升权限”,记得用管理员身份打开终端。
4.2 地址冲突的隐形杀手:DHCP Snooping绑定表
网络里出现两个设备抢同一个IP,最常见的原因是有人私接路由器,把上一级网络里的DHCP服务带进了当前局域网;也有可能是有人手工改了一个和网关冲突的IP。交换机上开启DHCP Snooping以后,会建立一张绑定表,记录每个端口的IP、MAC、VLAN和租期。
关键点在于信任端口和非信任端口的划分。连接DHCP服务器的端口必须设置为信任端口,用户端口默认是非信任端口。非信任端口如果收到DHCP OFFER或ACK,交换机会直接丢掉,因为它认为“只有信任端口才有资格下发地址”。同时,还可以配置限制非信任端口的DHCP报文速率,防止有人用大量伪造DISCOVER报文把服务器地址池耗尽,也就是所谓的DHCP饿死攻击。
这里有个经典的操作失误:有些人开启Snooping之后忘了把服务器上联口设成信任端口,结果全网设备立刻拿不到IP。日志里看到大量OFFER被丢弃,还以为是服务器坏了。实际上只要把上联口改成信任端口,问题立即消失。配置之前最好先画一张清晰的端口信任图,再动手。
4.3 地址池耗尽与租约风暴怎么快速确认
终端数量一多,地址池很快会告警。服务器日志里如果出现大量DISCOVER但没有可用地址的记录,基本可以判断是地址池耗尽。解决办法有四个方向:确认在线终端数是不是真的超过了地址池容量;合理调整租约时间;把固定设备改成静态绑定,减少对动态池的占用;必要时扩容地址池或拆分子网。
4.4 一组能救命的排查命令
下面这张表是我日常排查时常用的命令,覆盖客户端、服务端和交换机三个视角。
| 场景 | 命令/操作 | 能确认什么 |
|---|---|---|
| Windows客户端 | ipconfig /all | 是否拿到地址、租约到期时间、DHCP服务器IP |
| Windows释放/续租 | ipconfig /release再ipconfig /renew | 完整触发四步流程,便于抓包 |
| Linux客户端 | dhclient -v eth0或nmcli device reapply eth0 | 观察客户端请求过程 |
| 命令行抓包 | tcpdump -i eth0 port 67 or port 68 -n | 快速查看报文字段 |
| DHCP服务端日志 | journalctl -u dhcpd或查看/var/log/messages | 服务器是否收到DISCOVER、是否发出ACK |
| 交换机绑定表 | show dhcp snooping binding | 确认绑定表是否正常生成 |
| 交换机冲突列表 | show ip dhcp conflict | 查看设备曾检测到的IP冲突记录 |
不同厂商的命令写法可能不一样,但排查思路是通用的。还有一个容易被忽略的小细节:ipconfig /renew在Windows上如果没有管理员权限,会提示操作失败,这不是命令不对,而是权限不够。
4.5 一次“会议室断网”的排查实录
有一次会议室突然集体断网,设备能拿到IP,能Ping通网关,就是上不了外网。我先在会议室交换机上看了DHCP Snooping绑定表,发现所有终端的MAC都正常,说明地址分配没问题。然后抓包看ACK报文,发现DNS选项指向了一个已经废弃的旧地址,而网关接口上新配的DNS地址并没有生效。
原因很快找到:DHCP地址池里的DNS是旧的,网关接口上的新DNS只影响三层转发,不影响DHCP下发。这提醒我,改了网络设备的DNS配置,不代表终端就会自动跟着改,必须同步更新DHCP地址池里的对应选项。后来我把这个检查和“释放地址再续租”一起写进了网络变更的标准流程。每次改完配置,都强制重取一次地址,再验证一次网关和DNS,问题会少很多。
做网络问题处理做得越久,越觉得DHCP是个“入门容易精通难”的协议。它概念上不到一页纸,但真正的故障类型能写一本书。很多看似玄学的网络问题,最后追下去都在DHCP报文层面找到了答案。
我现在带新人的第一件事,不是教他怎么敲配置命令,而是让他开抓包工具,亲手抓一轮从DISCOVER到ACK的完整流程,对着字段讲一遍。能把四步握手和关键Option讲顺,再让他去碰中继和Snooping,基本就不会翻车。最后送大家一个实用小技巧:配置完DHCP之后,别急着宣布完成,先把客户端的地址释放一次、重新获取一次,然后拔掉网线换个网段再试一次。整个过程只要三十秒,但能提前发现大部分配置错误。愿你下次听到“我上不了网”的时候,心里已经有一套清晰的排查路径了。