捡垃圾捡到宝:把淘汰的 ZTE MF253S 4G 模块塞进 Linux 当原生网卡
本文最后更新于 32 天前,其中的信息可能已经有所发展或是发生改变。

前言です

事情的起因非常简单:作者在某鱼上花了一杯奶茶钱,淘到一块从旧 CPE(运营商送的 4G 家用路由)里拆出来的 mSATA 模块——ZTE MF253S,方案 ZX297510。

想法也很朴素:插进电脑的 mSATA 槽,当一张原生 4G 网卡用。能用就用,不能用就扔。

しかし……嘻嘻嘻(千早爱音唐笑),这东西不仅不能用,还会让你怀疑人生:插上去,开机 21 秒,它准时重启一次,像极了到点下班的本社畜;ModemManager 一碰它,它直接连人带机跑进 BootROM,连尸体都懒得留。

为什么呢?因为这块模块的固件是给运营商 CPE 定制的,压根没打算让你当普通网卡用。作者翻遍了恩山、读完了 ModemManager 的插件源码、把它家 CPE 的日志都掏出来看了一遍——最后给它写了个内核驱动。现在它已经能在装 stock ModemManager / NetworkManager 的机器上即插即用:任务栏直接弹出「移动数据」,跟正经的 EC20 一个待遇。

这篇文章就把整个折腾过程从原理到代码讲清楚。


硬件参数

项目 详情
模块型号 ZTE MF253S / ME3760V2
基带方案 ZTE Sanechips ZX297510(WF7510)
接口 mini-PCIe / mSATA(USB 2.0 信号)
USB ID 19d2:0199(正常模式)/ 19d2:0256(BootROM)
网络制式 TD-LTE(移动定制,硬件上只校准了 GSM / TD-SCDMA / LTE,没有 WCDMA)
USB 接口布局 if0 = USB-AT,if1 = USB-Rndis(ECM 数据),if2 = USB-Modem,if3 = USB-log,if4 = USB-adb

划重点:if1 是 ECM 数据口,但它不是标准 CDC ECM,主机侧需要自己写驱动绑定。


适用人群

会使用 Linux,能自己编译内核模块。此教程并不适合小白——如果你只是想插卡上网,建议直接买正经的 EC20 / RM500U。

准备材料

  • ZTE MF253S / ME3760V2 模块一块
  • 带 mSATA 槽的电脑一台(别用 mSATA 转 USB 的板子,容易供电不足)
  • 极强的动手 / 思考 / 搜索能力
  • AI 一位(这次用的是 DeepSeek V4.1 Flash,能啃几万行 C 代码,比作者自己靠谱多了)
  • 良好的网络环境(你知道我在说什么 👁)

为什么这么难搞(原理)

先说结论:这块模块的固件,从设计上就没考虑过「标准主机栈」这件事。作者踩过的坑依次如下:

  1. QMI 是死路。qmicli --dms-get-manufacturer 能通,但任何 UIM / NAS 类命令都会把它搞崩——直接掉进 BootROM 模式重启。ModemManager 如果按 QMI 去探它,就是无限的 21 秒重启循环。
  2. PPP 拨号被拒绝。LTE 的默认承载在 attach 时自动激活,此时再发 ATD*99***1#,模块直接回 +CME ERROR: 3。想用 PPP?只有刚做完 CFUN 循环的那几秒能用,等于没用。
  3. 没有 DHCP。ECM 网卡口不跑 DHCP 服务端。IP 地址、网关、DNS 只在私有 URC +ZGIPDNS 里出现,长这样:
    +ZGIPDNS: 1,1,"IP","10.x.x.x","0.0.0.0","36.x.x.x","36.x.x.x"
    其中网关字段恒为 0.0.0.0,需要自己推导成「IP+1」。
  4. 数据通道要靠私有命令激活。光发 AT+CGACT=1,1(标准 PDP 激活)是不够的——ECM 数据口必须再收到 AT+ZGACT=1,1 才会真正通数据。这个命令只有它家 CPE 会用。
  5. 21 秒自复位。模块要求主机定时轮询(CSQ / CEREG 之类的保活),不轮询就自杀重启。初始化和心跳序列是作者把它家 CPE 的 RAtlog 日志逆出来的:AT+ZEACT=2 → AT+CGDCONT=1,IP,CMNET,,0,0 → AT+ZGAAT=0 → AT+CFUN=1 → AT+ZSET=EXCEPT_RESET,1 → AT+ZEMCI=0。
  6. 还有一堆标准命令它不认识。ATZ、AT+GCAP、AT+WS46=? 统统回 +CME ERROR: 6003——而 ModemManager 初始化第一步就发 ATZ,直接卡死在「启用模块」阶段。

