ImmortalWrt系统CPE路由器蜂窝模组完整排障记录

本文最后更新于:5 小时前

前情提要

由于我租住的房子没有接入光纤网络,无法办理宽带,我便只能研究起流量卡的上网方案。在互联网上的中文技术论坛浏览了相关的讨论帖,发现大家都极为推崇CPE路由器,优点是插入流量卡上网很稳定。但是一般的大品牌国产CPE路由器功能又太过单一,例如没有收短信的功能,而我的流量卡有时会用来接收一些短信验证码。

爱好折腾网络设备的我又怎能甘心,就开始在国内二手平台上“寻宝”,没想到还真让我发现了一款不一般的CPE路由器:它是一款出口澳洲电信Telstra的出口版CPE路由器,并且还预装了类似于OpenWrt的ImmortalWrt系统,可玩性显著高于一般的国产CPE路由器。这款路由器搭载了Qualcomm IPQ8072A的芯片,Cortex-A53架构,并且蜂窝网络采用的是Quectel(移远)的5G模组。看价格挺合适,于是果断拿下,没想到插上流量卡并使用了一段时间以后,却发现了一些小问题。下面就跟随我的笔记一起,来复盘一下完整的排障记录。

遇到的问题和排错过程

QModem插件自动覆盖网络接口配置

在插入流量卡并完成路由器配置后的最初几天,5G 模组能够正常连接,QModem 的“拨号总览”页面显示绿色的“已连接”状态。

immortalwrt_qmodem.png

最初在“移动网络 → 拨号总览”中启用 QModem 自动拨号后,QModem 会创建名为 wwan0 的逻辑接口,并运行DHCP协议。该接口能够提供 IPv4 网络,但当时无法稳定获得 IPv6 地址及向 LAN 下发 IPv6 前缀。取消“启用拨号”后,手工创建 proto='quectel'wwan0 接口,则能够获得运营商下发的 IPv6 地址。

问题在于,这个手工接口不能稳定保留。路由器重启、QModem 重拨或执行 /etc/init.d/qmodem_network restart 后,可能出现以下情况:

  • network.wwan0 被重新创建或删除;
  • protodeviceifname、DNS 等字段被 QModem 改写;
  • LuCI 中的接口消失或显示“错误:网络设备不存在”;
  • QModem 的模组状态变为红色“未连接”;
  • 即使已经执行 uci commit network,下一次重拨后配置仍会发生变化。

这说明问题不是 UCI 没有提交成功,而是提交以后仍有其他进程继续修改 /etc/config/network。OpenWrt 的 UCI 只负责保存配置;如果后台服务之后再次执行 uci setuci deleteuci commit,原有配置仍会被覆盖。UCI 和 netifd 的职责可分别参考 OpenWrt 官方的 UCInetifd 项目。

排查 /etc/init.d/qmodem_init/etc/init.d/qmodem_network/etc/init.d/qmodem_reboot 后,最终确认真正修改接口的是:

1
/usr/share/qmodem/modem_dial.sh

其中主要涉及两个函数:

1
2
set_if()
flush_if()

set_if() 会创建或改写 network.<接口名>,而 flush_if() 会根据接口中的 modem_config 字段删除与当前模组关联的网络接口。hang() 在停止或重拨前会调用 flush_if(),因此 QModem 的“重拨”实际包含“先停止并清理接口,再重新创建接口”的过程。

在脚本中还发现了一个关键拼写错误:

1
2
3
4
5
6
interface=$(uci -q get network.$interface_name)

# 原代码误写成 inetrface
if [ -z "$inetrface" ]; then
...
fi

因为 inetrface 从未赋值,所以条件几乎始终成立。即使 network.wwan0 已经存在,脚本仍会把它当作不存在,重复执行创建和覆盖逻辑。该判断应改为:

1
2
3
if [ -z "$interface" ]; then
...
fi

