SD-WAN终端研发系列04 OpenWrt定制化开发:网络初始化、MAC地址与默认配置
本文是系列第4篇。前一篇介绍了编译体系的全链路。本文聚焦于一个实际需求:当固件刷入设备后第一次启动时,系统如何自动识别硬件、初始化网络接口、设置MAC地址、配置默认参数——让设备开箱即用。
一、为什么需要定制化
SD-WAN终端在出厂部署时,需要满足以下”开箱即用”的要求:
- 管理地址为预设值(如192.168.1.254),运维人员按该地址登录
- 管理密码为预设值,无需额外配置
- 时区设置为本地时区(如CST-8),日志时间戳才有意义
- WiFi SSID和密码有默认值,方便初次连接调试
- MAC地址从芯片的factory分区读取,确保每台设备唯一
- 主机名称、NTP服务器等系统参数有合理的默认值
这些定制化需求需要在源码层面实现,而非每台设备手动配置。
二、网络配置的自动生成流程
在第2篇中我们提到,系统启动时S10boot脚本会调用/bin/config_generate来生成初始网络配置。这里深入展开这个流程。
2.1 完整调用链
/etc/init.d/boot 启动
↓
/bin/config_generate 执行
├── 读取 /etc/board.json(硬件板级信息)
│ ├── 如果 board.json 不存在:
│ │ 调用 /bin/board_detect 生成
│ │ └── board_detect 遍历 /etc/board.d/ 下的脚本
│ └── 如果 board.json 已存在且 /etc/config/network 已存在:
│ 跳过(不覆盖已有配置)
↓
根据 board.json 中的信息生成:
├── /etc/config/network(网络接口配置)
└── /etc/config/system(系统基础配置)
↓
uci_apply_defaults 执行
└── 执行 /etc/uci-defaults/ 下的所有脚本
2.2 board_detect的探测机制
/bin/board_detect是硬件识别的核心。它会遍历/etc/board.d/目录下的所有脚本,按照编号顺序执行:
#!/bin/sh
CFG=$1
[ -n "$CFG" ] || CFG=/etc/board.json
[ -d "/etc/board.d/" -a ! -s "$CFG" ] && {
for a in $(ls /etc/board.d/*); do
[ -x $a ] || continue # 只执行有可执行权限的脚本
$(. $a)
done
}
每个board.d脚本通过调用/lib/functions/uci-defaults.sh中定义的函数来写入板级信息:
01_leds:识别并注册LED指示灯配置02_network:识别并注册网络接口布局(哪个口是LAN、哪个口是WAN)
这些脚本内部通过/lib/ramips.sh中的ramips_board_name()函数来获取板型名称,然后根据板型名称做switch-case判断。
2.3 源码中的文件对应关系
上述文件在源码工程中有明确的位置:
| 运行时路径 | 源码路径 | 作用 |
|---|---|---|
/bin/board_detect |
package/base-files/files/bin/board_detect |
板型探测入口 |
/bin/config_generate |
package/base-files/files/bin/config_generate |
配置生成程序 |
/lib/functions/uci-defaults.sh |
package/base-files/files/lib/functions/uci-defaults.sh |
UCI默认值函数库 |
/etc/board.d/02_network |
target/linux/ramips/base-files/etc/board.d/02_network |
网络接口探测 |
/etc/board.d/01_leds |
target/linux/ramips/base-files/etc/board.d/01_leds |
LED配置探测 |
/lib/ramips.sh |
target/linux/ramips/base-files/lib/ramips.sh |
平台识别函数 |
三、MAC地址的初始化
MAC地址是网络设备的”身份证”。每台路由器出厂时,厂商会将唯一的MAC地址写入factory分区。系统启动时需要从该分区读取MAC地址,并设置到对应的网络接口上。
3.1 factory分区与MAC地址
factory分区(通常是/dev/mtd2)保存了芯片出厂时写入的硬件参数。可以通过hexdump查看其内容:
hexdump -C /dev/mtd2
对于MT7628平台,MAC地址存储在特定的偏移位置:
- LAN MAC地址:偏移0x28,6字节
- WAN MAC地址:偏移0x2e,6字节
3.2 02_network脚本中的MAC初始化
在02_network脚本中,通过ramips_setup_macs()函数完成MAC地址的初始化。不同平台的实现方式有所不同:
ramips_setup_macs() {
local board="$1"
local lan_mac=""
local wan_mac=""
case $board in
mt7628)
# MT7628:从factory分区的固定偏移读取
lan_mac=$(hexdump -v -s 0x28 -n 6 -e '2/1 "%02x:"' /dev/mtd2)
wan_mac=$(hexdump -v -s 0x2e -n 6 -e '2/1 "%02x:"' /dev/mtd2)
[ -n "$lan_mac" ] && ucidef_set_interface_macaddr "lan" ${lan_mac%:}
[ -n "$wan_mac" ] && ucidef_set_interface_macaddr "wan" ${wan_mac%:}
;;
*)
# 其他平台:从eth0的MAC地址推算
lan_mac=$(cat /sys/class/net/eth0/address)
wan_mac=$(macaddr_add "$lan_mac" 1) # WAN MAC = LAN MAC + 1
[ -n "$lan_mac" ] && ucidef_set_interface_macaddr "lan" $lan_mac
[ -n "$wan_mac" ] && ucidef_set_interface_macaddr "wan" $wan_mac
;;
esac
}
这里有两种策略:
- 直接读取(MT7628):从factory分区的已知偏移位置用hexdump读取原始字节,拼接为MAC地址格式(xx:xx:xx:xx:xx:xx)。
${lan_mac%:}的作用是去掉末尾多余的冒号 - 推算生成(其他平台):读取eth0网卡的当前MAC地址作为LAN MAC,然后将最后一位加1作为WAN MAC。这种方式的前提是芯片已经在启动阶段从factory分区加载了eth0的MAC地址
3.3 MAC地址的唯一性保证
MAC地址的前三个字节称为OUI(Organizationally Unique Identifier),由IEEE分配给设备制造商,标识厂商身份。后三个字节由厂商自行分配。
对于SD-WAN终端,MAC地址在factory分区中的写入通常是在生产环节完成的(由生产工具通过JTAG或串口写入),确保每台设备的MAC地址唯一。如果需要在小批量场景下自行分配MAC地址,也可以通过IEEE的IAB(Individual Address Block)机制申请。
四、默认配置的修改
4.1 管理密码
管理密码存储在/etc/shadow文件中。要设置默认密码,需要先生成密码哈希值:
openssl passwd -1 -salt <随机盐值> <密码>
例如生成密码admin123的哈希:
openssl passwd -1 -salt rjJvOAYi admin123
# 输出:$1$rjJvOAYi$51BBvejOE6Gc/o95ybr5M0
然后将生成的哈希写入源码中的shadow文件模板。由于shadow文件包含敏感信息,通常不建议直接提交到版本控制,而是在编译过程中通过脚本生成。
4.2 管理地址
默认的OpenWrt管理地址是192.168.1.1。要修改为192.168.1.254(或其他值),需要修改config_generate脚本中设置LAN接口地址的代码。
修改位置:package/base-files/files/bin/config_generate
4.3 时区与NTP
默认时区是UTC,对于国内设备需要修改为CST-8。同时需要设置国内可用的NTP服务器:
# 修改时区
set system.@system[-1].timezone='CST-8'
# 配置NTP服务器
delete system.ntp
set system.ntp='timeserver'
set system.ntp.enabled='1'
set system.ntp.enable_server='0'
add_list system.ntp.server='cn.ntp.org.cn'
add_list system.ntp.server='0.pool.ntp.org'
add_list system.ntp.server='1.pool.ntp.org'
4.4 WiFi SSID和密码
WiFi配置的修改位置取决于使用的无线驱动:
开源驱动(mac80211):
修改位置:package/kernel/mac80211/files/lib/wifi/mac80211.sh
闭源驱动(MTK官方驱动):
修改位置:package/mtk/mt7628/files/mt7628.sh
4.5 WiFi初始化流程
WiFi的启动是网络初始化的一部分,调用链如下:
/etc/init.d/boot
↓
/sbin/wifi detect(或 config,取决于版本)
↓
调用 /lib/wifi/ 下的脚本
├── mac80211.sh(开源驱动)
└── mt7628.sh(闭源驱动)
↓
生成 /etc/config/wireless 配置
五、添加新设备型号的支持
当需要在一款新的路由器上运行OpenWrt时,需要为该型号添加设备支持。以ramips架构的MT76x8系列为例,需要完成以下步骤:
5.1 添加DTS设备树文件
设备树(Device Tree Source)文件描述了硬件的具体参数——GPIO引脚映射、Flash大小、内存大小、LED和按键的位置等。
文件位置:target/linux/ramips/dts/<型号>.dts
该文件会include平台通用的dtsi文件(如mt7628an.dtsi),然后只定义本型号特有的硬件参数。
5.2 编写board.d脚本
在board.d目录下添加或修改脚本,为新设备注册LED和网络接口配置:
target/linux/ramips/base-files/etc/board.d/01_leds # 添加LED配置
target/linux/ramips/base-files/etc/board.d/02_network # 添加网络接口配置
5.3 在Image Makefile中注册设备
最后,需要在Image Makefile中注册新设备,编译系统才能为其生成固件:
文件位置:target/linux/ramips/image/mt76x8.mk
define Device/zbt-we2805ac
DTS := ZBT-WE2805AC
IMAGE_SIZE := $(ralink_default_fw_size_16M)
DEVICE_TITLE := ZBT-WE2805AC
endef
TARGET_DEVICES += zbt-we2805ac
DTS:指定使用哪个DTS文件IMAGE_SIZE:指定固件镜像的大小DEVICE_TITLE:设备显示名称TARGET_DEVICES:将设备添加到编译目标列表
5.4 内存大小的自适应
在DTS中如果不显式指定内存大小,内核会自动检测:
memory@0 {
device_type = "memory";
reg = <0x0 0x8000000>; // 0x8000000 = 128MB
};
如果删除reg属性,内核会通过探测硬件来确定实际可用内存大小。
六、uc-defaults:一次性初始化脚本
/etc/uci-defaults/目录下的脚本只在第一次启动时执行,执行成功后自动删除。这种机制非常适合完成一些一次性的初始化工作。
典型用途:
- 设置管理密码
- 配置默认的网络参数
- 注册远程管理服务
- 安装并启用自启动服务
如果初始化逻辑比较复杂,可以编写独立的Shell脚本,通过uci-defaults脚本调用。
七、Shell脚本编程要点
在OpenWrt的定制化开发中,会大量编写Shell脚本。这里总结几个容易踩的坑:
7.1 if语句的字符串比较
# 错误:空字符串会导致语法错误
if [ $var == "test" ]; then
# 正确:变量加双引号
if [ "$var" == "test" ]; then
# 双中括号支持模糊匹配
if [[ "$source" == dest* ]]; then
7.2 短路求值
[ -f /tmp/lock ] && echo "locked" # 文件存在则执行
[ -f /tmp/lock ] || echo "not found" # 文件不存在则执行
7.3 字符串截取
# 从左边第0个字符开始,取5个字符
echo ${var:0:5}
7.4 重定向
command > file # 覆盖写入
command >> file # 追加写入
command 2>/dev/null # 丢弃错误输出
下一篇将深入4G模块集成的具体实践——从内核驱动修改、补丁制作到自动拨号脚本,解决”让路由器插上SIM卡就能上网”的工程问题。