方案演进:从守护进程到内核驱动

阶段一:用户态守护进程(能跑,但丑)

最初作者写了个 Python 守护进程:开机 → 打开 AT 口 → 发初始化序列 → 拨号 → 解析 +ZGIPDNS → 自己配 IP / 路由。能上网,问题是一堆:

  • 要在 systemd 里装服务、装 udev 规则、装 modprobe 黑名单——换个发行版就全废,完全没有即插即用;
  • ModemManager 只能看不能用(PPP 拨号被拒),任务栏里的「移动数据」永远连不上;
  • 守护进程和 MM 抢 AT 口,得靠 udev 规则让 MM 装瞎。

能用,但作者很不爽:这明明应该是驱动的活。

阶段二:全部下沉进内核(最终方案)

最终方案是两个内核模块,用户态零辅助进程:

模块 职责
zte_ecm.ko 基于 usbnet / usbnet_generic_cdc_bind,绑定 if1(ECM 数据口),生成标准网卡 enpXsYfZuVi1
zte_atfix.ko AT 命令修复 + 伪装 + 真实拨号翻译 + 内核态初始化 / 保活 + +ZGIPDNS 缓存 + %IPDPADDR 应答

驱动是怎么把 ModemManager 骗过去的

让 MM 直接管理这块模块的核心思路:把它伪装成 MM 已经完美支持的另一类模块。作者选了 ZTE 的 Icera 系列(比如 MF821)——因为 MM 的 ZTE 插件对「非 Icera」模块会直接忽略网卡口,而对 Icera 模块则会走「NET 口 + 静态 IP」的 bearer 路径,正好契合这台 ECM-only 的机器。

MM 发出的命令 驱动的处理
ATZ 重写为 ATE0 再转发
AT+GCAP 伪造 +GCAP: +CGSM,+CLTE(让 MM 知道这是 LTE 模块,才会开 EPS 跟踪)
AT+WS46=? / AT+WS46=<n> 伪造模式列表 / 直接回 OK
AT%IPSYS? 伪造 %IPSYS: 0,1,0——关键中的关键,MM 的端口探测只要在响应里看到 %IPSYS: 就把模块归类为 Icera
AT%IPDPACT=<cid>,1 不转发(模块不认识),驱动在私有口上真正执行 AT+CGACT=1,1 + AT+ZGACT=1,1,等数据通道起来后再回 OK 和一条 %IPDPACT: <cid>,1,0 URC
AT%IPDPADDR=<cid> 用嗅探到的 +ZGIPDNS 缓存构造标准响应:IP / 网关(自动 IP+1)/ DNS,让 MM 拿到一份「静态 IPv4 配置」

另外几个细节:

  • 私有口隐身术。驱动接管了 if2(原本给守护进程用的口),做法是把 ModemManager 探口时发的裸 AT 探测直接吞掉——MM 探不到响应,就把这个口标记为「非 AT 口」自动无视。驱动自己的命令走 bulk 端点,根本不过 tty 层。
  • 内核心跳。probe 完成后自动跑 CPE 逆向出来的初始化序列,然后每 2 秒轮询一次 AT+CSQ / AT+CEREG? / AT+COPS?——21 秒自复位从此绝迹。
  • autosuspend 也在驱动里关掉,不再需要 udev 规则。
  • APN 自动识别。驱动读 IMSI 前五位,46000/46002/46004/46007/46008 → CMNET,46001/46006/46009 → 3gnet,46003/46005/46011 → ctnet。

实测效果

装上之后,ModemManager 的视角:

Status | state: registered
       | signal quality: 100%
       | operator name: CHINA MOBILE
       | packet service state: attached
       | ports: enp0s21f0u5i1 (net), ttyUSB0 (at)
       | ignored ports: ttyUSB1 (unknown), ttyUSB2 (unknown)