不过,仅修正拼写仍不足以解决问题,因为 set_if() 后半部分还会强制执行:

1
2
3
4
5
uci set network.${interface_name}.ifname="${set_modem_netcard}"
uci set network.${interface_name}.device="${set_modem_netcard}"
uci set network.${interface_name}.modem_config="${modem_config}"
uci set network.${interface_name}.proto="${proto}"
uci commit network

当前模组工作在 QMAP 模式时,set_modem_netcard 通常为 wwan0_1,因此 QModem 会把原本正确的:

1
option device '/dev/cdc-wdm0'

覆盖成:

1
option device 'wwan0_1'

这正是 LuCI 中“调制解调器设备”反复变成 wwan0_1 的原因。

这里必须区分几个不同对象:

名称 作用
/dev/cdc-wdm0 QMI 控制字符设备,供 quectel 协议或拨号程序建立数据会话
wwan0 QMI 父网卡,quectel-CM-M -i 应使用的基础网络接口
wwan0_1 QMAP mux 数据网卡,实际 IPv4/IPv6 地址通常配置在此接口上
wwan0_4 netifd/协议脚本运行时生成的动态 IPv4 逻辑接口
wwan0_6 netifd/协议脚本运行时生成的动态 IPv6 逻辑接口

因此,下面两个选项不能混为一谈:

1
2
option device '/dev/cdc-wdm0'
option ifname 'wwan0_1'

前者是 QMI 控制设备,后者是数据面网卡。将 device 改成 wwan0_1 会导致 quectel 协议无法获得正确的调制解调器控制设备;把 quectel-CM-M-i 参数改成 wwan0_1 也同样错误。实际日志已经证明,正确使用 -i wwan0 时,程序能够自动识别:

1
2
3
Auto find qmichannel = /dev/cdc-wdm0
Auto find usbnet_adapter = wwan0
qmap_netcard = wwan0_1

最开始我还尝试通过以下设置禁用 QModem 自动扫描:

1
2
3
4
uci set qmodem.main.block_auto_probe='1'
uci set qmodem.main.enable_usb_scan='0'
uci set qmodem.main.enable_pcie_scan='0'
uci commit qmodem

这些设置可以减少自动探测或重新生成模组配置,但它们不是解决 network.wwan0 被覆盖的核心措施,并且在某些版本中可能导致模组开机后不再自动发现。因此不能把它们当作通用必选配置。真正需要限制的是 modem_dial.sh 对已有用户接口的增删改逻辑。

自定义配置持久化

在明确 /dev/cdc-wdm0wwan0wwan0_1 的区别后,固定的 wwan0 配置应保留 QMI 控制设备和 QMAP 数据网卡两个字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
config interface 'wwan0'
option modem_config '4_1'
option proto 'quectel'
option metric '11'
option device '/dev/cdc-wdm0'
option ifname 'wwan0_1'
option apn '<运营商APN>'
option pincode '<SIM卡PIN>'
option auth 'none'
option pdptype 'ipv4v6'
option defaultroute '1'
option peerdns '0'
list dns '223.5.5.5'
list dns '223.6.6.6'

需要注意,ifname 应通过以下命令确认,而不是通过 uci show network | grep interface 推断:

1
2
3
ip link show
ls -1 /sys/class/net
find /sys/bus/usb/devices/4-1 -type d -name net -print -exec ls -1 {} \;

因为uci show network 只能显示 UCI 逻辑配置,不能证明当前内核实际存在名为 wwan0_1 的网卡。

