从零搭建DHCP服务:Wireshark抓包DORA与故障排查实战

📅 发布时间:2026/10/18 9:09:27
从零搭建DHCP服务:Wireshark抓包DORA与故障排查实战
1. 这次实验的起点手动配IP到什么时候是个头先说个真事。我在某公司帮忙整理办公网络时整个办公室四十多台电脑IP全是手动填的。新来一台笔记本要上网得先查地址表找到没被占用的IP再手动填网关和DNS。手机Wi-Fi倒还好路由器自己会发但固定工位的有线终端全走静态。某天财务那边两台电脑同时报IP冲突我去看一个填了.110一个填了.108结果某个打印机恰好占用了.110段里的地址——其实谁也没乱填就是设备多了以后地址表对不上了。当时我就想这活再这么干下去早晚出大乱子。DHCPDynamic Host Configuration Protocol动态主机配置协议就是用来解决这个问题的。它的核心作用是让终端设备开机后自动从服务器获取IP地址、子网掩码、默认网关和DNS等网络参数免去人工逐台配置的麻烦。我写这篇实验记录就是想把从零搭建DHCP服务、抓包观察完整交互流程、再到排掉三个真实故障的整个过程完整复盘一遍给想搞懂DHCP到底怎么工作、以及准备在真实网络里部署DHCP的朋友做个参考。这次实验是在某实验室的隔离网络里做的没有接入互联网和公司生产环境可以随便折腾。实验的目的很明确第一用一台Linux服务器搭好DHCP服务让Windows和Linux两种客户端都能自动拿到地址第二用Wireshark把DHCP交互的完整报文抓下来逐条看懂第三人为制造几个典型故障练一遍排查思路。整个过程走下来我对DHCP的理解比光看书深了不是一星半点尤其是抓包那一步原来很多想当然的细节都是在报文里才看明白的。2. 实验拓扑与准备一台Linux服务器、两台终端和一根抓包网线2.1 拓扑设计和网段规划实验网络很小但该有的角色都得有。我用了一台某品牌的旧台式机做DHCP服务器装的是CentOS 7内核3.10有点老但跑DHCP绰绰有余两台客户端一台Windows 10一台Ubuntu 18.04另外还有一台装了Wireshark的笔记本专门用来抓包。所有设备通过一台某厂商的傻瓜交换机互联整个网络是一个独立广播域不与外界互通。地址规划是这样的网段用192.168.80.0/24服务器固定IP 192.168.80.1网关和DNS都指向服务器本身。规划地址池的时候特意留出了前50个地址.1到.50方便以后给打印机、门禁控制器这类需要固定IP的设备做保留。地址池范围设为192.168.80.100到192.168.80.200共101个可用地址足够覆盖实验里那几台设备。提示规划地址池时一定要留出保留段别把整个网段都扔进range里。真实生产环境里总有打印机、NAS、摄像头这些需要静态绑定的设备如果地址池顶着网段边界用后面做保留地址时会非常痛苦。2.2 安装DHCP服务版本选择和安装过程Linux下最常见的DHCP服务端是ISC DHCP就是dhcpdCentOS 7的默认软件源里直接有。我本来想用较新的Kea DHCP官方文档看了一遍功能确实更现代但考虑到实验重点是理解协议交互ISC DHCP的配置更经典参考资料也多排错更容易所以选了dhcpd。安装命令很简单yum install -y dhcp systemctl enable dhcpd这里有个小坑装完dhcp包后/etc/dhcp/dhcpd.conf默认是一个空文件实际上几乎为空你需要用模板文件覆盖或者手动创建。所以我第二步就是备份默认配置然后从零写配置cp /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak vim /etc/dhcp/dhcpd.conf注意到一个问题dhcpd启动时默认监听eth0这张网卡。如果服务器有多张网卡需要在/etc/sysconfig/dhcpd里指定监听哪张。我实验里服务器只有一张网卡eth0所以不用额外处理。但这在生产环境是高频踩坑点先记下后面故障排查里还会提到。2.3 Wireshark抓包准备抓DHCP要听哪个网段Wireshark抓DHCP有个讲究。DHCP客户端在没有拿到IP之前源地址是0.0.0.0目的地址是255.255.255.255这是广播包。所以抓包电脑只要和DHCP客户端处在同一个广播域里就能看到所有DHCP报文。我把抓包笔记本也接到那台傻瓜交换机上选择监听的是有线网卡抓包过滤条件先写一个粗的udp.port 67 || udp.port 68DHCP服务端监听UDP 67端口客户端监听UDP 68端口这个过滤条件能把所有DHCP报文都筛出来。等客户端开始获取地址时我再在Wireshark里把这个过滤条件调得更精细按DHCP消息类型分类查看。3. dhcpd.conf逐项拆解每个参数为什么这么写3.1 完整配置文件和行级注释先贴出我在实验里用的完整dhcpd.confoption domain-name lab.internal; option domain-name-servers 192.168.80.1; default-lease-time 600; max-lease-time 7200; authoritative; subnet 192.168.80.0 netmask 255.255.255.0 { range 192.168.80.100 192.168.80.200; option routers 192.168.80.1; option subnet-mask 255.255.255.0; option broadcast-address 192.168.80.255; }展开讲讲每个参数的作用。option domain-name-servers指定DNS服务器地址客户端拿到DHCP分配的地址后会使用这个DNS这里指向服务器自己因为我这台服务器还兼职跑dnsmasq做缓存DNS。default-lease-time是默认租期单位是秒我设了600秒10分钟实验嘛租期短一点更方便观察续租过程。max-lease-time是客户端主动请求更长租期时允许的上限设成7200秒2小时。authoritative;这行容易被忽略但实际上非常重要。它告诉DHCP服务器我就是这个网段的权威DHCP服务器。当客户端带着旧地址来续租而这个地址不在我的地址池里时如果是authoritative模式我会回复NAK让客户端重新走完整的获取流程如果不是authoritative服务器会保持沉默客户端就一直等直到超时重新Discover。生产环境里如果不加这行可能出现换了一台服务器但客户端一直用着旧网段地址不更新的情况加了这个声明地址分配才能立刻干净利落。3.2 地址池和保留地址的取舍逻辑range声明定义了动态分配的范围。我把它设在192.168.80.100到200为什么不是从.2开始前面说了要留出一段做固定地址。真实场景中打印机、IP电话、门禁主机这类设备不适合用DHCP动态分配因为IP变了业务就断了。虽然DHCP支持按MAC地址做固定绑定但那属于class和host配置实验里我先不做那么复杂只把保留段预留出来算是给以后留条路。对于服务器、网络设备这些基础设施我的原则是要么设保留地址DHCP reservation要么干脆手动配静态IP。实验里的服务器就是手动配的静态IP因为所有客户端都要通过它拿地址如果它自己的IP是DHCP动态分配的那就先有鸡还是先有蛋了。3.3 配置语法验证启动前必须做这一步配置写完后在重启服务之前一定先做语法检查。dhcpd给了专门的工具dhcpd -t -cf /etc/dhcp/dhcpd.conf-t是test mode只测试配置文件语法和语义不实际启动服务。我第一次写配置时漏掉了subnet闭合的大括号执行这条命令后就报错了unexpected token }这种低级错误如果直接重启服务服务起不来还得回头看日志先在命令行里拦住它效率最高。确认语法没问题后启动服务并查一下状态systemctl start dhcpd systemctl status dhcpd ss -ulnp | grep dhcpd第三条命令看UDP监听能看到dhcpd监听在0.0.0.0:67说明服务已经起来了。这一步我习惯做成固定动作改配置→语法检查→重启→确认端口监听。后面所有配置调整都走这个流程能省去很多改了没生效的排查时间。4. Wireshark还原DORA四步发现、提供、请求、确认的完整报文细节4.1 DORA到底是哪四步把所有设备接好、DHCP服务器就绪后我开始抓包。先把Windows 10客户端的网卡设置从静态IP改成自动获得IP地址然后在Wireshark里按下开始抓包再在Windows里点禁用再启用网卡强制它重新获取地址。这个过程一触发Wireshark里立刻出现了四步交互就是经典的DORADiscover客户端在网段内广播寻找DHCP服务器Offer服务器在池子里选一个地址先租给客户端Request客户端向服务器确认要这个地址ACK服务器最终确认租约生效我按顺序把这四个报文一个一个点开看字段发现了很多平时在文档里看过但没真正理解的细节。4.2 Discover报文从0.0.0.0到255.255.255.255第一个报文是DHCP Discover。看IP层源地址是0.0.0.0目的地址是255.255.255.255。这个0.0.0.0不是乱填的而是客户端此时确实没有IP用0.0.0.0作为源地址发送广播是RFC 2131规定的标准行为。再看DHCP层关键字段有这么几个Message typeOption 53值为1表示DiscoverClient Identifier客户端的MAC地址Host NameWindows机器名Parameter Request List就是Option 55一堆小数字代表客户端希望服务器告诉它哪些参数最值得看的就是那个Parameter Request List它相当于客户端举了个牌子路由器地址Option 1、子网掩码Option 1我没记错的话是3等等实际数字为Subnet Mask是Option 1Router是Option 3DNS是Option 6客户端把自己需要的配置项列了个清单发给服务器。服务器后面Outer的时候会从这个清单里挑自己能给的参数填进去。如果客户端没在清单里要求的参数服务器即使有也不会往下发。这也是为什么有些机器拿到的DHCP分配的DNS不生效因为客户端发Request时根本没有向服务器要DNS选项——这种情况在老旧嵌入式设备上比较常见。4.3 Offer和Request广播里的你来我往Offer报文的关键点在于候选IP字段。看IP层源地址是服务器的192.168.80.1目的地址还是255.255.255.255。咦为什么服务器发给特定客户端的Offer也是广播按直觉既然是服务器对某个具体客户端的回复应该单播才对。看报文就明白了客户端此时还没有IP地址它只有一个MAC地址。服务器虽然有客户端的MAC但DHCP的Offer封装在UDP里目的IP只能是255.255.255.255或者如果客户端用DHCP选项里的ciaddr字段带了地址才能单播。因为在第一阶段客户端没有IP服务器无法对没有IP的主机做IP单播只能广播。再往下看几个值Your (client) IP Address字段YIADDR填的是192.168.80.100这就是服务器打算分配给客户端的地址。同时还有一个Subnet Mask选项和Router选项以及DHCP Server IdentifierOption 54填的192.168.80.1。留意这个Server Identifier后面Request和ACK环节都依赖它。接着客户端发DHCP Request。这里有个细节容易误导人客户端在Discover后收到了Offer但它并没有立即确定用这个地址而是再广播一个Request。为什么还要广播因为可能有多个DHCP服务器都回了Offer客户端选择用其中一台的地址后需要通过广播让所有服务器都知道我选定了哪一台——这样没被选中的服务器就可以把要给的地址重新放回地址池避免地址被长时间占用而不自知。这是DHCP设计里一个很高明的状态同步机制。Request报文里有两个特殊字段Requested IP AddressOption 50里填的是192.168.80.100即客户端想要的地址Server Identifier里填的是192.168.80.1即客户端选定的服务器。这两个字段同时存在就是明确告诉所有DHCP服务器我选的是192.168.80.1我要192.168.80.100。4.4 ACK和租约时间这次抓包才发现的事最后一步是服务器的ACK。报文结构上它和Offer很像但Message type是5而且多了个关键的IP Address Lease TimeOption 51值是600秒。这个600秒就是配置文件里default-lease-time的值。除此之外ACK里还带了Router和DNS选项客户端拿到这些后开始配置自己的网络接口——实验里Windows右下角的网络图标从识别中变成已连接也就一两秒的事。用过这个规律后我在Wireshark里连续抓了多个客户端的完整流程作对比发现每个客户端的XIDTransaction ID事务ID都不一样。XID由客户端发起Discover时生成后面整个交互过程里服务器回Offer、客户端回Request、服务器回ACK用的都是同一个XID。这是把四步报文关联起来的核心字段。排查DHCP问题时如果在抓包里看到某个Discover报文没有对应的Offer或者Request和ACK之间断了就要靠XID去过滤单个交互流程一眼就能看出来问题出在哪一步。提示Wireshark里右键任一DHCP报文选择Follow UDP Stream不一定好用因为DHCP是广播模式多客户端同时交互时会话很乱。更靠谱的做法是直接用dhcp.id xx.xx.xx.xx按客户端MAC过滤或者dhcp.option.dhcp_server_identifier 192.168.80.1过滤特定服务器。4.5 租期续租T1和T2时间在看包时怎么认前面抓的都是完整的DORA四步是客户端第一次获取地址的流程。但DHCP里还有一半机制在租期续约上。租期默认10分钟所以实验里很快就能看到续租报文。租期过半T1即5分钟时客户端会单播发DHCP Request给服务器请求续租。这时源地址就是客户端自己的IP192.168.80.100不再是0.0.0.0目的地址是服务器地址192.168.80.1。如果服务器回ACK租约就续上了。如果T1没回复等到87.5%T2即8.75分钟客户端改用广播发Request。再没回复租期一到地址就被释放客户端重新从Discover开始。抓包看到的现象是客户端拿到IP后前几分钟很安静过了300秒左右突然有一条单播Request服务器秒回ACK然后又是几分钟安静。这就是续租发生在单播场景下的样子。在Wireshark里把dhcp.msgtype 3过滤一下就能单独挑出所有续租Request。Windows默认续租行为算比较标准的Linux的NetworkManager可能更激进一点比如提前续租提醒但大体节奏遵循RFC。5. 三个真实故障的排查链路地址拿不到、IP冲突、部分网段失灵5.1 故障一客户端显示正在获取地址卡住不动实验过程中我故意把服务器的防火墙iptables给开了再重启dhcpd然后客户端重新获取地址就卡在正在获取地址这个状态不动了。抓包看到的现象是客户端不停发Discover但服务器没有任何回复界面上只有一圈圈广播Discover的报文在反复刷。这个问题从抓包结果往外推原因就两个方向要么服务器没收到Discover要么收到了但没回。因为抓包电脑和客户端在同一广播域能抓到客户端发出去的Discover说明客户端到交换机这一段没问题服务器没回要么是服务没监听67端口要么是防火墙拦了入站UDP 67。一步步排查。先看dhcpd状态是active的再看端口监听ss -ulnp | grep dhcpd有结果。最后想起来重启服务前动过防火墙于是查iptables规则iptables -L -n | grep 67果然没有放行UDP 67的规则。补上以下规则后服务立即正常iptables -I INPUT -p udp --dport 67 -j ACCEPT iptables -I INPUT -p udp --dport 68 -j ACCEPT这个故障在生产环境里也很常见很多网管把DHCP服务挂在内网服务器上服务器本身装了安全软件或开了防火墙忘了放行DHCP端口。另外CentOS 7里和防火墙并列的还有selinux如果配置没错、端口也监听了但客户端就是拿不到IP可以查一下selinux的avc日志。这个坑我在另一台机器上踩过dhcpd进程被selinux拒绝写入租约文件直接导致服务起不来。5.2 故障二Windows拿到了169.254.85.3而不是192.168.80.x第二个故障更有意思。我把服务器上dhcpd服务手动停掉然后重新启动一台客户端的网卡。Windows 网卡在尝试了几秒后自己给自己分配了一个169.254.85.3的地址这就是著名的APIPAAutomatic Private IP Addressing地址。这是操作系统的本地兜底机制DHCP客户端在极短时间内没拿到服务器响应操作系统就自动分配一个169.254.0.0/16的链路本地地址保证同一网段内设备还能互相通信比如两台电脑直连传文件。排查这个现象时关键不是看169.254本身而是搞清楚为什么DHCP没有成功。抓包可以看到客户端发了一到两次Discover后一直等不到Offer于是放弃了。对Windows的DHCP客户端等待时间其实很短大约一两秒钟没收到Offer就会fallback到APIPA。这个时间窗口在生产环境很重要如果DHCP服务器性能差、或者网络上存在一个响应很慢的中继设备客户端可能已经放弃等待了但管理员看服务器日志发现请求根本没到。注意APIPA地址不是故障代号而是操作系统的正常兜底行为。见到169.254.x.x第一反应应该是DHCP交互没完成而不是去纠结为什么Windows会自己填这个地址。5.3 故障三部分网段能拿到地址部分网段不行第三个故障发生在网络结构稍微复杂一点以后。我给网络加了一个三层交换机划分了两个VLANVLAN 80是原来那批设备VLAN 81是新增的一台测试客户端。DHCP服务器在VLAN 80一侧。结果VLAN 80里的设备一切正常VLAN 81里的客户端死活拿不到地址。这是因为DHCP的广播报文不会跨越VLAN广播域边界。VLAN 81里的客户端发的Discover三层交换机默认不会把这个广播转发到VLAN 80。要解决这个问题标准做法是在三层交换机上配置DHCP Relay也叫ip helper-address让交换机把来自VLAN 81的DHCP广播转换成单播转发给DHCP服务器。在实验用的某品牌交换机上配置大概是interface vlan 81 ip helper-address 192.168.80.1加上这条配置后VLAN 81的客户端就能拿到地址了。但此处有个生产环境里很难发现的隐患默认的ip helper-address不仅转发DHCP还会转发TFTP、DNS、TACACS等一堆UDP广播。如果服务器上还跑了别的服务这些不需要的广播也会被转发过去轻则增加无谓流量重则引发安全事故。所以在有中继的网络里最好关闭不需要的转发服务只保留DHCPinterface vlan 81 ip helper-address 192.168.80.1 no ip forward-protocol udp tftp no ip forward-protocol udp dns另外还有个细节在有DHCP Relay的网络上服务器的配置里要确认authoritative;的作用域声明是否正确。如果服务器收到的是从Relay转过来的报文报文里的giaddr字段Gateway IP Address会带上交换机的VLAN接口地址比如192.168.81.1。dhcpd会根据这个giaddr决定从哪个subnet作用域里分配地址。所以vlan 81的subnet声明必须存在于配置文件中否则服务器会报no subnet declaration for 192.168.81.1日志一眼就能看到。6. 实验之外如果要上生产环境这几件事必须先想清楚6.1 租期长短到底怎么设实验里我把默认租期设成600秒纯粹是为了看到多轮续租过程。但生产环境里租期设置要结合网络规模和地址池大小来想。办公网络里地址充足、设备长期在线的话租期可以设成一天甚至更长减少DHCP交互流量和服务器压力。但地址相对紧张比如一个C段地址要分配给超过一半使用中的设备或者访客Wi-Fi这种流动性极大的场景租期最好缩短到几小时这样设备离开后地址能尽快释放回池子。我见过一个真实案例某公司搞活动200人的会场临时发了免费Wi-Fi地址池就一个C段路由器的默认租期是24小时上午200人陆续进场地址瞬间分完下午再来的人全被挡在外面。后来把租期改成2小时问题立刻缓解。这就是租期和地址池容量之间的平衡问题。6.2 默认网关和DNS的分配策略要提前定好DHCP不仅能分IP还能分网关和DNS。但如果DNS地址写死成服务器IP、没有冗余服务器一宕机全网DNS解析就跟着挂了。实验里我就用服务器兼任DNS没问题。生产环境至少要有两个DNS服务器分别填到地址池的option里。另外像一些营业场所需要做上网行为管理的最好把DNS和网关都指向行为管理设备这些策略要在配置DHCP之前就想清楚因为修改option routers会导致所有客户端在续租时才拿到新网关偏偏有些设备不等到续租就不刷新非常拖节奏。6.3 日志和监控DHCP出了问题先从日志下手最后分享一个实验里体会到最深的心得DHCP的排错日志比抓包更重要。抓包是看网络里发生了什么日志是看服务器认为自己做了什么。两台设备有可能对同一件事具有完全不同的视角。dhcpd的日志默认打到/var/log/messages里通过journalctl也很方便journalctl -u dhcpd -f日志里会看到一行行类似这样dhcpd: DHCPDISCOVER from xx:xx:xx:xx:xx:xx via eth0 dhcpd: DHCPOFFER on 192.168.80.100 to xx:xx:xx:xx:xx:xx via eth0 dhcpd: DHCPREQUEST for 192.168.80.100 from xx:xx:xx:xx:xx:xx via eth0 dhcpd: DHCPACK on 192.168.80.100 to xx:xx:xx:xx:xx:xx via eth0这些报文的流转分别对应DORA的四步一旦某一步缺了日志里就能看出服务器有没有收到对应的请求。比如客户端一直Discover但服务器没有发Offer日志里一般会给出原因最常见的两类是address pool exhausted地址池耗尽和no subnet declaration for [interface IP]没有匹配的作用域。真实环境里排查DHCP问题我的顺序永远是客户端日志/抓包 → dhcpd日志 → 配置review → 网络连通性。这个顺序能快速缩小范围不至于一开始就在网络上瞎猜。实验做到这一步DHCP的机制算是摸透了。从最初手动配IP的痛苦到一台Linux服务器把整个网段的地址分配管起来再到能从报文字段层面理解为什么客户端会这样表现整个过程收获很大。如果非要给后来者一句建议不要只搭好环境看到分配成功就收手一定要抓包、看日志、故意制造故障再排掉只有把交互细节和排查链路都走一遍才算真正掌握DHCP。