discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
ddimension/wwand · source
Snapshot 0 · 2026-11-04T12:47:28Z · ucode · GPL-2.0
Content fingerprint
a0437cb1d4b9ea3b16ba257cf43471b22d96cd5387c5b465cbdbfbbcdbf3486eGitHub description at capture
reliable mobile network manager
README at capture
# wwand
**A lean, event-driven WWAN connection manager for OpenWrt.**
Native QMI / MBIM / NCM. ~3 MB. No uqmi, qmicli, libqmi, glib or ModemManager.
```
┌─────────────────────────────────────────────────────────┐
│ wwand │
│ ┌─────┐ ┌──────┐ ┌─────┐ one daemon, one uloop │
│ │ QMI │ │ MBIM │ │ NCM │ indication-driven │
│ └──┬──┘ └──┬───┘ └──┬──┘ ~3 MB RSS, 0 spawns │
└──────┼─────────┼──────────┼────────────────────┬─────────┘
│ native │ native+ │ AT │ ubus
│ qmux │ passthru │ ▼
/dev/cdc-wdm cdc_mbim ttyUSB netifd (proto wwand)
────────────────────────────────► wwand0 / wwandN ──► WAN
modem ◄── SIM · eSIM/eUICC · PIN · cell-lock · telemetry
```
wwand talks to cellular modems natively — a compact ucode daemon that decodes
QMI/MBIM on the wire, drives **netifd** directly (it owns the context lifecycle;
no per-interface monitor process), and manages the **SIM and eSIM** end to end.
It replaces the grown bash QMI dialer: its field-proven behaviours,
quirks and recovery strategies were ported deliberately, its bugs left behind.
---
## Screenshots
A quick tour of the LuCI UI (modems overview · modem config · interface · connection · SIM/APN/PIN · Modem Tools with eSIM · live status with signal graphs) — full-size, captioned gallery in **[docs/luci.md](docs/luci.md)**:
[](docs/luci.md)
The plugins bring their own pages — remote SIM ([wwand-rsim](https://github.com/ddimension/wwand-rsim), below) and eSIM fleet management through an eIM ([wwand-ipa](https://github.com/ddimension/wwand-ipa), Network → eSIM Fleet):
[](https://github.com/ddimension/wwand-rsim)
---
## Why wwand
- **Tiny & quiet** — ~3 MB RSS, **zero** processes spawned in normal operation,
one uloop. State changes arrive as modem indications rather than polls; what
does poll is bounded and purposeful (telemetry while a UI is watching, the
zero-rx watchdog). Compare ModemManager + libqmi + glib at 15–30 MB.
- **Three backends, one contract** — QMI, MBIM and NCM sit behind a single
daemon-neutral interface, so netifd, the ubus API and the UI never care which
a modem speaks. MBIM even tunnels the whole QMI stack over an
[MBIM passthrough](docs/architecture.md#5-control-backends-qmi-mbim-ncm),
and the same stack runs over the Qualcomm IPC router (QRTR) for PCIe modems
with no cdc-wdm.
- **Multi-modem, multi-context** — several modems, and several parallel PDP
contexts per modem via QMAP multiplexing (rmnet / qmimux on QMI, 802.1q
sessions on MBIM, auto-selected; channels auto-assigned once a modem is
muxed). A vendor datapath can be added as an add-on package without patching
wwand — see [docs/datapath-interface.md](docs/datapath-interface.md).
- **VRF-safe by construction** — the daemon touches only the link layer; all
addressing/routing goes through netifd, so `ip4table`/`ip6table`/VRF just work.
- **Robust** — a persisted recovery ladder (retry → op-mode cycle → modem reset
→ board power-cycle / reset-GPIO → reboot), a zero-rx watchdog, non-destructive
restart (the WAN survives a daemon restart; the daemon adopts the live session).
- **Diagnostic** — EMM reject cause + limited-service flag (QMI + `AT+CEER`),
live cell environment (serving + neighbours, LTE & NR5G), signal, operator,
data-system mode (LTE/NSA/SA), all on ubus.
- **Watchable** — the LuCI status page graphs the signal live, one series per
radio technology, so a modem falling from 5G to LTE or a hand on the antenna
is something you see rather than infer. The history is kept in the browser;
nothing is sampled or stored on the router while nobody is looking.
## Features at a glance
| Area | What |
|---|---|
| **Connectivity** | QMI / MBIM / NCM behind one `proto wwand` · QMI also over the **QRTR bus** for PCIe/MHI modems with no cdc-wdm (`option device 'qrtr'`) · IPv4/IPv6/dual-stack · IPv4 /32 p-t-p or pushed prefix · IPv6 RFC-7278 PD · QMAP mux (multiple contexts/modem) with full **bidirectional aggregation** — downlink (modem→host) *and* uplink (host→modem, WDA-negotiated + rmnet egress coalesce), capability-gated so a non-QMAP modem falls back to plain framing |
| **Attach** | Attach profile programmed from config **before** registration → correct APN/IP family, avoids the EMM-33 IPv4-only reject |
| **SIM** | PIN unlock (UIM → DMS fallback, retry-guarded) · multi-slot switching · PIN enable/disable · per-SIM overrides by ICCID (`wwand_sim`) · **SIM inventory**: every card seen, by ICCID, and where it is — modem and slot, eUICC and profile, or a remote reader (`wwandctl sims`, LuCI Status → SIM cards) |
| **Remote SIM** | With the [wwand-rsim](https://github.com/ddimension/wwand-rsim) plugin a modem runs on a card that is not in its slot (QMI UIM Remote, also over the MBIM passthrough; HW-verified clients are **Quectel**, whose firmware switch for the service `wwandctl rsim enable` sets — on other Qualcomm modems UIM Remote has to be on in the firmware already, untested, tbd, see its [modem support](https://github.com/ddimension/wwand-rsim/blob/main/docs/modems.md)): a reader on the router or on another machine over SSH (Smartmouse USB, Phoenix, PC/SC), a phone's SIM over Bluetooth SAP, the card of another modem on the router (a *SIM sponsor*, whose radio wwand keeps off meanwhile) or of a modem on another wwand router. `wwandctl rsim scan` finds the sources, LuCI Network → Remote SIM sets them up |
| **eSIM/eUICC** | Native ES10c list/enable/disable/delete · **SM-DP+ download** via bundled lpac · APDU transport auto-chosen: **native MBIM MS UICC Low Level Access** → QMI UIM (native or over the passthrough) → AT — so eSIM works on MBIM modems without an AT port |
| **eSIM fleet** | With the [wwand-ipa](https://github.com/ddimension/wwand-ipa) plugin the router is the GSMA **SGP.32 IoT Profile Assistant** for an eIM, run by [ipad](https://github.com/ddimension/ipad) (`wwand-ipad`, C, SGP.32 v1.3): the eIM queues profile downloads (direct through lpac, or indirect through the eIM), enable/disable/delete and eIM configuration changes; wwand applies a profile switch to the modem, waits for a new data session and rolls the change back when none comes (SGP.32 3.3.2), and writes the enabled profile's APN into a `wwand_sim` for its ICCID. Works with IoT eUICCs and — by emulating the SGP.32 functions, results signed with a device key — with ordinary SGP.22 consumer eUICCs. `wwandctl ipa`, LuCI Network → eSIM Fleet |
| **Binding** | Pin a modem by USB **serial**, **IMEI**, or a stable **device path** (sysfs topology, PCIe/MHI-ready) so the right SIM/APN follows the right modem across re-enumeration; **stable L3 names** `wwand0…wwand100` (auto-assigned, kernel netdev renamed, written back) survive USB renumbering on multi-modem boxes |
| **RF unlock** | `option fcc_auth` unlocks laptop-SKU modems that boot radio-locked (Lenovo/Dell/HP Quectel EM1xx, Foxconn SDX55/SDX62, DW5821e) — QMI DMS/Foxconn auto-chain, MBIM Quectel radio-state |
| **Setup** | **Zero-config autosetup** (default on): a modem on an unconfigured box creates `wwmodem_auto` + interface `wwan0` (L3 device `wwand0`) in the default wan firewall zone, then, in that same run only, a one-shot internal **ICCID/IMSI → APN table** copies the carrier defaults (APN, PDP type, auth, credentials) into the config. wwand writes the configured APN into the modem's attach profile — an unset one as empty (network default), never a stale APN from the modem's previous life |
| **Radio** | Mode/band restriction · manual PLMN · network scan & selection · Quectel cell-lock (4G anchor / 5G SA) · QMI LOC positioning · **idempotent, radio-safe sets** (skipped when the modem already runs the value; `unchanged`/`deferred` results) with a LuCI-offered modem reset for deferred-apply firmwares |
| **SMS** | Receive/list · read · delete stored messages (SIM or modem store) with a full GSM 03.40 PDU decoder (7-bit incl. umlauts, UCS2, alphanumeric sender, multipart merge) · transport auto-chosen QMI WMS (native / passthrough) → native MBIM SMS → AT · LuCI inbox on the Modem Tools page |
| **Board** | Auto-detected board profiles (MikroTik Chateau 5G, Zyxel LTE3301-PLUS / -M209 / -Q222, LTE5398-M904, NR7101, Cudy LT300) drive modem **power/reset GPIOs** and **status LEDs** (5-bar signal graph or mobile/LTE) — absorbing the vendor helper scripts. Manual `modem_reset` / `modem_repower` (LuCI button); GPIO picker in the UI |
| **Ops** | Recovery ladder (opmode → modem reset → **board power-cycle / reset-GPIO** → reboot) + zero-rx watchdog · non-destructive restart + session adoption · **"waiting for modem"** surfaced to netifd/LuCI + logged · uniform rich telemetry line across all backends · the network's time (NITZ) sets the system clock when it is more than two minutes off — for routers without an RTC (`option nitz_time`, default on) · per-model quirk tables · AT side channel · **`at2_external`** reserves the secondary AT port for external tools (gpsd, scripts) |
## Packages
The daemon is a backend-neutral base plus per-backend packages — install only
what your modems need. Package definitions live in the
[openwrt-repo](https://github.com/ddimension/openwrt-repo) feed, which also
publishes **prebuilt, signed binary repositories** (snapshot and 25.12 across
seven architectures) in two channels,
`https://ddimension.github.io/openwrt-repo/<channel>/<release>/<arch>/`:
`stable` carries the tagged releases (wwand vX.Y.Z as package version X.Y.Z),
`main` the development state in between (X.Y.Z_pN, N commits after the tag).
See the [feed README](https://github.com/ddimension/openwrt-repo#binary-package-repositories)
for device setup (the `ddimension-feed` package, signing keys, switching
channels).
| Package | Role |
|---|---|
| `wwand` | daemon + framework + codec + shared core + the native `wwand_io.so` I/O module (no backend on its own) |
| `wwand-qmi` | QMI backend — the common case; over cdc-wdm, or over the QRTR bus for PCIe/MHI modems that have none (`DEPENDS wwand`) |
| `wwand-mbim` | MBIM backend (`DEPENDS wwand-qmi` — the passthrough reuses QMI) |
| `wwand-ncm` | NCM/ECM backend (`DEPENDS wwand`) |
| `wwand-mhi` | PCIe/MHI transport + MHI drivers (`DEPENDS wwand`; backend-neutral, add wwand-qmi or wwand-mbim) |
| `wwand-esim` | eSIM management + SM-DP+ download (`DEPENDS wwand + lpac` — no backend: the APDU transport chain MBIM UICC → QMI UIM → AT lives in the base `sim.uc`, so eSIM works on an NCM- or MBIM-only box too) |
| `wwand-gps` | reads the modem's NMEA port wwand found during enumeration and reports the fix through `modem_gps` (`DEPENDS wwand`) |
| `wwand-apntest` | runs configured APN tests in sequence on a dedicated test box — SIM or eUICC profile, dial, checks, one verdict per test, reported to Nagios/Icinga through NSCA-ng (`send_nsca`, package `nsca-ng` in the ddimension feed) (`DEPENDS wwand`) |
| `wwand-datapath-rmnet_nss` | optional datapath add-on: adopts the vendor `qmi_wwan_q` QMAP children (USB) so they keep their Qualcomm NSS offload (`DEPENDS wwand-qmi`) |
| `wwand-datapath-rmnet_nss_mhi` | the same for Quectel's vendor `pcie_mhi` driver (PCIe/MHI, QMI *and* MBIM) (`DEPENDS wwand`) |
The ucode tree can ship **precompiled to bytecode** (faster start, no on-device
parse) or as readable source. Which is the default depends on the packaging: the
ddimension feed precompiles unless `CONFIG_WWAND_UCODE_SOURCE` is set, while the
openwrt/packages submission ships source and precompiles only under
`CONFIG_WWAND_UCODE_PRECOMPILE`. Bytecode also needs a host ucode that can emit
relocatable modules — where it cannot, the build falls back to source on its own
rather than failing.
A typical QMI router installs **`wwand-qmi`** (which pulls in `wwand`). The LuCI
UI is [luci-proto-wwand](https://github.com/ddimension/luci-proto-wwand) +
[luci-app-wwand](https://github.com/ddimension/luci-app-wwand).
### Plugins
Features that are not meant for upstream live in their own repositories and
hook into the daemon through a neutral plugin interface (`plugins.uc`, see
[docs/reference.md](docs/reference.md#plugins)); the feed builds them too.
| Package | Role |
|---|---|
| [`wwand-rsim`](https://github.com/ddimension/wwand-rsim) | remote SIM: a modem runs on a card in a reader, a phone (Bluetooth SAP), another modem here or on another wwand router; `wwandctl rsim` (`DEPENDS wwand-qmi rsim-card`) + `luci-app-wwand-rsim` |
| `rsim-card` / `rsim-card-pcsc` | the card-side helper alone, for a **SIM host** — a PC or a second router holding the reader, reached over SSH (restricted to `rsim-card --serve`); `-pcsc` adds PC/SC readers through pcscd. Needs nothing of wwand |
| [`wwand-ipa`](https://github.com/ddimension/wwand-ipa) + `wwand-ipad` | SGP.32 IoT Profile Assistant: eSIM fleet management through an eIM (`DEPENDS wwand-esim`) + `luci-app-wwand-ipa` |
| [`wwand-qlog`](https://github.com/ddimension/wwand-qlog) | `wwandctl qlog`: Quectel QLog diagnostic capture on the port wwand found |
## Quick start
All configuration lives in `/etc/config/network` (WireGuard-style):
```
config wwand_modem 'm0'
option device 'wwan0' # netdev name or /dev/cdc-wdm0
# option path '1-1.2' # optional: pin to a fixed USB topology path
# option reset_gpio 'modem-reset' # optional: GPIO to reset the modem on recovery
option modes 'lte,nr5g'
option pincode '1234' # if the SIM needs one
config interface 'wan'
option proto 'wwand' # one proto for QMI/MBIM/NCM
option modem 'm0'
option apn 'internet'
option pdp_type 'ipv4v6'
```
`ifup wan` — done. wwand installs alongside the stock uqmi/umbim/comgt-ncm
packages and leaves their `proto qmi`/`mbim`/`ncm` interfaces alone; migrate one
to `proto wwand` on demand from the LuCI modem list, with
`/usr/libexec/wwand/migrate --apply`, or unattended at the next boot via the
example uci-defaults script in `/usr/share/wwand/examples/`. See
[docs/reference.md](docs/reference.md) for every option.
## Documentation
| Doc | For |
|---|---|
| [docs/luci.md](docs/luci.md) | Visual tour of the LuCI web UI (screenshots) |
| [docs/reference.md](docs/reference.md) | Config options, ubus API, eSIM, quirks, troubleshooting, FAQ |
| [docs/vrf.md](docs/vrf.md) | VRF & DMZ deep-dive (L3 isolation, NAT66, field notes) |
| [docs/connection-flow.md](docs/connection-flow.md) | How a connection comes up — wwand, modem and network side, phase by phase |
| [docs/architecture.md](docs/architecture.md) | How it works: layering, backends, netifd coupling, VRF, recovery |
| [docs/extending.md](docs/extending.md) | Add a modem/quirk, a config option, a backend, telemetry, a ubus method |
| [docs/backend-interface.md](docs/backend-interface.md) | The daemon-neutral control-backend contract |
| [docs/datapath-interface.md](docs/datapath-interface.md) | The datapath (mux) contract and how one is chosen |
The full map, including the design notes and the running log, is
**[docs/README.md](docs/README.md)**.
## Layout
| Path | Description |
|---|---|
| `src-ucode/` | The daemon (ucode): codec, session, backends, integration |
| `io/` | Native C module — cdc-wdm/tty I/O, rmnet netlink helper, non-blocking `spawn()` |
| `files/` | netifd proto shim, init, hotplug, migration helper + uci-defaults |
| `tests/` | Host-side suites — `sh tests/run_tests.sh`, no hardware needed |
| `docs/` | Reference, architecture, extending guide |
## Status
Production-tested across MikroTik Chateau 5G (Quectel RG650E-EU + Huawei E392),
Zyxel NR7101, Zyxel LTE3301-M209 (Quectel BG96, LTE Cat-M), Cudy LT300 (MeiG
SLM770A) and GL.iNet X3000 (RM520N), plus further Quectel modems (RG502Q, EG06).
PCIe/MHI runs on a Dell DW5932e (Foxconn T99W373, Snapdragon X62) in an x86_64
HP EliteBook 850 G7: `mhi-pci-generic` + `mhi_wwan_mbim` from `wwand-mhi`, MBIM
with the QMI passthrough, 5G NSA (ENDC), and the remote SIM of another router.
Native MBIM without a QMI passthrough runs on a Dell DW5931e (Fibocom FM350-GL,
MediaTek T7xx, kernel `mtk_t7xx`) in the same notebook: the GTFCCLOCK FCC unlock
at every bring-up, 5G SA, and its eUICC over MBIM.
QMI over QRTR was contributed with a hardware test on a Quectel RG520N-EB
(IPQ5018, PCIe/MHI); the hub as it is now is host-tested only so far. Remote SIM (wwand-rsim) ran on the RG650E (QMI)
and the RM520N (MBIM) with cards from a Smartmouse reader, phones over
Bluetooth, sponsors on the same router and modems on other wwand routers — the
details and workarounds are in its
[README](https://github.com/ddimension/wwand-rsim#what-works). A hardware-free
host suite runs on every change — current suite/check counts live in
[docs/STATUS.md](docs/STATUS.md).
## Credits
wwand is shaped by the people who report, test and contribute — see
[CREDITS.md](CREDITS.md). Thank you.
## License
GPL-2.0-only. The AT port table is generated from ModemManager's udev rules
(GPL-2.0-or-later); see the file header for attribution.