使用 UCI 重建接口时,可以执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uci -q delete network.wwan0
uci set network.wwan0='interface'
uci set network.wwan0.modem_config='4_1'
uci set network.wwan0.proto='quectel'
uci set network.wwan0.metric='11'
uci set network.wwan0.device='/dev/cdc-wdm0'
uci set network.wwan0.ifname='wwan0_1'
uci set network.wwan0.apn='<运营商APN>'
uci set network.wwan0.pincode='<SIM卡PIN>'
uci set network.wwan0.auth='none'
uci set network.wwan0.pdptype='ipv4v6'
uci set network.wwan0.defaultroute='1'
uci set network.wwan0.peerdns='0'
uci -q delete network.wwan0.dns
uci add_list network.wwan0.dns='223.5.5.5'
uci add_list network.wwan0.dns='223.6.6.6'
uci commit network

但仅在 /etc/config/network 中设置 APN、PIN 和 PDP 类型并不完整。modem_dial.shupdate_config()/etc/config/qmodem 的模组 section 中读取:

1
2
3
4
5
apn
pincode
auth
pdp_type
dns_list

如果 qmodem.4_1.apn 不存在,部分版本会使用默认值 auto。当时的日志已经出现:

1
2
requestGetProfile ... <运营商APN> ... IPV4V6
requestSetProfile ... auto ... IPV4V6

这说明 QModem 曾把模组内部原有的正确 PDP profile 改成 auto。因此相同的拨号参数还应写入 QModem 配置:

1
2
3
4
5
6
uci set qmodem.4_1.apn='<运营商APN>'
uci set qmodem.4_1.pincode='<SIM卡PIN>'
uci set qmodem.4_1.auth='none'
uci set qmodem.4_1.pdp_type='ipv4v6'
uci set qmodem.4_1.dns_list='223.5.5.5 223.6.6.6'
uci commit qmodem

这里使用 dns_list,是因为当前提供的脚本明确执行:

1
config_get dns_list $modem_config dns_list

仅设置 list dns 不一定会被这个版本的 QModem 拨号脚本读取。

如果 SIM 卡已经关闭 PIN,应删除 QModem 中的 PIN,而不是写入字符串 disabled

1
2
uci -q delete qmodem.4_1.pincode
uci commit qmodem

否则 SIM 一旦再次报告需要 PIN,脚本可能把 disabled 当作真实 PIN 发送。

自定义网络配置持久化

执行 uci commit 只能保存当前内容,不能阻止 QModem 后续再次执行 uci setuci delete。要真正持久化配置,必须让 QModem 保留拨号与状态检测能力,同时停止它对已有用户接口的自动覆盖和删除。

之前使用的uci set qmodem.4_1.define_connect='0'命令来“禁用自动创建/删除 UCI 网络接口”,但define_connect 只在部分 AT 拨号分支中决定使用哪个 PDP context,并没有控制 set_if()flush_if() 是否运行,因此不能依靠这个选项解决接口覆盖问题。

我还尝试过使用 iface hotplug 在 wwan0 ifdown 时重新写回 UCI。该方法存在几个问题:

  • QModem 删除 section 时不一定产生可供该脚本捕获的 ifdown 事件;
  • hotplug 中再次执行 network reload 可能触发新的接口事件,形成递归或事件风暴;
  • 每次恢复都重复 add_list dns,可能生成重复 DNS 项;
  • 它只是在接口被删除后补写,不能阻止 QModem 先破坏正在运行的 netifd 状态。

因此,hotplug 恢复脚本不是最终方案。实际需要修改的是 /usr/share/qmodem/modem_dial.sh,至少处理以下三处逻辑。

第一,修正拼写错误:

1
2
- if [ -z "$inetrface" ]; then
+ if [ -z "$interface" ]; then

第二,在 set_if() 写入 UCI 之前识别用户固定接口。检测到 device='/dev/cdc-wdm0' 时,不再覆盖其 deviceifnameproto、APN、PIN 和 DNS;但不要在函数中途过早 return,以免跳过后续必要的 LED、状态或其他处理。示意代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user_managed_if=0
existing_device="$(uci -q get network.${interface_name}.device)"
existing_proto="$(uci -q get network.${interface_name}.proto)"

