为 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后,流程会变成:

  1. 电脑网卡保持“自动获取 IP(DHCP)”

  2. 进入 U-Boot failsafe 模式

  3. 电脑自动拿到一个局域网地址(例如 192.168.1.100

  4. 直接访问 http://192.168.1.1 完成刷机

从“手动配网卡”升级为“插上就能用”。

改动总览

一共做了 6 件事:

  1. 新增 DHCPD 头文件:include/net/mtk_dhcpd.h

  2. 新增 DHCPD 实现:net/mtk_dhcpd.c

  3. 加一个 Kconfig 开关:CONFIG_MTK_DHCPD(依赖 CONFIG_HTTPD

  4. net/Makefilemtk_dhcpd.o 编进来

  5. failsafe/failsafe.c:Web failsafe 开始前 mtk_dhcpd_start(),退出后 mtk_dhcpd_stop()

  6. 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

4) 为啥要在 net/net.c 里再 start 一次?

net/net.c 加了这么一段(逻辑层面):

  • net_init()/net_init_loop() 会清理 handler

  • DHCPD 的实现是通过 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 树里移植,按仓库中的提交做就行:

  1. 拷贝新增文件:

    • include/net/mtk_dhcpd.h
    • net/mtk_dhcpd.c
  2. net/Kconfig 增加 CONFIG_MTK_DHCPD(依赖 HTTPD

  3. net/Makefile 增加:obj-$(CONFIG_MTK_DHCPD) += mtk_dhcpd.o

  4. 在 Web failsafe 入口/出口处启停(MTK 用的是 failsafe/failsafe.cstart_web_failsafe()

  5. 为了防止 handler 被清理,在 net_loop() 初始化后补一刀:TCP 模式下再 start 一次

移植时最值得注意的是“网段/地址池”这一点(下一节会说)。

已知限制与改进方向

这个实现定位就是“failsafe 专用极简 DHCP”,所以它有一些明确的限制:

  1. 地址池固定写死在代码里192.168.1.100-200

    • 如果你的 WebUI 使用的是 192.168.31.1 / 192.168.2.1 这类网段,建议把池子改成同网段,或者进一步改成“从 server_ip 自动推导池子”。
  2. 租约表只有 8 个,且不持久化。

  3. 不处理 DHCPDECLINE/DHCPRELEASE/DHCPNAK,也不做地址冲突检测。

  4. 没有提供 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”折磨过,希望这篇能帮你少踩坑、少浪费几分钟生命。