From d0c79a9f71a54640f34a0693cf708c40cf4d7657 Mon Sep 17 00:00:00 2001 From: butubb <66757505+8butubb@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:58:09 +0800 Subject: [PATCH] =?UTF-8?q?post:=20=E7=A7=BB=E9=99=A4=E6=97=A7=E7=89=88?= =?UTF-8?q?=E6=A0=87=E9=A2=98=EF=BC=88=E5=B7=B2=E5=90=88=E5=B9=B6=E8=BF=9B?= =?UTF-8?q?=E3=80=8A=E7=BB=99=E9=A3=9E=E7=89=9B=20NAS=20=E5=8A=A0=E8=A3=85?= =?UTF-8?q?=E5=A4=A7=E7=96=86=204G=20=E6=A8=A1=E5=9D=97=E4=B8=80=E4=BB=A3?= =?UTF-8?q?=E5=BD=93=E5=A4=87=E7=94=A8=E7=BD=91=E3=80=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...NAS加装4G模块当备用网:拔掉网线也不掉线.md | 235 ------------------ 1 file changed, 235 deletions(-) delete mode 100644 content/posts/给飞牛NAS加装4G模块当备用网:拔掉网线也不掉线.md diff --git a/content/posts/给飞牛NAS加装4G模块当备用网:拔掉网线也不掉线.md b/content/posts/给飞牛NAS加装4G模块当备用网:拔掉网线也不掉线.md deleted file mode 100644 index eff7895..0000000 --- a/content/posts/给飞牛NAS加装4G模块当备用网:拔掉网线也不掉线.md +++ /dev/null @@ -1,235 +0,0 @@ ---- -title: 给飞牛 NAS 加装 4G 模块当备用网:拔掉网线也不掉线 -tags: [nas, 4g, 网络, 飞牛os] -date: 2026-10-05T00:00:00Z -summary: "单条网线的 NAS 一断线就彻底失联:中继连不上、机器人掉线、远程全废。这篇记录我给飞牛 NAS 插上 USB 4G 模块做备用链路,一路踩过 OVS 藏断线、僵尸默认路由、网口编号漂移、模块编号变化这些坑,最后实现「拔线自动切 4G、插回自动切回」的全过程。" ---- - -## 起因:单线 NAS 的焦虑 - -家里的 NAS 只有一条网线。平时岁月静好,但只要出现下面任意一种情况: - -- 网线被谁碰掉(比如我插拔 USB 设备时顺手带下来) -- 光猫/路由器抽风重启 -- 宽带施工挖断 - -NAS 上的东西就**全部对外失联**:远程访问进不来、自建的机器人掉线、同步全部卡住,而人在外面只能干瞪眼。 - -想上第二条宽带吧,又贵又没必要。所以很自然就想到了:**插一块 4G 模块当备用链路**,平时待命,主线断了自动顶上去。 - -## 硬件:一块 USB 4G 模块 + 一张流量卡 - -硬件很朴素:一块 USB 接口的 4G 模块(Quectel EC25 方案的成品,USB ID `2c7c:0125`),加一张运营商的流量卡。 - -插上飞牛(fnOS,Debian 12)之后,Linux 这边其实**什么都没配就能认**: - -```bash -lsusb | grep -i quectel # Bus 007 Device 003: ID 2c7c:0125 Quectel EC25 LTE modem -mmcli -L # /org/freedesktop/ModemManager1/Modem/0 QUECTEL Mobile Broadband Module -ip -br link | grep wwan # wwan0 UP -ip route show default -# default via 192.168.20.1 dev enp4s0-ovs proto static metric 100 ← 有线(主) -# default via 10.200.35.20 dev wwan0 metric 5000 ← 4G(备) -``` - -几个要点: - -- 网卡驱动是 `qmi_wwan`,出来一个 `wwan0`; -- 拨号是 **ModemManager 的 initial EPS bearer** 自动做的,**NetworkManager 完全不接管它**(这点后面很关键); -- 运营商给的是内网地址(CGNAT)+ 一段 IPv6; -- 默认路由的 `metric` 决定优先级:**数字小的优先**。所以有线 100、4G 5000 —— 有线在的时候,4G 就是一条安静的备胎。 - -## 飞牛的第一盆冷水:界面里根本没有 4G - -打开飞牛「网络设置」,**找不到这块 4G 网卡**。 - -一开始以为是配置问题,后来翻了 `/usr/trim/bin/network_service` 二进制、它的日志、以及前端页面资源,发现里面连 `wwan`/`modem` 这类字样都没有 —— 结论很干脆:**飞牛的网络设置目前不支持 WWAN 类型的接口**。 - -所以 4G 这条链路只能靠命令行管(`mmcli` / `ip` / `nmcli`)。界面指望不上,但也不影响用。 - -## 大坑:拔掉网线,NAS 反而彻底失联 - -最反直觉的地方来了。 - -信心满满地拔掉网线,结果**整台 NAS 直接像死了一样**:远程中继连不上、飞书机器人静默、连域名都解析不出来。 - -但诡异的是: - -```bash -curl -4 -s --interface wwan0 https://ifconfig.me/ip # ✅ 走 4G 单独测,通! -ip route show default # ✅ 4G 那条 metric 5000 明明在路由表里 -``` - -**4G 是好的,路由也在,但流量就是不从它走。** 这就是典型的需要抓现场的问题 —— 等你回我消息的时候,故障现场早就过去了。 - -### 第一步:用「触发式采样器」抓现场 - -间歇性故障最忌讳猜。做法是写个采样器,**等状态跳变**(比如物理网口 `carrier` 从 1 变 0),跳变后立刻连采十几轮,把每一轮的路由表、DNS、每个接口单独发请求的出口 IP、中继域名可达性全记下来。 - -采样结果非常干净: - -- 有线接口 `enp4s0` 的 `carrier=0`(物理上确实断了); -- 但桥接口 `enp4s0-ovs` 的 `carrier` **恒为 1**; -- 断网窗口里,ping/HTTPS/DNS 全是超时(`000`),而 `curl --interface wwan0` 单独走 4G 是通的。 - -### 第二步:根因 —— OVS 把断线藏了起来 - -我的网口套在 OVS 里。**OVS 会把物理断线藏起来**: - -- 物理口 `enp4s0`:`carrier=0`(断了) -- OVS 内部口 `enp4s0-ovs`:`carrier` 永远是 1 - -于是 **NetworkManager 根本不知道线断了**,那条 `proto static metric 100` 的默认路由**不会撤销**。结果就是:所有流量继续往一个已经不可达的网关灌(黑洞),而 4G 的 `metric 5000` 老实排在后头,**永远轮不到**。 - -所以这不是 4G 的问题,是**僵尸默认路由**的问题。 - -## 解法:2 秒轮询的默认路由看门狗 - -思路很直接:**盯物理口的 carrier**,断了就把 4G 那条默认路由的 metric 从 5000 提到 50(压过有线的 100),线回来再撤掉。 - -```bash -PHYS=enp4s0 # 物理口(读 carrier 用) -LTE_DEV=wwan0 # 4G 网卡 -LTE_PRIO_METRIC=50 # 临时优先权 - -carrier() { cat /sys/class/net/$PHYS/carrier 2>/dev/null || echo "?"; } -lte_gw() { ip route show default | awk -v d="$LTE_DEV" '$1=="default" && $5==d {print $3; exit}'; } - -# 注意:判断 metric 必须用数字比较,别用字符串匹配(见下面的坑) -route_metric_is() { # $1=网关 $2=网卡 $3=metric - ip route show default | awk -v g="$1" -v d="$2" -v want="$3" ' - $1=="default" && $3==g && $5==d { for (i=1;i<=NF;i++) if ($i=="metric" && $(i+1)+0==want+0) found=1 } - END { exit(found?0:1) }' -} - -check_once() { - c=$(carrier); lg=$(lte_gw) - case "$c" in - 0) # 有线断了 → 给 4G 提权 - if [ -n "$lg" ] && ! route_metric_is "$lg" "$LTE_DEV" "$LTE_PRIO_METRIC"; then - ip route replace default via "$lg" dev "$LTE_DEV" metric "$LTE_PRIO_METRIC" - fi ;; - 1) # 有线恢复 → 撤掉临时路由 - if [ -n "$lg" ] && route_metric_is "$lg" "$LTE_DEV" "$LTE_PRIO_METRIC"; then - ip route del default via "$lg" dev "$LTE_DEV" metric "$LTE_PRIO_METRIC" - fi ;; - esac -} -``` - -### 这里有两个必须踩过才知道的坑 - -**坑 1:不要动 NetworkManager 管的那条路由。** - -我最早的做法是去改**有线**那条默认路由的 metric(100 → 10000),结果 **30 秒内就被 NM 改回 100**,看门狗白干 ✗。 - -正确姿势是改**没人管的那条**:4G 的默认路由是 **ModemManager** 建的,NM 不碰它,所以把它从 `metric 5000` 提到 `metric 50`,稳稳压过有线的 100 ✓。 - -> 通用经验:**改配置前先问「这条配置属于哪个管理器?我改它会不会被覆盖?」** 会覆盖,就换一个等价但归你的杠杆。 - -**坑 2:判断 metric 别用 `grep` 做前缀匹配。** - -我第二版用了 `grep "metric 50"` 来判断"临时优先路由还在不在",看着挺对,实际灾难: - -``` -default via 10.200.35.20 dev wwan0 metric 5000 - ^^^^ 里面就含 "metric 50" -``` - -于是有线恢复之后,脚本以为临时路由还在,**每 2 秒重复删一次、刷满日志** ✗。改用 `awk` 把 metric 取出来做**数字比较**之后,世界清净了。 - -## 部署:非 root 也能长期维护的「转发脚本 + 自更新」 - -这台 NAS 上我跑服务的账号没有 root,每次改脚本都要找人 `sudo` 重装,很烦。所以又套了一层: - -1. `/usr/local/sbin/wan_carrier_fix.sh` 只放一个 **3 行的 stub**,干一件事:exec 到工作目录里的真身; -2. 真身脚本放在普通用户可写的工作目录里,主循环每 2 秒顺手比一下**自己的 mtime**,发现被改了就地 `exec "$SELF"` 重新加载。 - -```bash -# /usr/local/sbin/wan_carrier_fix.sh —— 只装一次,以后再也不碰 -#!/bin/bash -exec /path/to/real/wan_carrier_fix.sh "$@" -``` - -```ini -# /etc/systemd/system/wan-carrier-fix.service -[Unit] -Description=WAN carrier failover watchdog -After=network-online.target - -[Service] -ExecStart=/usr/local/sbin/wan_carrier_fix.sh -Restart=always -RestartSec=3 - -[Install] -WantedBy=multi-user.target -``` - -效果:**以后改脚本只要改工作目录里那份,2 秒后自动生效**,systemd 只负责兜底崩溃重启。省下的都是找 sudo 的时间。 - -(小细节:自更新重载必须用**绝对路径**,因为 systemd 给的工作目录可能是 `/`。) - -## 切到 4G 之后要处理的两件事 - -**① 出口 IP 变了,中继要重新握手。** -4G 出口 IP 和宽带完全不是一个网段。家里的 NAS 中继(fnid)是长连接,切过去之后它那边不认旧会话,**必须重启一次中继服务**(`systemctl restart trim_connect`),重启完立刻可用 ✓。 - -**② 裸 4G 只有出、没有进。** -4G 是运营商 CGNAT,**入站不可能**。所以对外访问要么走中继、要么走 Tailscale —— 局域网直连在断线时必然不通,这不是故障。 - -顺带一个同源坑:**给隧道/反代配置回源地址时别写局域网 IP**。我原来写的是 `http://192.168.20.18:8080` 这种,一旦网卡上的地址没了,回源直接断(外部看到 530)。改成 `http://127.0.0.1:8080` 之后,**跟走哪条线路、哪个地址完全无关**了 ✓。 - -## 别让 4G 偷偷烧流量 - -装完之后我看了眼计数器,吓了一跳:**开机 36 分钟,4G 上行已经 3 GB** 😅 - -原因:机器上 **IPv6 的默认路由只有 4G 这一条**,所以所有 IPv6 出站流量都在走 4G。而跑着 qBittorrent 的话,做种上传会疯狂吃 IPv6。 - -两个处理: - -- **治本(软件里就能做)**:qBittorrent → 设置 → 高级 → 「网络接口」选有线口、「可选 IP 地址」选有线地址 → 它只用有线收发,线断了它自己哑掉,**绝不会吃 4G** ✓ -- **兜底**:加一个静默流量看门狗,**只在"有线本来就正常"时才可能报警**(当日 4G > 1 GB,或单轮增量 > 200 MB),断线期间 4G 是唯一出口,属正常使用,一个字都不说。 - -## 换个 USB 口的意外收获:别写死网卡名 - -后来我把 4G 模块换个 USB 口插,顺手发现两个"绝对不能写死"的东西: - -1. **ModemManager 里的模块编号会变**:`Modem/0` 变成了 `Modem/2`; -2. **接口名也可能变**(这次还是 `wwan0`,但没理由指望它永远不变)。 - -所以所有脚本都改成了**自动识别**: - -```bash -# 找 4G 网卡:优先看驱动是不是 qmi_wwan,退路才找 wwan* -for d in /sys/class/net/*; do - [ -e "$d/device/driver" ] || continue - [ "$(basename "$(readlink -f "$d/device/driver")")" = "qmi_wwan" ] && basename "$d" -done -``` - -另外还遇到一次 ModemManager 状态错乱:`mmcli` 报 `state: failed / failed reason: sim-missing`,可数据其实是通的(模块自己续着会话)。`systemctl restart ModemManager` 让它重新识别 SIM 即可 —— **但要注意,这时 MM 已经不托管任何数据会话了,一旦掉线它不会自动重连**,所以别放着不管。 - -## 效果 - -现在这套跑下来,日常是这样的: - -| 场景 | 表现 | -| --- | --- | -| 拔掉网线 | 1~2 秒内 4G 提权顶上,NAS 服务、远程访问、机器人都不断 | -| 插回网线 | 自动撤掉临时的 4G 路由,出口回到有线,4G 回到待命 | -| 只有 4G 时 | 远程访问走中继(需重启一次中继服务),局域网直连不通属正常 | -| 有线正常时 | 4G 只当备胎,流量几乎为零(qB 已绑定网卡) | - -成本就是一块 4G 模块 + 一张流量卡,比第二条宽带便宜太多了。 - -## 小结 - -- **单线 NAS 的备用链路,值得折腾**;4G 模块在 Linux 上几乎是插上就能用; -- **断线切换的坑常常不在备用链路本身,而在主链路的"僵尸状态"**(本例:OVS 藏了物理断线 → 默认路由不撤 → 备用链路永远轮不到); -- **别跟上层管理器抢它管的配置**,换个"没人管的等价杠杆"(改 4G 那条 metric,而不是有线那条); -- **判断数值别用字符串匹配**,`metric 50` 会被 `metric 5000` 命中; -- **脚本别写死网卡名/模块编号**,用 `qmi_wwan` 驱动去认; -- 计量的链路上,**把大流量程序绑到主网卡**,再配一个"只在异常时才说话"的看门狗。 - -折腾的过程很烦,但结果是"真香":现在拔网线,我甚至感觉不到。