if [ "$existing_device" = '/dev/cdc-wdm0' ] && \
[ "$existing_proto" = 'quectel' ]; then
user_managed_if=1
m_debug "preserve user managed interface ${interface_name}"
fi

# 只有不是用户固定接口时,才执行原来的创建/覆盖逻辑
if [ "$user_managed_if" -eq 0 ]; then
# 原 set_if() 中创建和更新 network.${interface_name} 的代码
:
fi

第三,hang() 不再调用会删除固定用户接口的 flush_if(),或者给 flush_ip_cb() 增加保护条件,只删除由 QModem 临时创建、且没有被标记为用户固定配置的接口。例如可以给固定接口增加:

1
option qmodem_preserve '1'

并在删除回调中判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
flush_ip_cb()
{
local network_cfg="$1"
local bind_modem_config
local preserve

config_get bind_modem_config "$network_cfg" modem_config
config_get preserve "$network_cfg" qmodem_preserve '0'

if [ "$remove_target" = "$bind_modem_config" ] && \
[ "$preserve" != '1' ]; then
uci delete network."$network_cfg"
fi
}

对应 UCI 配置增加:

1
2
uci set network.wwan0.qmodem_preserve='1'
uci commit network

这种做法比完全注释 flush_if() 更安全,因为其他由 QModem 动态创建的临时接口仍可按原逻辑清理。

修改后应检查:

1
2
3
/etc/init.d/qmodem_network restart
uci show network.wwan0
logread -e modem_dial -e modem_hang

需要确认重拨日志中不再出现:

1
2
delete interface wwan0
set interface wwan0 to wwan0_1

同时还要确认实际拨号命令仍使用父接口,不能改成:

1
-i wwan0_1

也不要在未确认该版本命令行语法前自行增加:

1
-d /dev/cdc-wdm0

此前的实际日志已经表明,这两种改法会使 quectel-CM-M 无法检测网络接口或错误解析参数,随后进程退出并被 procd 反复拉起。

手动重启网络才出现IPv6接口

在解决 UCI 配置被覆盖以后,路由器冷启动时 LuCI 中仍可能只有:

1
2
3
4
lan
wan
wan6
wwan0

这些接口,执行/etc/init.d/network restart后才生成wwan0_4wwan0_6这两个逻辑接口,并获得 IPv6 地址和 LAN 前缀。

immortalwrt_interfaces.png

network restart 的作用不是单纯刷新 LuCI,而是停止并重新启动 netifd 管理的协议流程,重新读取 /etc/config/network,再次运行 proto='quectel' 的协议处理器。wwan0_4wwan0_6 是该协议流程在运行时注册的动态逻辑接口;如果第一次协议初始化发生在模组控制口或 QMAP 网卡就绪之前,它们就不会生成。

冷启动时的实际竞态大致为:

1
2
3
4
5
6
7
8
9
netifd 读取 network.wwan0

/dev/cdc-wdm0、wwan0 或 wwan0_1 尚未完全就绪

quectel 协议第一次初始化不完整

QModem 稍后完成模组扫描、SIM 检查和拨号

netifd 没有自动重新执行完整协议初始化

手工执行 network restart 时,模组通常已经完成 USB 枚举、网络注册和 QMAP 数据接口创建,所以第二次协议初始化能够成功生成动态接口。

我曾经尝试以下做法:

  • qmodem_network start_service() 中立即执行 network reload
  • wwan0 ifup hotplug 中执行 network reload
  • wwan0_1 add/ifup 后执行 ifup wwan0_4ifup wwan0_6
  • 统一增加 sleep 5sleep 20sleep 30

这些方法在实际调试过程中均未稳定解决问题。主要原因是:

  1. network reload 可能发生得太早,且不一定重置失败的协议实例;
  2. 在 iface hotplug 中再次 reload network 容易形成重复事件;
  3. wwan0_4wwan0_6 尚未被 netifd 注册时,执行 ifup 没有可操作对象;
  4. 固定延时无法适应 USB 枚举、SIM 解锁和运营商注册时间的波动。