NetworkManager 里直接出现「移动数据」,点一下就连上,网卡自动配好 IP / 默认路由 / DNS:

$ nmcli connection up "China Mobile"
连接已成功激活

$ ip addr show enp0s21f0u5i1
    inet 10.x.x.x/32 scope global noprefixroute enp0s21f0u5i1

$ ping -I enp0s21f0u5i1 -c 3 223.5.5.5
3 packets transmitted, 3 received, 0% packet loss
rtt min/avg/max/mdev = 51.5/56.4/61.8/4.2 ms

$ curl --interface enp0s21f0u5i1 -o /dev/null -w '%{http_code}' http://www.baidu.com
200

重启电脑?模块自动加载,MM 自动认卡,NM 自动连接。全程无守护进程、无 udev 规则、无手工命令——这才是即插即用该有的样子。


⚠️ Tips

  • mSATA 不能热插拔!一定要关机 → 插拔 → 开机。别问,问就是作者重启了三次。
  • 别折腾 QMI。这模块的 QMI 是残废的,一探就崩。内核里建议把 qmi_wwan 拉黑。
  • IPv6 别指望。固件虽然声称支持 IPV6 PDP(实测能激活、能拿到链路本地地址和 DNS),但 ECM 通道没有任何 RA/NDP 转发能力,拿不到全局地址,实际不可用。
  • 网络制式看硬件。TD-LTE 版本只在硬件上校准了 GSM / TD-SCDMA / LTE,插联通的卡是收不到信号的——这不是锁,是物理上就没有 WCDMA 校准数据,别去折腾什么解锁码和全网通固件。
  • 不同批次的模块固件可能略有差异,本文基于 ZTE_MF253SV1.0.0B01。

后续:一个不得不做的妥协(ModemManager 与 Icera 插件)

文章发出之后有朋友来问:GNOME 里的网络模式为什么会蹦出个 5G?还有 2G/3G 都退网了,为什么还留着这些选项?作者去翻了一圈,发现这是 ModemManager 的历史包袱,值得展开讲讲。

先说背景:这块 ZTE 模块必须伪装成 ZTE Icera 机型,ModemManager 才会把它的 ECM 网口当作数据通道(非 Icera 的 ZTE 模块会被 MM 直接忽略网卡口,那数据就走不了了)。但 MM 的 Icera 插件写于 LTE 普及之前,它的模式/制式映射里根本没有 LTE——上游没有,作者机器上这个 1.25.95 定制版也没有(二进制反汇编确认过)。于是就有了下面这些”善意谎言”:

  • 5G 是哪来的:MM 的 WS46 表里 25 = GERAN+UTRAN+E-UTRAN 会被映射成 MM_MODEM_MODE_ANY(全比特,含 5G),GNOME 就把 5G 列了出来。驱动现在改报 +WS46: (28)(纯 E-UTRAN);
  • 模式选择器:Icera 类实际用 AT%IPSYS=? 加载模式,而它的解析器只有 2G/3G(连 mode 4 = LTE 的分支都没有,日志会打 unsupported mode found in %IPSYS=?: 4)。驱动伪造成 3G-only → GNOME 只显示一个”3G”条目,没有 5G、也没有 2G;用户在界面切换模式驱动也直接回 OK(模块实际仍锁在 LTE);
  • 网络类型显示:Icera 的 nwstate_to_act() 同样没有 LTE 分支,驱动只能上报最接近的 HSPA+(GNOME 显示 3G)——技术串可以用 nwstate_tech 模块参数自己调;
  • 信号 100% 的锅:固件 +CSQ 返回的是私有刻度 253 + RSRP(比如 149 → RSRP -104 dBm,99/100 表示未知),MM 按标准 0-31 解析后直接钳位成满格。驱动把它归一化回标准刻度再上报,现在显示的是真实信号(约 60%)。

再补一刀?不,是”姿势”问题——短信其实能收发

先前的结论是”能发、收随缘”:发送实测 OK(ModemManager 的 Messaging 接口,PDU 模式,短信中心 +8613800745500),但收到的短信死活落不进 SIM 卡存储(+CPMS: "SM",0,50 永远是空的),连验证码都进不来。

