4、DHCP协议在U-Boot中的应用:自动获取IP地址,DHCP选项解析与自定义

网络升级,第一步是什么?

你得让板子有个IP地址。手动设?调试阶段还行,产线上几千块板子你一个个敲?不现实。所以DHCP几乎是U-Boot网络部署的标配。

这一章,我们就聊聊DHCP在U-Boot里到底怎么跑,怎么拿到IP,怎么解析那些选项,以及——怎么塞进去我们自己想要的东西。

4.1 U-Boot里的DHCP客户端:比你想象得简单

U-Boot的DHCP客户端实现,说白了就是一个有限状态机。它不像Linux里那么复杂,没有租约续期,没有地址冲突检测。U-Boot的DHCP只做一件事:发Discover,收Offer,发Request,收Ack。拿到IP就完事。

核心代码在 net/dhcp.c 里。我刚开始看的时候也觉得奇怪,怎么这么短?后来想明白了——U-Boot是bootloader,不是操作系统。它不需要长期维护一个网络栈,够用就行。

U-Boot DHCP 基本流程:

  1. 调用 dhcp() 函数,触发状态机
  2. 广播 DHCPDISCOVER
  3. 等待 DHCPOFFER(超时可重试)
  4. 发送 DHCPREQUEST
  5. 接收 DHCPACK,提取 IP、网关、DNS 等信息
  6. 设置环境变量 ipaddr、gatewayip、netmask、dnsip

你可能会问:如果服务器不回包怎么办?嗯,U-Boot有重试机制,默认5次,每次超时2秒。我曾经在一个电磁环境很差的产线上遇到过,板子死活拿不到IP。后来发现是网线太长,信号衰减太厉害。换了根短网线,问题解决。这种坑,你不实际踩一次,看代码是看不出来的。

4.2 DHCP选项解析:不只是IP地址

DHCP报文里除了IP地址,还有很多选项。U-Boot会解析哪些?我列个表给你看。

选项代码 名称 U-Boot 处理方式
1 子网掩码 设置 netmask
3 路由器(网关) 设置 gatewayip
6 DNS 服务器 设置 dnsip
12 主机名 设置 hostname
15 域名 设置 domainname
42 NTP 服务器 设置 ntpserverip
43 厂商特定选项 需要自定义解析(重点)

你看,U-Boot默认只解析了最常用的几个。但实际项目中,我们往往需要更多。比如,我想通过DHCP告诉板子:你的固件服务器IP是多少,你的设备ID是什么。这些信息,标准DHCP选项里没有,就得用厂商特定选项(Option 43)。

我的习惯: 在DHCP服务器上配置Option 43,里面塞一个自定义的JSON字符串或者二进制结构体。U-Boot端解析出来,设置到环境变量里,后续脚本直接引用。这样,不同设备只需要改DHCP服务器配置,不用改U-Boot代码。

4.3 自定义DHCP选项解析:动手改代码

好,理论说完了,我们动手。假设我想解析Option 43,从中提取一个“固件服务器IP”和一个“设备类型”。

首先,找到 net/dhcp.c 里的 dhcp_process_options() 函数。这个函数遍历DHCP报文的选项区,遇到已知选项就处理。我们要做的,就是在这里加一个case。

// 在 dhcp_process_options() 中添加
case 43: /* 厂商特定选项 */
    // 假设我们的格式:前4字节是固件服务器IP,后1字节是设备类型
    if (len >= 5) {
        // 提取IP
        u32 fw_server_ip = (ptr[0] << 24) | (ptr[1] << 16) |
                           (ptr[2] << 8) | ptr[3];
        // 设置环境变量
        char ip_str[16];
        sprintf(ip_str, "%d.%d.%d.%d",
                ptr[0], ptr[1], ptr[2], ptr[3]);
        env_set("fwserverip", ip_str);

        // 提取设备类型
        u8 dev_type = ptr[4];
        char type_str[4];
        sprintf(type_str, "%d", dev_type);
        env_set("devtype", type_str);
    }
    break;

代码不复杂,对吧?但有几个坑,我当年都踩过。

我曾经犯过的错:

  • 没有检查 len 就读取数据,结果收到一个空Option 43,直接访问越界,板子死机。
  • 环境变量名用了大写,但U-Boot脚本里引用时用了小写,死活匹配不上。
  • 忘记在 dhcp() 函数返回前调用 env_apply(),环境变量写进去了但没生效。

嗯,这些都是血泪教训。你改代码的时候,记得加上长度检查,环境变量名统一用小写,改完后跑一遍 saveenv 确认持久化。

4.4 DHCP与网络部署的整合

拿到IP和自定义信息之后,怎么用?我一般写一个U-Boot脚本,自动完成整个升级流程。

# 在 U-Boot 脚本中
if dhcp; then
    echo "Got IP: ${ipaddr}"
    echo "FW Server: ${fwserverip}"
    echo "Device Type: ${devtype}"

    # 根据设备类型选择固件
    if test "${devtype}" = "1"; then
        setenv fw_file "firmware_type1.bin"
    elif test "${devtype}" = "2"; then
        setenv fw_file "firmware_type2.bin"
    else
        echo "Unknown device type!"
        exit 1
    fi

    # 从固件服务器下载
    tftp ${loadaddr} ${fwserverip}:${fw_file}
    if test $? -eq 0; then
        # 烧写固件
        sf probe 0
        sf erase 0x0 ${filesize}
        sf write ${loadaddr} 0x0 ${filesize}
        echo "Upgrade done!"
    fi
else
    echo "DHCP failed, fallback to static IP"
    setenv ipaddr 192.168.1.100
    setenv gatewayip 192.168.1.1
fi

你看,整个流程就串起来了。DHCP不只是拿个IP,它是整个部署流程的入口。通过自定义选项,你可以把配置下发的权力从U-Boot代码转移到DHCP服务器上。产线上换一种设备?改DHCP服务器配置就行,U-Boot不用重新编译。

4.5 知识体系总览

下面这张图,把DHCP在U-Boot中的角色和流程画清楚了。你对照着看,应该能一目了然。

U-Boot DHCP 应用架构 U-Boot DHCP 客户端 DISCOVER → OFFER → REQUEST → ACK 选项解析:Option 1/3/6/12/15/42/43 设置环境变量:ipaddr / gatewayip / fwserverip / devtype U-Boot 脚本:TFTP 下载 + 固件烧写 核心思路:通过 DHCP 自定义选项(Option 43)将部署参数下发给 U-Boot, 实现“一次编译,灵活部署”。产线换设备只需改 DHCP 服务器配置。

4.6 调试技巧:抓包是王道

最后,分享一个调试技巧。DHCP出问题时,别光盯着U-Boot日志。在PC上开Wireshark,抓一下网络包,看看服务器到底回了什么。

我遇到过最诡异的一次:U-Boot明明收到了DHCPACK,但 ipaddr 就是不对。抓包一看,服务器回了两个Option 3(网关),U-Boot只取了第一个,而第一个是0.0.0.0。嗯,DHCP服务器配置有问题。这种问题,看代码是看不出来的,必须看报文。

调试命令备忘:

  • dhcp — 触发DHCP流程,打印详细日志
  • printenv ipaddr gatewayip netmask — 查看获取到的网络参数
  • printenv fwserverip devtype — 查看自定义选项解析结果
  • 在 dhcp.c 中加 debug() 打印,开启 CONFIG_DHCP_DEBUG

好了,DHCP这块就聊到这儿。你回去可以试试:搭一个DHCP服务器,配一个自定义Option 43,然后在U-Boot里把它解析出来。跑通了,你就真正掌握了U-Boot网络部署的第一步。