因此,应保留“完整 network restart 能恢复”的已验证结论,但把自动化方式改为“等待实际设备就绪后,只在每次开机执行一次”。可以创建 /etc/init.d/restart-network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh /etc/rc.common

START=100
USE_PROCD=1

start_service() {
procd_open_instance
procd_set_param command /bin/sh -c '
marker=/tmp/qmodem-network-restarted
[ -e "$marker" ] && exit 0

ready=0
for i in $(seq 1 90); do
if [ -e /dev/cdc-wdm0 ] && \
[ -d /sys/class/net/wwan0 ] && \
[ -d /sys/class/net/wwan0_1 ]; then
ready=1
break
fi
sleep 2
done

if [ "$ready" -ne 1 ]; then
logger -t restart-network \
"QMI/QMAP devices did not become ready"
exit 1
fi

# 设备节点出现后,再给 QModem/网络注册少量稳定时间
sleep 5
touch "$marker"
logger -t restart-network \
"QMI/QMAP ready, restarting network once"
/etc/init.d/network restart
'
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}

启用:

1
2
3
chmod +x /etc/init.d/restart-network
/etc/init.d/restart-network enable
/etc/init.d/restart-network start

该脚本比固定 sleep 30 更可靠,但仍需要在当前设备上验证。判断恢复成功不能只看 LuCI,应使用:

1
2
3
4
ubus call network.interface.wwan0_4 status
ubus call network.interface.wwan0_6 status
ip -6 addr show dev wwan0_1 scope global
ip -6 route show default

如果条件式启动脚本仍不能稳定恢复,就需要进一步确认 QModem 与 netifd 是否同时对同一 QMI data call 进行拨号。长期最干净的架构是只保留一个拨号控制者;否则任何自动重启都只能缓解竞态,不能消除两套拨号流程之间的控制权冲突。

长期运行后IPv6地址消失

最开始,我认为长期 IPv6 丢失是因为QMI 模式下 DHCPv6/PD 客户端不会像 IPv4 一样自动续租,但这种说法存在问题。OpenWrt 的 odhcp6c 本身负责 DHCPv6、前缀委派和租约生命周期;如果租约无法续期,应继续检查客户端进程、上游会话、netifd 动态接口及 QMI data call,而不能直接认定“不支持续租”。

当前设备上的实际现象是:运行一段时间后,wwan0_6 可能从 netifd 中消失,IPv6 默认路由或公网连通性随之丢失;此时执行完整的 network restart 可以重新创建动态接口并恢复 IPv6。因此更准确的描述是:

QMI/IPv6 会话或 netifd 动态协议状态在长期运行后没有自动恢复,导致 wwan0_6、IPv6 地址、默认路由或前缀委派中的一个或多个状态失效。

我编写的原有检测脚本存在以下问题:

  • 任意一次 ping 失败就立即重启整个网络,容易因移动网络瞬时抖动造成重启风暴;
  • ping 未指定出口接口,可能使用其他 IPv6 路由;
  • 同时要求 wwan0_4 正常,会把纯 IPv4 逻辑状态误算为 IPv6 故障;
  • 没有锁,cron 上一次尚未结束时可能再次启动;
  • 每十分钟持续写详细日志文件,可能造成不必要的日志增长;
  • 只检查 br-lan 地址,不能区分上游 IPv6、默认路由和 LAN 前缀哪个环节失效。

改进后的 /usr/bin/check_ipv6.sh 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/bin/sh

LOCK='/var/lock/ipv6-check.lock'
FAILFILE='/tmp/ipv6-check.fail'
TAG='ipv6-check'
WAN_DEV='wwan0_1'
WAN6_IF='wwan0_6'
TEST_HOST='2606:4700:4700::1111'

lock -n "$LOCK" 2>/dev/null || exit 0
trap 'lock -u "$LOCK" 2>/dev/null' EXIT

