#
分享运维 2026-07-24

一次中兴光猫救砖与 DDNS-GO (在光猫内安装其他应用) 自启动方案迁移之旅

By 虚浮时代的艺术家 7 Views 17 MIN READ 0 Comments

起因

手里一台 ZTE G7606V6 光猫,ZX279133 双核 A53、512MB 内存、256MB SPI NAND。

起初是为了改光猫 Tr069、远程管理、上报信息相关设置,发现新版固件自带的 DDNS 是灰色的使用不了,虽然可以通过 Telnet 命令直接更改,但是为了方便点想把网页修改成可编辑的,试了一下居然可以。

把原来 /home/httpd/app_ddns_conf_js.gch备份一下,然后把修改版的app_ddns_conf_js.gch放在了/userconfig/app_ddns_conf_js.gch

看了一下光猫配置和系统,突发奇想能不能在光猫内运行其他应用,比如 DDNS-GO 或者 Lucky。

最初方案很直接:
Telnet 登录,把 DDNS-GO 传到 /opt/cu/apps/ddns-go/
然后修改 /etc/init.d/rcS
加入

mount --bind /userconfig/app_ddns_conf_js.gch /home/httpd/app_ddns_conf_js.gch

test -x /opt/cu/apps/ddns-go/ddns-go && SSL_CERT_FILE=/opt/cu/apps/ddns-go/cacert.pem setsid /opt/cu/apps/ddns-go/ddns-go -l 192.168.1.1:9876 -f 600 -c /opt/cu/apps/ddns-go/config.yaml >> /opt/cu/apps/ddns-go/ddns-go.log 2>&1 < /dev/null &

替换光猫 DDNS 页面和 DDNS-GO 自启动。

操作时做了 shell 语法检查、端口验证、网页验证,一切正常——直到一次意外断电冷启动。

变砖

通电后光猫所有指示灯不亮,LAN 口物理链路都不起来。接 TTL 看启动日志,U-Boot 读两个槽位都报:

Failure while reading at offset ...
Read on kernel1/kernel2 failed with error -74

-74EBADMSG——NAND ECC 无法纠正。两个固件槽位(N60 和 N58)都在各自镜像末尾几页报错。

排查

逐页对比 NAND 数据与以前备份:

  • 修改前的最后一页:与原固件逐字节一致
  • 紧接着的新页:数据大量不同,U-Boot 报 ECC 失败
两个槽位都在修改前内容结束、后来写入内容开始的第一页发生变化并报 ECC -74:
- kernel2:0x237e000 与 N58 备份逐字节一致;从 0x237e800 起 1875/2048 字节不同并报错。
- kernel1:0x23a6800 与 N60 固件逐字节一致;从 0x23a7000 起 1886/2048 字节不同并报错。

原因清楚了:修改 rcS 后,JFFS2 向 rootfs 所在的 NAND 区块写入了新页面,而这些新页的 ECC/OOB 不被当前 U-Boot 正确读取,导致 U-Boot 加载整幅镜像时在这些页失败,加载内核时读不下去,内核因此根本没启动,不是 rcS 执行时卡住,但确实是修改 rcS 所触发的写入导致无法冷启动。

救援

因为我有光猫备份的固件和新固件,所以不用找固件了。

把 CH340(UART TTL 串口转 USB)的电压跳线调成 3.3V 后连接光猫和电脑(如果需要驱动要先安装 CH340 驱动),这款光猫中兴 G7606V6 光猫 UART TTL 串口从上到下顺序是 VCC,TX,RX,GND,CH340和光猫 UART 口的连接方式是

CH340 USB-TTL光猫主板 UART
GNDGND
TXDRX(光猫的接收,接 CH340 的发送)
RXDTX(光猫的发送,接 CH340 的接收)
VCC/3.3V/5V不接(悬空)

连接好后使用xshellSERIAL协议连接 COM 端口

在给光猫通电后狂按空格打断然后停在 U-Boot 命令行,此时光猫 LAN 口 2.5G 口连接电脑是不能识别的,需要插后面的千兆 LAN 口,电脑才能识别到本地链路。

通过 TFTP 把修改前的完整 N58 槽位备份加载到 RAM,CRC32 校验通过后,只擦写 kernel2/rootfs2 这一个分区。

写入后整区回读,CRC32 完全一致。重启,N58 回来了,网络正常、网页正常。

然后在 TTL 中打断 U-Boot,把 N60 的槽位也如法炮制:提取原厂 N60 升级包中前 0x23c0000 字节(精确槽位边界,后续的 preplugin.img 排除),TFTP 加载、校验、擦写 kernel1/rootfs1、回读验证。

N60 也恢复正常。最终把 others 分区中的 BootImageNum1 改回 0,让默认启动回到 N60。

教训:不能再改 rcS

修改 rcS 是这次问题的根源,而且两个槽位都改了意味着没有退路。问题是:

  1. rcS 位于 JFFS2 rootfs,直接向 rootfs 写入会触发 NAND ECC 不一致;
  2. 主系统 rootfs 是只读挂载的,强行写入本身就说明设计上不该这么用;
  3. 如果冷启动时 U-Boot 读不了修改后的页面,任何校验机制(Secure Boot、FIT hash)都轮不到执行。

