0. 前言
在 fw4(nftables)环境中,可以使用 ipset 保存电信、移动和境外地址表,再由 mwan3 根据目标地址选择不同的出口。
这种配置运行起来并不复杂,但实际使用中可能遇到两个时序问题:
- OpenWrt 启动时,ipset 地址表已经加载,而电信、移动的 PPPoE 还没有拨号完成。此时过早执行
mwan3 reload,mwan3 无法基于完整的接口和路由状态生成规则。 - 运营商定期断开 PPPoE,接口自动重拨并取得新地址后,mwan3 的运行状态没有及时刷新,最终出现分流错乱。
本文使用两个职责明确的脚本解决这些问题:
ipset-load.sh:开机加载地址表。99-mwan3-wan-reload:在电信或移动接口重新上线后,自动重载 mwan3。
ipset 负责地址数据,mwan3 负责策略与选路,OpenWrt hotplug 负责监听接口事件。
1. 网络环境与问题原因
本文使用三个 mwan3 逻辑接口:
ct:中国电信 PPPoE 出口。cm:中国移动 PPPoE 出口。glb:使用静态 IP 的代理 OpenWrt 虚拟机。
其中 ct 和 cm 会因为开机拨号、运营商强制重拨或临时掉线而重新上线;glb 是本地静态接口,通常不会发生 PPPoE 重拨,所以只需要监控 ct 和 cm。
原来的开机流程如下:
|
|
增加接口 hotplug 钩子后,流程变为:
|
|
同一个钩子也能处理路由器运行期间发生的 PPPoE 自动重拨。
2. 地址表加载脚本
2.1 脚本代码
将下面的脚本保存为 /usr/bin/ipset-load.sh:
|
|
2.2 脚本原理
load_set 函数接收两个参数:ipset 集合名称和对应的地址表文件。
以电信地址表为例:
|
|
脚本通过 awk 把文本地址表转换成 ipset restore 能够批量执行的命令:
|
|
参数 -exist 表示集合或条目已经存在时不报错。因此,同一脚本既可以在首次开机时运行,也可以在更新地址表后重复运行。
地址表应该分别放在:
|
|
文本文件一行写一个 IP 或 CIDR,可以包含空行和以 # 开头的注释:
|
|
2.3 设置开机加载
赋予执行权限:
|
|
编辑 /etc/rc.local,在 exit 0 前加入:
|
|
rc.local 只在开机阶段执行一次,因此适合恢复 ipset 地址表,但不能用于持续监听 PPPoE 重拨。
3. PPPoE 上线监听脚本
3.1 为什么使用 hotplug
OpenWrt 会在网络接口上线或下线时执行 /etc/hotplug.d/iface/ 中的钩子,并向脚本传入环境变量:
ACTION:事件类型,例如ifup或ifdown。INTERFACE:/etc/config/network中的逻辑接口名,例如ct、cm。
需要注意,INTERFACE 不是 eth0、pppoe-wan 等设备名称。
3.2 脚本代码
将下面的脚本保存为 /etc/hotplug.d/iface/99-mwan3-wan-reload:
|
|
3.3 接口筛选
下面的配置表示只监控电信和移动接口:
|
|
LAN、loopback 和静态的 glb 接口事件都会被忽略。如果以后还需要监控其他动态 WAN,可以把对应逻辑接口名加入列表:
|
|
3.4 延迟与重复事件处理
接口刚产生 ifup 事件时,地址已经取得,但路由状态仍可能处于更新阶段。因此脚本默认等待 5 秒:
|
|
如果设备启动较慢,或者开机后仍偶尔出现规则异常,可以提高到 10 秒:
|
|
PPPoE 重连期间可能连续产生多个相近事件。脚本使用 /var/lock/mwan3-wan-reload.lock 作为互斥锁:第一份脚本创建锁目录后继续执行,后续重复事件发现锁已存在便直接退出。
如果 ct 和 cm 几乎同时上线,通常只会执行一次 mwan3 reload。这不会遗漏某一套规则,因为 reload 会重新加载完整的 mwan3 配置,而不是只刷新触发事件的单个接口。
3.5 为什么文件没有 .sh 后缀
Linux 脚本能否执行取决于第一行的解释器声明和文件执行权限,而不是扩展名:
|
|
OpenWrt 的 hotplug 钩子通常使用 数字-名称 的格式命名,例如:
|
|
前面的 99- 让该钩子在多数接口钩子之后运行。ipset-load.sh 是放在 /usr/bin 中供人主动调用的普通脚本,保留 .sh 后缀更容易辨认;两种命名方式都不影响 Shell 执行。
4. 安装方法
假设已经把两个文件上传到路由器的 /tmp 目录,执行:
|
|
最终文件结构如下:
|
|
两个脚本不要放在同一个位置,也不要把 hotplug 钩子加入 rc.local:
| 文件 | 放置位置 | 调用方式 |
|---|---|---|
ipset-load.sh |
/usr/bin/ipset-load.sh |
/etc/rc.local 在开机时调用 |
99-mwan3-wan-reload |
/etc/hotplug.d/iface/99-mwan3-wan-reload |
OpenWrt 在接口上线时自动调用 |
5. 测试与验证
5.1 测试地址表加载
手动运行:
|
|
检查三个集合:
|
|
5.2 模拟接口上线
模拟电信接口上线:
|
|
模拟移动接口上线:
|
|
等待超过 RELOAD_DELAY 设置的时间,然后查看日志:
|
|
正常情况下可以看到:
|
|
最后检查 mwan3 的接口和规则状态:
|
|
5.3 重启验证
完成手动测试后重启路由器:
|
|
重新登录后依次检查:
|
|
如果日志中出现 ct 或 cm 上线后重载 mwan3 的记录,并且规则恢复正常,说明开机时序问题已经解决。
6. 常见问题
6.1 为什么不能只用 rc.local
rc.local 只在系统启动时运行一次。运营商在数小时或数天后强制断开 PPPoE 时,它不会再次执行,因此无法处理运行期间的自动重拨。
hotplug 是由接口事件驱动的:只要 ct 或 cm 再次上线,脚本就会重新执行。
6.2 是否需要监控 glb
本文中的 glb 是使用静态 IP 的本地代理 OpenWrt 虚拟机,不参与 PPPoE 拨号。代理节点的外部连接最终仍通过 ct 或 cm 出口,因此通常不需要监控 glb。
即便代理虚拟机重启,主路由上的静态 glb 接口也不一定产生新的 ifup 事件。如果确实需要检测代理虚拟机是否在线,应使用独立的可达性检测方案,而不是简单地把 glb 加入 hotplug 列表。
6.3 如何确认逻辑接口名称
执行:
|
|
确认 /etc/config/network 中的逻辑接口名是否确实为 ct 和 cm。不要把 pppoe-ct、pppoe-cm 或物理网卡名填入 WAN_INTERFACES。
6.4 为什么重载而不是重启
本文执行的是:
|
|
它用于重新加载 mwan3 策略,不会主动重启 PPPoE 接口。相比重启整个网络服务,它影响更小,也不会为了修复分流而制造一次新的断线。
7. 总结
这套方案把开机初始化和接口事件处理拆成两个脚本:
|
|
最终既解决了 OpenWrt 开机时 PPPoE 尚未完成的问题,也处理了运营商定期重拨后 mwan3 分流失效的问题。两个脚本各自只负责一类任务,后续维护和排错也更直观。