logical_if_up() {
ubus call "network.interface.${WAN6_IF}" status 2>/dev/null |
jsonfilter -e '@.up' 2>/dev/null |
grep -qx true
}

has_global_addr() {
ip -6 addr show dev "$WAN_DEV" scope global 2>/dev/null |
grep -q 'inet6 '
}

has_default_route() {
ip -6 route show default 2>/dev/null |
grep -q "dev ${WAN_DEV}"
}

has_connectivity() {
ping6 -I "$WAN_DEV" -c 2 -w 6 "$TEST_HOST" \
>/dev/null 2>&1
}

if logical_if_up && has_global_addr && \
has_default_route && has_connectivity; then
echo 0 > "$FAILFILE"
exit 0
fi

fails="$(cat "$FAILFILE" 2>/dev/null)"
case "$fails" in
''|*[!0-9]*) fails=0 ;;
esac

fails=$((fails + 1))
echo "$fails" > "$FAILFILE"
logger -t "$TAG" "IPv6 check failed (${fails}/3)"

# 移动网络可能短暂切换小区或刷新会话,连续三次失败后再恢复
[ "$fails" -lt 3 ] && exit 0

logger -t "$TAG" \
"IPv6 failed repeatedly; restarting network"

/etc/init.d/network restart

recovered=0
for i in $(seq 1 60); do
sleep 2
if logical_if_up && has_global_addr && \
has_default_route && has_connectivity; then
recovered=1
break
fi
done

if [ "$recovered" -eq 1 ]; then
echo 0 > "$FAILFILE"
logger -t "$TAG" "IPv6 recovered"
else
logger -t "$TAG" "IPv6 recovery failed"
fi

赋予权限并配置 cron:

1
2
chmod +x /usr/bin/check_ipv6.sh
*/5 * * * * /usr/bin/check_ipv6.sh

然后重启 cron:

1
/etc/init.d/cron restart

之所以最终仍使用/etc/init.d/network restart,而不是ifup wwan0_6,是因为发生故障时 wwan0_6 往往已经不再是 netifd 中存在的逻辑接口。对一个尚未注册的动态接口执行 ifup 无法重建它;完整重启 netifd 网络流程才能重新读取 network.wwan0 并再次运行 quectel 协议处理器。

不再建议在 /etc/rc.local 中无条件执行:

1
( sleep 30; /etc/init.d/network restart ) &

因为固定延时已经在实际排障中证明不稳定,而且可能和前述条件式初始化脚本重复执行。开机恢复由 /etc/init.d/restart-network 负责,运行期间的自愈由 check_ipv6.sh 负责,两者不要同时无条件重启网络。

解决方案

修改系统配置

最终方案的核心不是“多写几个 hotplug 脚本”,而是明确配置所有权,并把开机竞态与长期运行故障分别处理。

参考以下模板配置 /etc/config/network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
config interface 'loopback'
option device 'lo'
option proto 'static'
list ipaddr '127.0.0.1/8'

config globals 'globals'
option packet_steering '0'

config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
list ports 'lan4'

config interface 'lan'
option device 'br-lan'
option proto 'static'
option ip6assign '64'
option ip6ifaceid 'eui64'
option ipv6 '1'
list ipaddr '192.168.75.1/24'
list dns '223.5.5.5'
list dns '223.6.6.6'

config interface 'wan'
option device 'wan'
option proto 'pppoe'
option ipv6 'auto'

config interface 'wan6'
option device 'wan'
option proto 'dhcpv6'
option reqaddress 'try'
option reqprefix 'auto'

config interface 'wwan0'
option modem_config '4_1'
option qmodem_preserve '1'
option proto 'quectel'
option metric '11'
option device '/dev/cdc-wdm0'
option ifname 'wwan0_1'
option apn '<运营商APN>'
option pincode '<SIM卡PIN>'
option auth 'none'
option pdptype 'ipv4v6'
option defaultroute '1'
option peerdns '0'
list dns '223.5.5.5'
list dns '223.6.6.6'

