Android 10 DHCP问题排查:从dumpsys到DhcpServer源码

发布时间:2026/10/3 9:12:33
Android 10 DHCP问题排查:从dumpsys到DhcpServer源码
拿到一台Android 10的设备用户反馈“插上网线拿不到IP”或者Wi-Fi信号满格但就是上不了网这类问题十有八九要落在DHCP流程上。Android 10这一代刚好是网络栈换血的关键节点旧版依赖的外部dhcpcd进程在AOSP里逐步被内置的DhcpServer替换从dumpsys network_stack到DhcpServer.java调试工具链和思路跟以前完全不一样。这篇文章我把实际排查过程中用到的方法、命令、源码埋点和踩过的坑完整梳理一遍希望能给正在被Android 10 DHCP问题折磨的人一点参考。1. 为什么现在调试Android 10的DHCP这么麻烦1.1 从dhcpcd到DhcpServerAndroid网络栈的第一次大换血Android 10之前DHCP客户端的实现主要靠一个独立的守护进程dhcpcd它是从Linux生态移植过来的。你可以在adb shell里直接ps -A | grep dhcp能看到它的进程日志也存在logcat里tag基本都是DhcpClient。排查这类问题基本三板斧看dhcpcd有没有起来看logcat里有没有dhcp相关的报错再不行就tcpdump抓包。整体思路比较线性跟排查普通Linux服务器上的DHCP问题差别不大。到了Android 10情况变了。AOSP开始把网络相关的东西收拢成一个名叫NetworkStack的系统模块DHCP客户端从外部进程慢慢迁移到Android框架进程内部实现。这个过程中诞生了一个新类就是标题里提到的DhcpServer.java位置在packages/modules/NetworkStack/src/com/android/networkstack/dhcp/DhcpServer.java。它不仅仅是把旧代码翻新而是直接在Java层实现了一个完整的DHCP服务器逻辑比如构造DHCPOFFER、处理DHCPREQUEST、管理租约等。正是因为这次架构调整调试手段也得跟着升级。以前ps一下就能确认进程存在现在你得学会dumpsys network_stack以前logcat里直接搜dhcpcd现在得知道NetworkStack进程里的tag到底叫什么更麻烦的是如果你需要给DHCP服务器加日志、改逻辑还得重新编译整个NetworkStack APK再推到系统里跟以前改个dhcpcd配置文件完全是两码事。很多人第一次接触Android 10的DHCP问题就是栽在这个“找不着北”的阶段。1.2 一套通用的调试出发思路其实不管架构怎么变DHCP问题的本质没变就是四个报文的你来我往客户端发DHCPDISCOVER服务器回DHCPOFFER客户端再发DHCPREQUEST确认服务器最后回DHCPACK。任何一步断了表象都是拿不到IP。所以我的调试思路一直都遵循一个固定套路先确认服务端“活了没”再确认报文“到没到、回没回”最后才是“代码里到底发生了什么”。对应到Android 10上这三步分别就是用dumpsys network_stack查看NetworkStack服务的运行状态、接口状态、各客户端注册情况用logcat过滤NetworkStack、DhcpServer相关日志同时用tcpdump抓包确认报文交互如果前两步都正常但问题依旧那就得进源码在DhcpServer.java里加埋点日志重新编译验证。这套思路的好处是每一步都建立在前面一步的事实基础上不会一上来就乱改代码。很多新手拿到问题喜欢直接翻源码但DHCP这种网络问题报文层面的证据往往比代码逻辑更能说明问题。我见过太多“代码看起来没问题实际是报文根本没到”的案例了所以先把链路打通再往下钻永远是最省时间的路径。2. dumpsys network_stack先看清楚服务端活没活2.1 命令正确打开方式与输出解读拿到一台Android 10设备第一步我建议先敲下面这条命令adb shell dumpsys network_stack这个命令会把NetworkStack进程内部的所有状态全部dump出来内容非常多但不要被吓到。我一般不会从头看到尾而是用grep先做第一轮粗筛adb shell dumpsys network_stack | grep -i dhcp adb shell dumpsys network_stack | grep -iE interface|eth0|wlan0第一次执行的时候你可能会遇到一个常见问题dumpsys network_stack输出为空或者提示找不到服务。这种情况大概率是设备上没有跑NetworkStack这个服务或者服务异常退出了。正常的Android 10设备上这个服务应该一直在后台运行因为它承载了以太网、Wi-Fi的IP配置管理职责。如果服务不存在那问题已经找到一半了往system_server的崩溃日志方向查。输出里你需要重点关注几个区块。下面是我在一台正常设备上抓到的关键信息片段我做了精简NetworkStack Service: mRegisteredClients: InterfaceState{interfaceNameeth0, ...} IpClient{...} mIpClientStateCONNECTED mDhcpServerStateSTARTED mServerAddress/192.168.50.1 mLeases{...}看到mDhcpServerStateSTARTED说明这台设备的以太网口上确实启动了内置DHCP服务器这个状态字段非常关键。如果这里显示的是IDLE或者STOPPED那就说明服务端压根没把DHCP服务器跑起来问题根源就锁定在这一层不用再去管客户端收没收到报文了。2.2 几个必看的核心字段与其含义为了不让大家面对输出发懵我把dumpsys network_stack输出里和DHCP强相关的几个核心字段整理成了一张表方便对照着看字段常见值含义与判断思路interfaceNameeth0 / wlan0当前IP配置适用的网络接口确认是不是你在排查的那张网卡mIpClientStateCONNECTED / DISCONNECTED / STOPPEDIpClient整体的连接状态DISCONNECTED说明还没完成配置mDhcpServerStateSTARTED / IDLE / STOPPED内置DHCP服务器是否在跑IDLE往往是没拿到服务器地址或者接口未就绪mServerAddress192.168.x.xDHCP服务器自身的IP这个值决定了OFFER报文里会填什么来源地址mLeasesMAC-IP绑定列表已分配的租约记录客户端如果在这里能看到自己的MAC说明服务器已经分配过IPmRegisteredClients接口列表注册到NetworkStack服务的接口集合如果接口都没注册后面的所有逻辑都不会执行每次dump完我建议先把这几个字段抄出来再决定下一步。举个例子如果mDhcpServerState是IDLE而mServerAddress为空那基本可以判断是服务器没有拿到一个合法的静态地址来作为DHCP的锚点最常见的原因是接口还没被配成静态IP就尝试启动DHCP服务了。这时候光看logcat可能绕很久dumpsys一眼就能拨开迷雾。2.3 状态不对时的第一反应dumpsys network_stack的输出就像体检报告指标不对第一步肯定是找“病根”在哪层。我的经验是如果mDhcpServerState不是STARTED先别急着去改动DhcpServer.java而是要把接口状态、网络配置来源这两件事同步确认掉。接口状态用ifconfig或者ip addr查adb shell ifconfig eth0 adb shell ip addr show eth0网络配置来源就要看法则是手工静态配置还是通过EthernetTracker从系统设置里读出来的。在Android 10的以太网配置里如果管理员把“IP设置”定成了静态并且填了192.168.x.x的地址那么NetworkStack才有可能在这个接口上启动DHCP服务器。如果配置来源是DHCP客户端模式那mDhcpServerState长期停留在IDLE就很正常因为此时这台设备是作为客户端去外面获取IP的压根不应该在这里开服务器。很多人在这一步会忽略一个很关键的事情——时间顺序。打开dumpsys看到当前是IDLE不等于它从来没启动过。我踩过一次坑某台设备启动早期DHCP服务器是正常STARTED的但跑到某个阶段接口重启了状态就掉回IDLE。所以dumpsys输出只能反映当下如果怀疑是“启动后中途崩了”得配合logcat看启动时序不能只看一次dump就下结论。3. 跟进日志与抓包把“看不见”的报文还原成证据3.1 logcat 过滤规则与关键日志dumpsys告诉你服务端当前状态但无法告诉你报文交互的过程是否顺畅这一层要靠日志和抓包来补。Android 10的NetworkStack把核心的DHCP逻辑都跑在NetworkStack进程里对应的日志tag你需要记住几个NetworkStack、DhcpServer、IpClient、EthernetTracker。下面是我常用的过滤命令adb logcat -v time -s NetworkStack:V DhcpServer:V IpClient:V EthernetTracker:V如果设备上logcat默认把verbose级别过滤掉了可以用*:S把所有输出静音再按tag打开adb logcat -v time NetworkStack:V DhcpServer:V *:S正常流程下你会在日志里看到类似这样的序列DhcpServer: Starting DHCP server on eth0 DhcpServer: Received DISCOVER from xx:xx:xx:xx:xx:xx DhcpServer: Sending OFFER with ip 192.168.50.100 DhcpServer: Received REQUEST from xx:xx:xx:xx:xx:xx DhcpServer: Sending ACK to 192.168.50.100这里有个经验要分享如果你看到“Received DISCOVER”但后面没有“Sending OFFER”那问题大概率出在服务器处理逻辑内部比如租约池满了、地址冲突检测没过、或者报文解析抛了异常。如果连“Received DISCOVER”都没有那就要回头去查链路层的东西了比如网线、交换机端口、VLAN配置等因为报文压根没进到Android系统里。3.2 tcpdump 抓包实操与DHCP交互流程对照排查网络协议问题抓包永远是终极武器。Android 10的root设备上tcpdump可以直接用前提是系统里有这个二进制文件。没有的话可以用adb push一个静态编译版本进去adb root adb remount adb push tcpdump /system/bin/ adb shell chmod 755 /system/bin/tcpdump抓包命令和思路跟Linux下基本一致adb shell tcpdump -i eth0 -s 0 -w /data/local/tmp/dhcp.pcap port 67 or port 68这里提个细节很多人喜欢加-vv参数想把报文内容打全但我实际使用中更倾向于把pcap导出来用Wireshark分析。因为DHCP报文每个字段都有官方名称和含义Wireshark里一眼就能看到option 53消息类型、option 55请求的参数列表、option 61客户端ID等关键信息比在终端里硬读十六进制效率高太多。抓完把文件导出adb pull /data/local/tmp/dhcp.pcap用Wireshark打开设置过滤条件dhcp即可。这时候你就能清晰地看到四条报文的来回顺序了。3.3 用日志报文定位“不发Offer”的常见原因结合日志和抓包基本能对所有“发不出Offer”的情况做归类。我把最常见的几种原因和对应的表象整理一下根因方向日志表现报文表现服务器地址未配置DhcpServer缺少server address启动失败无任何DHCP响应租约池已满日志提示no available leaseDISCOVER发出后无OFFER接口状态异常IpClient报接口down或address缺失DISCOVER发不到服务器报文被交换机/iptables丢无日志抓包只看得到DISCOVER没有上行包客户端ID不合法日志解析异常DISCOVER报文option 61格式错误遇到“DISCOVER发出去、OFFER回不来”这类情况我个人的排查习惯是先在Android侧抓包确认OFFER到底有没有从网卡发出去。如果在Android系统的出口已经看到OFFER但客户端收不到那就是中途链路的问题跟Android无关如果Android侧压根没发出OFFER再回logcat看服务器处理逻辑到底卡在哪一步。4. 读源码改日志拿到DhcpServer.java的第一手信息4.1 源码位置与工程结构前两招用完之后如果问题还没解决或者你明确需要改一些默认行为比如租约时间、地址池范围那就到了请出DhcpServer.java的时候。Android 10的NetworkStack源码路径在AOSP里是这样排布的packages/modules/NetworkStack/ ├── src/com/android/networkstack/ │ ├── dhcp/ │ │ ├── DhcpServer.java │ │ ├── DhcpServerEventHandler.java │ │ └── DhcpPacket.java │ ├── apishim/ │ └── NetworkStackService.java └── tests/其中DhcpServer.java是整个DHCP服务器的核心调度类它负责监听UDP 67端口、解析收到的DHCP报文、生成并回送响应报文。如果只是排查问题我通常会先在这个类里加日志而不是直接改业务逻辑。记住先定位再动手是改动系统代码的基本素养。如果要编辑源码Android Studio可以直接打开packages/modules/NetworkStack这个目录作为工程但更常见的做法是直接在服务端代码树里改然后用命令行编译。个人开发机上没有完整Android源码的话也可以单独构建NetworkStack模块前提是你已经下载过对应版本的AOSP代码。4.2 关键方法与埋点选择打开DhcpServer.java你首先会看到这样一个类注释和核心成员变量public class DhcpServer { private static final String TAG DhcpServer; private final ServerSocket mServerSocket; private final MapString, DhcpLease mLeases new HashMap(); ... }注意TAG的值是DhcpServer这就是你在logcat里要过滤的tag。整个服务器的生命周期很清晰核心方法有这么几个start()初始化socket、绑定端口、启动接收线程handlePacket()根据报文类型分发处理DISCOVER走offer流程REQUEST走ack流程buildPacket()构造DHCPOFFER、DHCPACK等响应报文sendResponse()把构造好的报文从socket发出去。我一般会在handlePacket()的入口加一条日志把收到的报文类型源MAC等关键信息打出来Override protected void handlePacket(DhcpPacket packet, ...) { Log.d(TAG, handlePacket type packet.mMessageType mac packet.getClientMac()); ... }如果你怀疑某个DHCP OFFER的参数不对那就在buildPacket里把即将填充的ip地址、掩码、网关、DNS都打出来这样能直接把问题定位到构造逻辑还是上层传参。这种“入口埋点出口埋点”的做法基本能覆盖90%以上的定位需求。4.3 编译替换NetworkStack的两种方式改完源码接下来就是编译和替换。这里有两种常见路线分别对应有完整源码树和只有单模块的开发者。路线一在完整AOSP源码树里编译整个模块source build/envsetup.sh lunch 你的设备对应的平台 cd packages/modules/NetworkStack mm编译产物通常在out/target/product/设备/system/framework/下面模块名可能是NetworkStack.apk。然后重新打包system.img刷机或者用adb push把APK推到system/framework里再重启。要注意的是这种替换方式要求设备的dm-verity处于关闭状态一般userdebug版本比较方便。路线二单独编译APK再push如果你只改了DhcpServer.java一个小文件也可以单独构建APKcd packages/modules/NetworkStack make NetworkStack生成的APK路径用find命令找一下即可find out/ -name NetworkStack.apk然后用adb push覆盖系统文件adb root adb remount adb push NetworkStack.apk /system/framework/ adb reboot这里有一个特别容易踩的坑Android 10上NetworkStack是以APK形式存在的系统模块但它还涉及权限签名直接用debug key编译出来的APK可能签名不匹配装上去会导致NetworkStack服务起不来。所以如果只是临时调试我一般建议尽量用官方预签名的方案或者确保你的编译输出是带平台签名的版本。否则你会发现设备重启后network_stack服务直接消失场面会变得很难看。5. 实战案例Android 10设备获取不到IP的排查全程5.1 场景还原与初步定位为了帮助大家把前面讲的内容串起来我分享一个真实的排查过程。某款基于Android 10的嵌入式设备通过以太网口连接测试网络路由器开了DHCP网关是192.168.50.1但设备Android系统里面怎么都拿不到IP。用户反馈到我们这边时日志已经被抓了好几轮但没找到明确的报错。我上手第一步就是执行dumpsys network_stack看接口状态。结果发现这台设备的以太网卡eth0虽然在mRegisteredClients里但IpClient一直停留在CONNECTED之前的状态mDhcpClientState显示为DISCONNECTED而不是预期中作为DHCP客户端应该有的状态。这里插一句Android 10设备本身的角色是“客户端”时它要去外部DHCP服务器拿地址此时它跟dumpsys network_stack里DhcpServer的一部分字段其实关系不大要看的是IpClient这一侧的运行状态。很多内网设备跑的是“客户端模式”但如果你dumpsys时重点放在mDhcpServerState上方向就容易跑偏。5.2 从报文逆向锁定“问题帧”初步定位后我直接在设备上抓包adb shell tcpdump -i eth0 -s 0 -w /data/local/tmp/eth0.pcap port 67 or port 68抓到一份pcap后导入Wireshark分析结果很有意思DISCOVER报文确实发出去了而且路由器也回了OFFER但OFFER到达Android设备后Android并没有继续回REQUEST。正常DHCP流程到这里应该继续往下走结果却像是“收到了但没认”。于是我把抓包里收到的OFFER报文展开逐个option看。最后发现路由器下发的DHCP OFFER里包含了一个Domain Search Listoption 119而Android 10的DhcpClient在解析某些格式的option 119时存在兼容性处理问题处理失败导致整个报文被丢弃。这个根因不看到报文是完全猜不出来的因为客户端日志几乎不会有任何输出最多就是“收到OFFER但状态机没跳转”的沉默表现。5.3 修复与验证定位到问题之后修复方案就有了明确方向。最直接的办法是调整路由器配置把Domain Search List这个选项去掉或者让它在合法格式内发出。但生产环境路由器不一定能改所以我最终选择在Android侧做兼容处理修改DhcpPacket里解析option 119的逻辑把解析失败的异常兜住不至于因为一个可选项导致整个报文被丢弃。修改后重新编译NetworkStack并替换到设备上再次执行抓包能够看到完整的DISCOVER、OFFER、REQUEST、ACK四条报文接口顺利拿到了192.168.50.x的地址。整个过程下来dumpsys、抓包、源码修改各占三分之一的力气缺一个都得走不少弯路。6. 常见问题与排查技巧实录6.1 常见问题速查表以下是我在调试Android 10 DHCP过程中遇到过的典型问题整理成速查表方便直接对照现象可能原因快速排查命令/手段dumpsys network_stack无输出NetworkStack服务未启动或崩溃logcat搜AndroidRuntime查system_server异常mDhcpServerState一直是IDLE接口没有合法静态地址ifconfig eth0确认IP配置来源DISCOVER发了没OFFER地址池满、接口状态不对、报文被链路上设备吞掉tcpdump确认设备出口是否有OFFER收到OFFER但不回REQUEST报文选项解析异常、校验失败Wireshark逐项检查option重点看119、55REQUEST发了没ACK服务器端租约确认失败服务器端抓包看是否收到REQUEST每次获取IP都很慢DHCP discover重传间隔长、超时设置不合理检查代码里的超时参数必要时调短连续重启设备IP老是变客户端ID不稳定检查客户端是否每次生成新的DUID实际排查的时候我习惯先把现象归类到“链路层、网络层、应用层”。链路层对应网线和交换机端口网络层对应DHCP报文交互应用层对应Android里DhcpServer/IpClient的处理逻辑。分层思考能大幅缩小排查范围避免在错误层级上白费功力。6.2 几条保命级别的实操经验最后分享几条我在实际调试中摸索出来的经验每一条都是用时间换来的。第一尽量保留抓包原始文件。很多人排查的时候抓完包看一眼没发现问题就删了但DHCP协议的问题经常是间歇性的一次抓包没抓到不代表没有。我一般会持续抓20分钟以上确保覆盖重启、断网重连等场景再统一分析。第二修改NetworkStack之前先备份原始APK。无论是adb push还是整包刷机都可能出现权限、签名、依赖不匹配的问题。原版APK备份好出问题还能快速还原不至于卡在“系统起不来”的尴尬状态。第三日志永远不要全开。有人喜欢logcat直接V级别全部打开然后被汹涌的日志淹没。正确的做法是精确过滤NetworkStack、DhcpServer、IpClient这几个tag必要时再单独开EthernetTracker。日志不是越多越好而是关键链路的越完整越好。第四不要把客户端模式和服务器模式搞混。Android 10的设备既可以做DHCP客户端去外部获取地址也可以通过内置的DhcpServer给其他设备分配地址。同一个dumpsys输出里这两套状态都存在排查前先分清楚你这台设备扮演的是哪个角色不然很容易拿着服务器状态的字段去套客户端的问题方向全反。写在最后Android 10的DHCP调试相比老版本确实多了一些门槛新的NetworkStack架构、新的服务管理方式、以及新的源码结构都需要重新适应。但把dumpsys network_stack当作入口以抓包数据作为证据再深入到DhcpServer.java里加日志做定点分析这套组合拳到目前为止没有让我失望过。我个人在实际操作中最深的体会是DHCP这种协议层问题70%的根因都能靠报文还原找到方向剩下30%才是代码逻辑层面的疑难杂症。所以无论你面对的是拿不到IP、反复掉线、还是地址分配冲突先静下心把报文链路走一遍让事实替你说话这比任何“魔法般”的改动都要稳妥得多。