排查过程全绿:+CSMS: 0,1,1,1(MO/MT 全支持)、+CEMODE: 2(CS/PS mode 2,正是 LTE 上走 NAS 收发短信的正确模式)、+CNMI: 2,1,2,1,0(新短信通知已开)、短信中心也填了。参数一个不错,短信就是不来。

最后死马当活马医:手动 AT+CFUN=0 再 AT+CFUN=1 强制重新附着——验证码立刻就收到了(【哔哩哔哩】965014,5 分钟内有效,请勿泄露 😋)。

结论:不是半残废,是“附着姿势”问题——这块 2013 年的固件在已经附着(attach)的状态下不会重新向网络协商 LTE 短信(SMS over NAS)能力,MME 于是不给投递 MT 短信;而发送是 UE 主动发起,所以一直正常。驱动里加了一次初始化 CFUN 0→1 循环(force_reattach 参数可关,代价是开机多等约一分钟),现在收发都通了。

想要 GNOME 里显示真正的 4G/LTE?给 MM 的 Icera 插件打个小补丁就行:add_supported_mode() 加一个 case 4 → MM_MODEM_MODE_4G,nwstate_to_act() 加一个 "lte" 分支,一共十行左右。驱动层面到此为止——总不能强求一个 2013 年的插件认识 LTE 吧(笑)。

相关代码和说明已同步到仓库:github.com/xmb505/zte-mf253s

收尾:现在它真的”插上就能用”了

折腾到这里,最终验收场景是这样的:

  1. 重启电脑,什么都不干;
  2. 约 1 分钟后,任务栏自动出现「移动数据」,NetworkManager 自动连上「China Mobile」;
  3. ping -I enp0s21f0u5i1 → 0% 丢包,60~100ms;
  4. 手机发个验证码过来 → ModemManager 自动收下:「【知乎】你的验证码是 763670」。

全程没有守护进程、没有 udev 规则、没有 modprobe 黑名单、没有一条手工命令——两个内核模块加一个开机自启的 ModemManager,齐活。

收尾审查时又抓出三个”看起来能用、其实有雷”的典型 bug,记录一下:

  • 设备认领:模块的接口同时会被 option/qmi_wwan 匹配,谁先 probe 谁得手。驱动现在会在加载时把它从别的驱动手里释放并强制接管——热插拔有通知、每秒还有看门狗兜底,实测连手动 unbind 都让不出去(1 秒内自动收回)。也正因为有这层,qmi_wwan 黑名单删掉了:同一台机器上的 EC20 照常用 QMI,互不干扰;
  • 私有口防泄漏:驱动用 if2 做初始化/拨号/保活,结果 ModemManager 探测时从另一个读 URB 看到了驱动的轮询应答,把私有口当成半个 AT 口抢走。修法很简单粗暴——私有口的数据永远不进 tty 层;
  • 命令通道竞态:保活轮询的”迟到应答”会冒充下一条命令的响应,导致 ZGACT 秒回”成功”但实际没执行(表现为 ping 不通、ARP FAILED)。修法:发命令前先排空迟到数据。

外加一个冷启动专属的坑:ModemManager 服务是 disabled 的,开机没人管理模块,NetworkManager 就把 ECM 网卡当成了普通”USB 以太网”——这大概是最符合”你以为驱动写完了、其实环境在演你”的一个 bug(笑)。

至此收官:数据 ✅、信号 ✅、短信收发 ✅、EC20 共存 ✅、IPv6 焊死 ✅、制式显示 3G(MM 插件限制,认了)。一块从运营商淘汰设备里拆出来的电子垃圾,终于变成了 Linux 下即插即用的原生 4G 网卡。代码在 github.com/xmb505/zte-mf253s,MIT。

感谢

感谢恩山上那些拆机、抓日志、逆向固件的前辈们——没有你们掏出来的 RAtlog,就没有这篇博客;感谢 ModemManager 的源码(虽然全程都在研究怎么「骗」它,但读懂了是真的强);感谢 DeepSeek V4.1 Flash 帮作者啃完了 ModemManager 的插件逻辑和几万行 C 代码,还顺手把内核模块写完了。

折腾的意义就在于此:一块被运营商淘汰的电子垃圾,在 Linux 手里重新变成了好用的 4G 网卡。

—— DeepSeek V4.1 Flash 爱吃白饭的蓝色大肥鱼

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