参考当前脚本实际读取的字段配置 /etc/config/qmodem。模组路径、AT 端口、频段等字段应保留设备自动检测出的值,不建议照抄其他设备的完整 section,只需要确保拨号关键字段与 network.wwan0 一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
config main 'main'
option enable_dial '1'

config modem-device '4_1'
option alias 'wwan0'
option path '/sys/bus/usb/devices/4-1/'
option data_interface 'usb'
option enable_dial '1'
option soft_reboot '1'
option extend_prefix '1'
option pdp_type 'ipv4v6'
option state 'enabled'
option metric '11'
option name 'rg500q-ea'
option manufacturer 'quectel'
option platform 'qualcomm'
option at_port '/dev/ttyUSB3'
option apn '<运营商APN>'
option pincode '<SIM卡PIN>'
option auth 'none'
option dns_list '223.5.5.5 223.6.6.6'

实际应用顺序为:

1
2
3
4
5
6
7
8
9
10
1. 备份原始 modem_dial.sh 和 UCI 配置;
2. 修正 inetrface 拼写错误;
3. 给固定 wwan0 增加 qmodem_preserve='1';
4. 修改 set_if(),不覆盖被保护接口;
5. 修改 flush_ip_cb(),不删除被保护接口;
6. 同步 network.wwan0 与 qmodem.4_1 的 APN、PIN、PDP 和 DNS;
7. 保持 quectel-CM-M 使用 -i wwan0,不改成 wwan0_1;
8. 开机等待 /dev/cdc-wdm0、wwan0、wwan0_1 出现后,只执行一次 network restart;
9. 定时任务定期检查 wwan0_6、上游全局 IPv6、默认路由和公网连通性;
10. 连续失败三次才执行 network restart。

备份系统配置

修改 /usr/share/qmodem 下的脚本不会自动进入常规 UCI 备份,应将相关文件加入 /etc/sysupgrade.conf,例如:

1
2
3
/usr/share/qmodem/modem_dial.sh
/etc/init.d/restart-network
/usr/bin/check_ipv6.sh

然后检查并生成备份:

1
2
sysupgrade -l
sysupgrade -b /tmp/immortalwrt-backup.tar.gz

添加HTTPS访问

ImmortalWrt 系统默认只支持 HTTP 访问 web 后台,如果还需要为 LuCI 配置 HTTPS,可以继续使用现有 uhttpd 结构,uhttpd 配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config uhttpd 'main'
list listen_http '0.0.0.0:80'
list listen_http '[::]:80'
list listen_https '0.0.0.0:443'
list listen_https '[::]:443'
option redirect_https '1'
option home '/www'
option rfc1918_filter '1'
option max_requests '3'
option max_connections '100'
option cert '/etc/config/certs/cloudflare.pem'
option key '/etc/config/certs/cloudflare.key'
option cgi_prefix '/cgi-bin'
list lua_prefix '/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua'
option script_timeout '60'
option network_timeout '30'
option http_keepalive '20'
option tcp_keepalive '1'
option ubus_prefix '/ubus'

该 HTTPS 配置与 QModem/IPv6 修复是两个独立问题。network restart 后若 Web 后台不可访问,应单独检查:

1
2
3
/etc/init.d/uhttpd status
logread -e uhttpd -e procd
ss -lntp | grep -E ':(80|443)'

不应把 uhttpd 进程崩溃简单归因于 IPv6 地址尚未出现。


ImmortalWrt系统CPE路由器蜂窝模组完整排障记录
https://rookieterry.github.io/2025/07/20/ImmortalWrt系统CPE路由器蜂窝模组完整排障记录/
作者
HackerTerry
发布于
星期日, 七月 20日 2025, 11:56 晚上
许可协议