所以需要彻底换一种自启动方案,不能再碰 rcS、不能再写 rootfs。

寻找新的启动方案

Telnet 进入系统探查:

mount 输出显示 /opt/cu/apps 挂载为:

ubi0_0 on /opt/cu/apps type ubifs (rw)

这是一个独立的 UBIFS 分区(plugin_data,约 106MB),不属于 N60/N58 任何槽位。

ps w 看到有一堆 OpenWrt 风格的进程:procdubusdrpcduappd……进一步看 /proc/2602/root/etc/,发现这其实是一套内嵌的 UFW/OpenWrt 运行环境:

DISTRIB_ID='UFW'
DISTRIB_REVISION='V02.01.0240_arm_zte_ZX279133K54_F'
OpenWrt 19.07.7 for zte_133_K54_hgu_fttr

PS:UFW 是 Unified Framework,统一管理框架。

它的根文件系统采用 overlay 挂载:

lowerdir=/opt/cu/framework/ufw/rootfs
upperdir=/opt/cu/apps/apps

也就是说,在 /opt/cu/apps/apps/ 下创建的任何文件,都会在 UFW 容器内对应路径下出现。所有系统自带服务都通过 /etc/init.d/ 脚本 + /etc/rc.d/S??* 链接由 procd 管理。

其中 /etc/rc.d/S95done 会执行 /etc/rc.local,而 /etc/rc.d/ 中的每个 S??* 链接在启动时都会被 procd 自动扫描并启动。

新方案实施

  1. 移动 DDNS-GO 到 UFW overlay 可见路径:

    mv /opt/cu/apps/ddns-go /opt/cu/apps/apps/opt/apps/ddns-go

    主系统原路径就不保留了。

    UFW 容器内通过 /opt/apps/ddns-go/ 访问。

  2. 创建 procd 启动脚本(持久 overlay):

    1. mkdir -p /opt/cu/apps/apps/etc/init.d /opt/cu/apps/apps/etc/rc.d
    2. printf '%s\n' '#!/bin/sh /etc/rc.common' '' 'START=32' 'USE_PROCD=1' '' 'PROG=/opt/apps/ddns-go/ddns-go' '' 'start_service() {' '    procd_open_instance' '    procd_set_param command "$PROG" -c /opt/apps/ddns-go/config.yaml' '    procd_set_param stdout 1' '    procd_set_param stderr 1' '    procd_close_instance' '}' > /opt/cu/apps/apps/etc/init.d/ddns-go
    3. chmod 755 /opt/cu/apps/apps/etc/init.d/ddns-go

    创建的内容为:

    #!/bin/sh /etc/rc.common
    
    START=32
    USE_PROCD=1
    
    PROG=/opt/apps/ddns-go/ddns-go
    
    start_service() {
        procd_open_instance
        procd_set_param command "$PROG" -c /opt/apps/ddns-go/config.yaml
        procd_set_param stdout 1
        procd_set_param stderr 1
        procd_close_instance
    }

    内容为标准 OpenWrt procd 服务格式,USE_PROCD=1

  3. 创建 rc.d 启动链接

    ln -sf ../init.d/ddns-go /opt/cu/apps/apps/etc/rc.d/S32ddns-go

    效果如下

    /opt/cu/apps/apps/etc/rc.d/S32ddns-go → ../init.d/ddns-go
  4. 冷启动验证:断电重启后,DDNS-GO 自动运行,PID 为 2779。

整个过程没有修改 N60/N58 rootfs,没有改 rcS,没有写 JFFS2,所有持久化数据都在独立的 UBIFS 分区上。

经验总结

总结内容
不要改 rootfs 内的启动脚本冷启动失败概率高且两个槽位同时损坏后无法回退
嵌入式设备的持久分区是首选独立于固件槽位,切换 N58/N60 仍保留
光猫内部可能藏着一套 OpenWrtprocd + ubus + opkg,/opt/cu/framework/ufw/rootfs 是只读基础层
UFW 容器的 overlay 上层是持久入口/opt/cu/apps/apps/ 在 plugin_data,不受槽位切换影响
NAND ECC 坑修改 JFFS2 rootfs 时,U-Boot 可能读不了新写的 ECC 页面,这不是校验问题,是硬件层解码失败

现在 DDNS-GO 已通过 UFW procd 安全自启,不再依赖 rcS 修改。

原厂 DDNS 页面恢复出厂版本,修改版副本保留在 /userconfig/ 待后续通过更安全的路线(如 httpd 别名)重新启用,但是没有继续测试了,只能使用花生壳的 DDNS 意义也不是很大。

还有内嵌的 LXC 容器内的 OpenWrt 不清楚能不能装 OpenWrt 的哪些插件以及有没有其他可玩性,有兴趣的可以自己试试。

本文由 虚浮时代的艺术家 原创

采用 CC BY-NC-SA 4.0 协议进行许可

转载请注明出处:https://blog.vtl.wang/archives/70.html

相关推荐

  • 暂无相关推荐,看看别的吧。

0 评论

发表评论