1. 为什么我要在本地把 DHCP 八种报文抓一遍很多人背 DHCP 流程时只记得「Discover、Offer、Request、Ack」四步真到线上排障就懵了客户端拿不到地址到底是没收到 Offer还是 Request 被 NAK 了租约续期为什么是单播续不上又为什么变广播这些问题光看文字描述很难有体感。DHCP 一共定义了八种报文Discover、Offer、Request、Ack、Nak、Decline、Release、Inform。前四种是首次拿地址的主线后四种分别对应拒绝、释放、冲突上报和「我已有地址只想要配置」。它们全部跑在 UDP 上服务端 67 端口客户端 68 端口报文格式统一靠 Option 53 里的消息类型字段区分。这篇的做法是在本地 Linux 或容器网络里起一个 DHCP 服务端用 dhclient 当客户端同时开 tcpdump 抓包把八种报文一条条抓出来对照。抓完你再看任何 DHCP 故障脑子里都能自动回放这个时序。最后我会给一段 settings.json 骨架把抓到的日志丢给 AI 工具做分析时用统一的 Key 通道接进去省得每个工具配一遍。适合谁正在学网络协议的学生、要排查 DHCP 故障的运维、以及想搞懂容器网络里地址是怎么来的开发者。全程命令可直接复制不需要额外硬件一台能跑 Docker 的机器就够。2. 前置准备TaoToken 统一 Key 与本地抓包环境抓包本身不需要任何外部服务但后面我们要把 pcap 解析结果、dhclient 日志喂给 AI 做时序分析所以先把 Key 通道准备好。TaoToken 提供统一的 API 入口一个 Key 可以对接多种模型省去在多个平台之间来回切换配置的麻烦。先拿到 Key打开 https://taotoken.net/api-keys 登录后创建一个 API Key复制保存。注意这个 Key 只在创建时完整显示一次丢了只能重建。拿到 Key 之后模型对话入口在 https://taotoken.net/models 可以先用它验证 Key 是否可用如果你打算长期跑编码类或 Agent 类任务走 https://taotoken.net/coding-plan 更划算接入文档在 https://taotoken.net/doc 里面有各语言 SDK 的调用示例。本地环境这边确认三样东西tcpdump、dhclient、以及一个能隔离的实验网络。容器网络最省事因为不会影响你宿主机的真实网络配置。# 检查工具是否齐全 which tcpdump dhclient # 没有的话Debian/Ubuntu 系 sudo apt update sudo apt install -y tcpdump isc-dhcp-client注意抓包要在实验网络里做别在办公网或生产网随便跑 dhclient容易和现有 DHCP 服务冲突把正常机器的地址搅乱。我用的是 Docker 自定义 bridge 网络它自带一个内嵌的 DHCP 服务正好可以当服务端用客户端用另一个容器跑 dhclient。这样 Discover 到 Ack 的完整交互都能抓到而且随时销毁重建。3. 可复制配置搭实验网络 dhclient 配置片段3.1 建一个带 DHCP 的 bridge 网络Docker 的 bridge 驱动默认会给容器分配地址背后就是一套 DHCP 交互。我们建一个指定子网的网络方便抓包时过滤。# 创建一个自定义 bridge 网络指定子网 docker network create \ --driver bridge \ --subnet 172.31.0.0/24 \ --gateway 172.31.0.1 \ dhcp-lab # 确认网络创建成功 docker network inspect dhcp-lab --format {{.IPAM.Config}}输出里能看到172.31.0.0/24和网关172.31.0.1说明网络就绪。这个网关地址就是后面 DHCP 服务端的地址。3.2 起一个客户端容器并抓包关键点客户端容器要能跑 tcpdump并且我们要在它发起 DHCP 之前就把抓包挂上否则会漏掉 Discover。# 起一个带网络工具的容器先不接网络避免自动获取地址 docker run -d --name dhcp-client \ --network none \ --cap-add NET_ADMIN --cap-add NET_RAW \ alpine:latest sleep 3600 # 把它接入 dhcp-lab 网络此时它会尝试 DHCP docker network connect dhcp-lab dhcp-client更稳的做法是先进容器手动控制。我们 exec 进去手动挂 veth、手动跑 dhclient这样每一步都可控。docker exec -it dhcp-client sh # 容器内安装工具 apk add --no-cache tcpdump dhclient # 查看网卡名通常是 eth0 或 eth1 ip link show3.3 dhclient 配置片段dhclient 默认配置在/etc/dhcp/dhclient.conf。为了抓包清晰我们写一份最小配置只请求必要参数并打开详细日志。# /etc/dhcp/dhclient.conf # 只请求这几项减少 Option 干扰 request subnet-mask, routers, domain-name-servers, dhcp-lease-time; # 租约文件位置 lease-file-name /var/lib/dhcp/dhclient.leases; # 打开调试日志方便和抓包对照 # 运行时用 -d 前台调试更直观启动时用前台调试模式日志直接打到终端# 前台运行-d 输出调试信息-v 详细 dhclient -d -v eth03.4 抓包过滤命令DHCP 的过滤表达式要同时覆盖 67 和 68 端口并且注意广播和单播都要抓。# 抓 DHCP 全部报文-n 不做 DNS 解析-e 显示 MAC 层 tcpdump -i eth0 -n -e -vvv port 67 or port 68 # 只抓某类消息按 Option 53 类型过滤不方便先全抓再筛 # 保存到文件方便后续用 tshark 按类型过滤 tcpdump -i eth0 -n -w /tmp/dhcp.pcap port 67 or port 68抓下来的 pcap 可以用 tshark 按消息类型筛# 筛出所有 Discover类型 1 tshark -r /tmp/dhcp.pcap -Y dhcp.option.dhcp 1 # 筛出 Offer类型 2 tshark -r /tmp/dhcp.pcap -Y dhcp.option.dhcp 2 # 一次看全部八种类型 tshark -r /tmp/dhcp.pcap -T fields \ -e frame.number -e ip.src -e ip.dst \ -e dhcp.option.dhcp -e dhcp.ip.your4. 逐条验证八种报文的抓包结果对照4.1 Discover 与 Offer广播找服务端客户端启动时没有地址源 IP 是 0.0.0.0目的 255.255.255.255源端口 68目的端口 67。抓包看到的第一条就是 Discover。# tcpdump 输出片段 IP 0.0.0.0.68 255.255.255.255.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff DHCP-Message Option 53, length 1: Discover Client-ID Option 61: hardware ethernet aa:bb:cc:dd:ee:ff Parameter-Request Option 55: ...服务端收到后从地址池挑一个可用地址构造 Offer 回给客户端。Offer 里带上了预分配的 IP、租期、网关、DNS 等。注意此时地址只是「预留」还没正式生效。IP 172.31.0.1.67 255.255.255.255.68: BOOTP/DHCP, Reply DHCP-Message Option 53, length 1: Offer Your-IP: 172.31.0.5 Server-ID Option 54: 172.31.0.1 Lease-Time Option 51: 3600 Subnet-Mask Option 1: 255.255.255.0 Router Option 3: 172.31.0.1这里有个容易忽略的点Offer 阶段服务端会做 ARP 探测确认这个 IP 没被占用才发出来。如果地址池里某个 IP 已被静态占用服务端会跳过它。4.2 Request 与 Ack确认选择客户端可能收到多个 Offer它选第一个到达的然后广播 Request 通告所有服务端「我选了谁」。Request 里带 Server-ID其他服务端看到不是自己就撤销预留。IP 0.0.0.0.68 255.255.255.255.67: BOOTP/DHCP, Request DHCP-Message Option 53, length 1: Request Requested-IP Option 50: 172.31.0.5 Server-ID Option 54: 172.31.0.1服务端确认后回 Ack客户端拿到 Ack 才真正把地址绑到网卡上。Ack 之后客户端还会发三个 ARP 探测做冲突检测确认没冲突才启用。IP 172.31.0.1.67 255.255.0.5.68: BOOTP/DHCP, Reply DHCP-Message Option 53, length 1: ACK Your-IP: 172.31.0.5 Lease-Time Option 51: 36004.3 Nak续约失败的通知Nak 出现在 Request 之后。典型场景客户端换了网络还拿着旧地址发 Request服务端发现这个地址不属于当前网段就回 Nak客户端收到后必须重新走 Discover。IP 172.31.0.1.67 255.255.255.255.68: BOOTP/DHCP, Reply DHCP-Message Option 53, length 1: NAK Server-ID Option 54: 172.31.0.1抓 Nak 有个技巧它通常和 Request 成对出现且目的地址是广播。如果你在日志里看到DHCPNAK基本就是地址不匹配或租约记录丢了。4.4 Decline地址冲突上报客户端 Ack 后做 ARP 检测如果发现地址已被别人用就发 Decline 告诉服务端这个地址不可用。服务端收到后把该地址标记为冲突不再分配。IP 0.0.0.0.68 255.255.255.255.67: BOOTP/DHCP, Request DHCP-Message Option 53, length 1: Decline Requested-IP Option 50: 172.31.0.5Decline 在实际网络里不常见但一旦出现往往意味着地址池里有静态配置和动态分配重叠了。4.5 Release主动释放客户端正常关机或下线时发 Release 告诉服务端「这个地址我还给你」。Release 是单播直接发给服务端。IP 172.31.0.5.68 172.31.0.1.67: BOOTP/DHCP, Request DHCP-Message Option 53, length 1: Release Server-ID Option 54: 172.31.0.1注意 Release 不是必须的。很多客户端直接关机不发 Release服务端只能等租约到期才回收地址。4.6 Inform只要配置不要地址客户端已经有地址比如静态配的但想从服务端拿 DNS、域名等额外配置就发 Inform。服务端回 Ack但不会分配新地址。IP 172.31.0.5.68 172.31.0.1.67: BOOTP/DHCP, Request DHCP-Message Option 53, length 1: Inform Client-IP Option 61: 172.31.0.5Inform 在 PXE 启动、无盘工作站场景里用得比较多。4.7 八种报文速查表报文类型值方向典型触发场景Discover1客户端广播首次找服务端Offer2服务端广播/单播预分配地址Request3客户端广播/单播选择服务端或续约Ack5服务端广播/单播确认分配或续约成功Nak6服务端广播拒绝请求Decline4客户端广播地址冲突Release7客户端单播主动释放Inform8客户端单播只要配置5. 本篇常见错排查5.1 抓不到 Discover最常见原因是抓包挂晚了。客户端一接入网络就发 Discover你 exec 进去再开 tcpdump 已经错过。解决办法是先在容器里挂好 tcpdump再手动触发 dhclient。# 先挂抓包后台运行 tcpdump -i eth0 -n -w /tmp/dhcp.pcap port 67 or port 68 # 再手动触发 dhclient -d -v eth0另一个原因是网卡选错。容器里可能有 eth0 和 eth1DHCP 跑在哪个网卡上要确认清楚用ip addr看哪个网卡是 UP 且没地址。5.2 只有 Discover 没有 Offer说明服务端没响应。检查三点服务端是否在监听 67 端口、地址池是否耗尽、客户端和服务端是否在同一广播域。容器网络里如果用了--network none再 connect有时 iptables 规则会挡住广播可以临时清一下规则验证。# 看服务端是否在监听 ss -ulnp | grep :67 # 看地址池使用情况以 dnsmasq 为例 cat /var/lib/misc/dnsmasq.leases5.3 Request 之后收到 Nak典型是客户端拿着旧地址在新网段请求。抓包看 Request 里的 Requested-IP 和当前网段是否匹配。不匹配就清掉客户端租约文件重新来。# 清租约强制重新 Discover rm -f /var/lib/dhcp/dhclient.leases dhclient -d -v eth05.4 续约时单播抓不到租期过半时客户端发单播 Request 给原服务端源和目的都是单播地址。如果你的过滤只写了广播就会漏掉。过滤表达式用port 67 or port 68就能覆盖别加broadcast限定。5.5 tshark 过滤字段写错dhcp.option.dhcp是消息类型字段值 1 到 8 对应八种报文。有人写成dhcp.type会报错。另外 pcap 里如果同时有 BOOTP 和 DHCP用dhcp限定更准。# 正确写法 tshark -r /tmp/dhcp.pcap -Y dhcp.option.dhcp 3 # 看所有 DHCP 消息类型分布 tshark -r /tmp/dhcp.pcap -T fields -e dhcp.option.dhcp | sort | uniq -c6. 把抓包日志接进 AI 工具做时序分析抓包只是第一步真正省时间的是让 AI 帮你把 pcap 解析结果和 dhclient 日志对齐自动指出哪一步断了。这里给一份 settings.json 骨架用 TaoToken 的统一 Key 通道把模型接入你的分析脚本或编辑器插件。{ provider: taotoken, apiBase: https://taotoken.net/api, apiKey: sk-你的Key, models: { default: claude-sonnet, fallback: gpt-4o }, features: { logAnalysis: true, pcapSummary: true }, timeout: 60, retry: 2 }把 tshark 的输出存成文本连同 dhclient 日志一起作为上下文发给模型提示词可以这样写以下是 DHCP 抓包摘要和客户端日志请按时间顺序还原报文交互 指出哪一步缺失或异常并给出排查建议。 抓包摘要 粘贴 tshark 输出 客户端日志 粘贴 dhclient -d 输出模型对话入口在 https://taotoken.net/models 接入文档在 https://taotoken.net/doc 里面有完整的请求格式和参数说明。如果你要长期跑这类日志分析任务Coding Plan 的额度更合适入口在 https://taotoken.net/coding-plan 。实测下来把八种报文的抓包结果喂给模型做时序还原比自己对着 tcpdump 一行行看快很多尤其是 Nak 和 Decline 这种不常出现的报文模型能直接点出触发条件。Key 统一之后换模型不用改代码只改 settings.json 里的 model 字段就行。
