SD-WAN终端研发系列08 OpenWrt远程OTA升级方案设计与实现
本文是系列最后一篇。SD-WAN终端通常部署在企业分支机构的现场,数量可能达到数百甚至数千台。当需要更新固件或业务软件时,逐台手动升级是不可行的。本文介绍基于MQTT协议的远程OTA(Over-The-Air)升级方案。
一、OTA升级的基本需求
SD-WAN终端的OTA升级需要满足以下要求:
- 远程触发:通过云端平台下发升级指令,无需现场操作
- 安全可靠:固件传输过程加密,升级失败可回退
- 断点续传:网络不稳定时能从断点继续下载
- 版本管理:能区分设备当前版本和目标版本
- 选择性升级:可以指定升级固件类型(系统固件或业务软件)
- 升级保留:升级后保留关键配置和数据
二、方案选型:为什么选择MQTT
远程升级需要一个可靠的通信通道,将升级指令和固件文件从云端传输到终端设备。常见的方案有:
| 方案 | 优点 | 缺点 |
|---|---|---|
| HTTPS轮询 | 实现简单 | 延迟高,服务器压力大 |
| WebSocket | 双向通信 | 需要自建服务端 |
| MQTT | 低带宽、高可靠、支持遗嘱消息 | 需要MQTT Broker |
| 自定义TCP协议 | 完全可控 | 开发成本高 |
选择MQTT的原因:
- 轻量级:MQTT协议头部最小仅2字节,适合带宽受限的嵌入式设备
- 可靠性:支持QoS消息质量等级,确保消息不丢失
- 连接状态感知:遗嘱消息(Will Message)可以在设备异常离线时通知服务器
- 成熟的云服务:阿里云、AWS等云平台都提供MQTT IoT服务,免去自建Broker的工作
本方案使用阿里云IoT平台作为MQTT Broker。
三、系统架构
┌──────────────┐ MQTT ┌──────────────────┐
│ 阿里云IoT │◄────────────►│ OTA升级服务 │
│ 平台 │ │ (管理后台) │
│ (MQTT Broker)│ └──────────────────┘
└──────┬───────┘
│ MQTT (TLS加密)
▼
┌──────────────┐
│ SD-WAN终端 │
│ │
│ remote-ota │ ← OTA客户端程序
│ (C程序) │
│ │
│ watchdog_daemon │ ← 进程守护
│ (监控) │
└──────────────┘
升级服务:运行在云端,通过MQTT协议向终端下发升级指令。管理员在管理后台选择固件版本、目标设备范围,触发升级任务。
remote-ota:运行在终端设备上的OTA客户端程序,用C语言编写(通过阿里云C Link SDK)。负责与阿里云IoT平台通信、下载固件、校验和刷写。
watchdog_daemon:进程守护程序,监控remote-ota的运行状态,异常退出时自动重启。
四、OTA客户端的运行逻辑
4.1 完整流程
1. 设备启动 → watchdog_daemon启动 → remote-ota启动
↓
2. 动态注册(免预注册模式)
设备向阿里云IoT平台发送注册请求
平台返回设备证书(用于后续MQTT连接认证)
↓
3. 建立MQTT连接
使用获取的证书发起TLS加密的MQTT连接
↓
4. 上报版本信息
向平台上报当前固件版本号(如sdwan_1.0.0)
↓
5. 等待升级指令
平台通过MQTT下发升级指令(包含固件下载URL和版本信息)
↓
6. 下载固件
检查本地是否已有固件文件,有则删除
从URL下载固件到临时目录(如/tmp/firmware.bin)
↓
7. 校验固件
校验固件的完整性(MD5/CRC)
校验失败 → 上报升级失败 → 删除文件 → 等待重试
↓
8. 执行升级
调用sysupgrade命令刷写固件
sysupgrade支持在升级时保留指定的文件和目录
↓
9. 设备重启
新固件启动 → 上报新版本号 → 升级完成
4.2 版本号格式
采用语义化版本号,前缀标识产品:
sdwan_1.0.0 # sdwan = 固件标识, 1.0.0 = 版本号
sdwan_1.1.0 # 小版本更新
sdwan_2.0.0 # 大版本更新
4.3 动态注册
阿里云IoT支持两种设备认证方式:
- 预注册:在阿里云控制台预先创建设备,获取设备证书(ProductKey、DeviceName、DeviceSecret)
- 动态注册:设备在运行时向平台请求证书,无需预先创建
动态注册的优势是:可以批量部署设备,无需在平台上逐个创建。设备首次上线时自动完成注册。
五、C Link SDK的交叉编译
remote-ota客户端基于阿里云C Link SDK开发,需要将其交叉编译为MIPS平台可执行文件。
5.1 环境变量配置
export PATH=$PATH:/home/dev/openwrt-build/staging_dir/toolchain-mipsel_24kc_gcc-7.5.0_musl/bin
export CROSS_COMPILE=mipsel-openwrt-linux-musl-
export CC="${CROSS_COMPILE}gcc -pthread"
export LDSHARED="${CC} -shared"
export STAGING_DIR=/home/dev/openwrt-build/package/ota_package/remote_ota/src/core:\
/home/dev/openwrt-build/staging_dir/toolchain-mipsel_24kc_gcc-7.5.0_musl/bin:$STAGING_DIR
注意:STAGING_DIR需要包含SDK的C Link SDK头文件路径和编译输出的临时文件路径。
5.2 编译
cd /path/to/clink-sdk
make # 生成 fota-posix 可执行文件
六、将OTA客户端打包为OpenWrt软件包
6.1 包结构
package/
ota_package/
remote_ota/
Makefile # OpenWrt包定义
src/
hello.c # 示例源码(实际是C Link SDK的封装)
Makefile # C代码的Makefile
6.2 OpenWrt Makefile编写
include $(TOPDIR)/rules.mk
PKG_NAME:=remote-ota
PKG_VERSION:=1
PKG_BUILD_DIR:= $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
CATEGORY:=Utilities
PKGARCH:=all
TITLE:=Remote OTA module for SD-WAN terminal
endef
# 准备:将源码复制到build_dir
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
# 编译:调用源码目录的Makefile
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
ARCH="$(LINUX_KARCH)" \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS) -Wall" \
LDFLAGS="$(TARGET_LDFLAGS)"
endef
# 安装:将编译产物部署到目标系统
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/lib/ota/components
$(INSTALL_DIR) $(1)/usr/lib/ota/core
$(INSTALL_DIR) $(1)/usr/lib/ota/external
$(INSTALL_DIR) $(1)/usr/lib/ota/portfiles
$(INSTALL_DIR) $(1)/usr/sbin
$(CP) $(PKG_BUILD_DIR)/output/components/* $(1)/usr/lib/ota/components/
$(CP) $(PKG_BUILD_DIR)/output/core/* $(1)/usr/lib/ota/core/
$(CP) $(PKG_BUILD_DIR)/output/external/* $(1)/usr/lib/ota/external/
$(CP) $(PKG_BUILD_DIR)/output/portfiles/* $(1)/usr/lib/ota/portfiles/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/output/fota-posix-demo $(1)/usr/sbin/remote_ota
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
这个Makefile的要点:
Build/Prepare:将C Link SDK的源码复制到build_dirBuild/Compile:传入OpenWrt的交叉编译参数,调用SDK的Makefile进行编译install:将编译产物(可执行文件、库文件、配置文件)部署到目标系统的对应目录PKGARCH:=all:虽然C Link SDK是编译产物,但由于它会被预编译后放入files目录再打包,所以标记为all
6.3 自启动服务配置
OTA客户端需要作为系统服务自动启动,并配置看门狗监控:
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
start_service() {
procd_open_instance "remote_ota"
procd_set_param command "/sbin/remote_ota"
procd_set_param respawn 3600 5 0
procd_set_param stdout 1 # 输出到系统日志
procd_set_param stderr 1
procd_close_instance
}
七、升级保留策略
OpenWrt的sysupgrade工具在升级固件时,默认会清除所有用户数据。但SD-WAN终端的升级场景下,有些文件需要在升级后保留:
- OTA客户端的配置和证书文件
- 业务软件的配置和数据
- 网络配置(可能需要保留)
- 4G拨号相关配置
7.1 配置保留机制
OpenWrt通过/lib/upgrade/keep.d/目录下的文件来定义升级保留规则。每个文件包含一系列路径模式,匹配的文件/目录在升级时不会被清除。
默认保留规则位于源码:
package/base-files/files/lib/upgrade/keep.d/
7.2 添加自定义保留规则
在源码的keep.d目录下添加一个文件,列出OTA相关文件的保留规则:
# /lib/upgrade/keep.d/remote-ota
/usr/lib/ota/
/etc/config/ota/
/var/lib/remote_ota/
这样,sysupgrade在执行升级时会保留这些目录中的文件。
7.3 sysupgrade命令
# 基本升级(保留配置)
sysupgrade /tmp/firmware.bin
# 不保留配置(相当于恢复出厂设置)
sysupgrade -n /tmp/firmware.bin
# 仅升级特定分区
sysupgrade -k /tmp/firmware.bin # 只升级内核
八、异常处理与可靠性
8.1 进程守护
watchdog_daemon程序监控remote-ota进程的运行状态。如果remote_ota异常退出,watchdog_daemon会自动重启它。这通过OpenWrt的procd机制实现。
8.2 升级失败回退
固件下载后、刷写前的校验环节可以防止损坏的固件被刷入设备。如果校验失败:
- 删除已下载的不完整固件文件
- 通过MQTT上报升级失败事件
- 等待平台重新下发升级指令
8.3 看门狗兜底
作为最后的防线,硬件看门狗可以确保设备在严重故障时自动重启。如果OTA升级过程中设备完全死机(连看门狗喂狗程序都停止了),看门狗会强制重启设备。由于升级尚未完成(sysupgrade还没执行),设备会以旧固件重新启动,保持可用状态。
九、调试与运维
9.1 查看OTA日志
logread | grep remote_ota # 查看OTA程序日志
logread | grep ota # 查看所有OTA相关日志
9.2 手动触发升级
在调试阶段,可以手动执行OTA流程来验证功能:
# 手动下载固件
wget -O /tmp/firmware.bin "http://ota-server/firmware/v1.1.bin"
# 手动校验
md5sum /tmp/firmware.bin
# 手动升级
sysupgrade /tmp/firmware.bin
9.3 远程调试建议
在实际部署中,建议:
- 先在少量设备上灰度升级,验证通过后再全量推送
- 升级前确认设备在线且网络稳定
- 避免在业务高峰期执行升级
- 保留回退方案(如旧版本固件的下载链接)
十、总结
OTA远程升级是SD-WAN终端运维体系的关键能力。本文介绍的方案的核心设计思路是:
- 通信层:使用MQTT协议(阿里云IoT平台),确保低带宽下的可靠通信
- 安全层:TLS加密传输 + 固件校验,防止固件被篡改
- 执行层:sysupgrade完成实际的固件刷写,支持文件保留
- 保障层:procd进程守护 + 硬件看门狗,多层兜底确保设备可用
这套方案在实际部署中经受住了数百台设备的规模化升级考验。从工程实践看,OTA升级最常遇到的问题不是技术方案本身,而是网络环境的复杂性——不同现场的带宽、延迟、防火墙策略差异很大,需要在通信协议层面做好重试和超时处理。
本系列文章到此结束。 从业务需求理解,到系统原理剖析,再到编译、定制、集成、分流、嵌入式Agent开发和OTA升级,完整覆盖了在OpenWrt嵌入式路由器上开发SD-WAN终端的核心技术环节。希望这个系列对从事相关领域开发的工程师有所帮助。