为 UBoot 添加 DHCP 服务器功能实现自动分配 IP
先放出成品:
uboot-mt7621-dhcpd
bl-mt798x-dhcpd
前言
U-Boot作为嵌入式设备的引导程序,通常需要通过网络获取启动镜像和配置文件。DHCP(动态主机配置协议)服务器在这个过程中扮演着重要角色,负责为设备分配IP地址和提供必要的网络配置信息。
这并非是U-Boot的内置功能,因此我们需要在 U-Boot 代码中配置一个DHCPD服务器,以便设备能够顺利启动并连接到网络。
然而这个功能的实现并不复杂,在最新的U-Boot2025版本中,已经内置了DHCP服务器的支持。
就这样不太复杂的功能,已经有人实现了,但是只提供设备已经编译好的二进制文件,并没有开源源码。
所以,我决定自己动手实现一个开源版本,供大家参考学习。
手搓了几天,终于搞定了。
如果对你有帮助,请给个 Star 鼓励一下。
这篇文章解决什么问题
如果你用过 MTK/开源圈常见的 U-Boot failsafe WebUI(也叫 Web Recovery),大概率都遇到过同一个“反人类”步骤:
进入 failsafe 后 U-Boot 没有 DHCP
电脑必须手动改成静态 IP(比如
192.168.1.100/24),才能打开http://192.168.1.1
在家里折腾还好,一旦你在不同电脑/不同网卡/不同系统之间来回切换,真的很烦。
而你看,DHCP 服务器本质上就是:
监听 UDP
67/68收到
DHCPDISCOVER回一个DHCPOFFER收到
DHCPREQUEST回一个DHCPACK
对 failsafe 场景而言,我们甚至不需要完整 RFC 行为(比如 NAK、DECLINE、RELEASE、地址冲突检测等),能把 PC 配上 IP 并能打开 WebUI 就够了。
就是这个目标:
在 Web failsafe UI 运行期间,启动一个极简 DHCPv4 server,给直连电脑自动分配地址。
效果预览
开启DHCPD后,流程会变成:
电脑网卡保持“自动获取 IP(DHCP)”
进入 U-Boot failsafe 模式
电脑自动拿到一个局域网地址(例如
192.168.1.100)直接访问
http://192.168.1.1完成刷机
从“手动配网卡”升级为“插上就能用”。
改动总览
一共做了 6 件事:
新增 DHCPD 头文件:
include/net/mtk_dhcpd.h新增 DHCPD 实现:
net/mtk_dhcpd.c加一个 Kconfig 开关:
CONFIG_MTK_DHCPD(依赖CONFIG_HTTPD)net/Makefile把mtk_dhcpd.o编进来failsafe/failsafe.c:Web failsafe 开始前mtk_dhcpd_start(),退出后mtk_dhcpd_stop()net/net.c:当net_loop(TCP)初始化网络栈后,再次调用mtk_dhcpd_start()做“兜底挂钩”
但是需要注意的是,这个 DHCPD 依赖 HTTPD 功能,如果你的 U-Boot 连 HTTPD 都没有,那你就需要自己改造一下。
这套组合拳的重点是:
仅在 Web failsafe(TCP 协议循环)时启动 DHCPD
通过 hook U-Boot 的 UDP handler 方式接收 DHCP 报文
实现细节(读懂 net/mtk_dhcpd.c)
1) DHCP 报文解析:只识别必要的 Option
dhcpd_parse_msg_type() 只解析:
magic cookie(
99 130 83 99)option 53(Message Type)
dhcpd_parse_req_ip() 只解析:
option 50(Requested IP Address)
其它 option 一律跳过。这正是“failsafe 专用极简实现”的核心:够用即可。
2) 地址分配:固定地址池 + 最小租约表
实现里维护了一个小表:
DHCPD_MAX_CLIENTS = 8通过 MAC 绑定已分配的 IP(
leases[])
默认地址池:
192.168.1.100~192.168.1.200
分配策略也很直白:
先查 MAC 是否已有 lease,有就复用
否则按
next_ip_host递增分配表满了就“摆烂”:直接返回池子的第一个地址
3) 回包内容:Offer/Ack 需要的配置一次给齐
发送回包使用广播:
目的 IP:
255.255.255.255目的 MAC:广播 MAC
回包里塞的 option(最关键的几项):
53:Message Type(
DHCPOFFER/DHCPACK)54:Server Identifier(服务器 IP)
1:Subnet Mask
3:Router(默认网关)
6:DNS
51:Lease Time(固定
3600秒)
其中 server/netmask/gateway/dns 的取值逻辑是:
如果 U-Boot 网络参数已配置(
net_ip/net_netmask/net_gateway/net_dns_server),优先用现有值否则 fallback 为默认:
- IP:
192.168.1.1 - Netmask:
255.255.255.0 - Gateway/DNS:跟随服务器 IP
- IP:
4) 为啥要在 net/net.c 里再 start 一次?
在 net/net.c 加了这么一段(逻辑层面):
net_init()/net_init_loop()会清理 handlerDHCPD 的实现是通过
net_set_udp_handler()挂钩的
所以如果只在 failsafe.c 里 start,一旦网络循环里把 handler 清掉,DHCPD 就“没声音”了。
因此在 net_loop() 里对 protocol == TCP 的情况再调用一次 mtk_dhcpd_start(),并且 mtk_dhcpd_start() 自己也做了“如果 handler 被覆盖则重新挂回去”的健壮处理。
这段属于:真实工程里最容易踩坑、也最值钱的细节。
5) UDP handler 链式调用:不抢别人的包
DHCPD 不是把原 UDP handler 直接替换掉就完事了,而是保存一个 prev_udp_handler:
DHCPD 先处理自己关心的 UDP
67/68然后把包再交给原 handler(如果存在)
这样能尽量避免和已有 UDP 功能(比如别的协议栈逻辑)冲突。
如何移植到你的 U-Boot(通用思路)
如果你不是用我上面两个成品仓库,而是要往自己的 U-Boot 树里移植,按仓库中的提交做就行:
拷贝新增文件:
include/net/mtk_dhcpd.hnet/mtk_dhcpd.c
net/Kconfig增加CONFIG_MTK_DHCPD(依赖HTTPD)net/Makefile增加:obj-$(CONFIG_MTK_DHCPD) += mtk_dhcpd.o在 Web failsafe 入口/出口处启停(MTK 用的是
failsafe/failsafe.c的start_web_failsafe())为了防止 handler 被清理,在
net_loop()初始化后补一刀:TCP 模式下再 start 一次
移植时最值得注意的是“网段/地址池”这一点(下一节会说)。
已知限制与改进方向
这个实现定位就是“failsafe 专用极简 DHCP”,所以它有一些明确的限制:
地址池固定写死在代码里:
192.168.1.100-200。- 如果你的 WebUI 使用的是
192.168.31.1/192.168.2.1这类网段,建议把池子改成同网段,或者进一步改成“从 server_ip 自动推导池子”。
- 如果你的 WebUI 使用的是
租约表只有 8 个,且不持久化。
不处理 DHCPDECLINE/DHCPRELEASE/DHCPNAK,也不做地址冲突检测。
没有提供 PXE/TFTP 相关 option(比如 66/67),因为 WebUI 刷机不需要。
如果后续想把它做得更通用,我建议的优先级是:
从
net_ip自动推导地址池(例如server_ip的 /24 网段,池子从.100起)地址池、租约时间做成 Kconfig 或环境变量可配置
在之后
又进行了升级,详见 MTK_DHCPD_ENCHANCED 内的内容,基本做到秒发地址
参考与链接
成品仓库:
- https://github.com/Yuzhii0718/uboot-mt7621-dhcpd
- https://github.com/Yuzhii0718/bl-mt798x-dhcpd
相关背景(hanwckf 文档里也明确提到:uboot failsafe webui 场景通常要手动配静态 IP,因为不支持 DHCP):
- https://cmi.hanwckf.top/p/mt798x-uboot-usage
小结
做的事情很“朴素”,但收益巨大:
Web failsafe 期间自动开 DHCP
PC 不用再手动改静态 IP
实现小、侵入性低、只 hook UDP handler
如果你也被“每次进 failsafe 都要改 IP”折磨过,希望这篇能帮你少踩坑、少浪费几分钟生命。