Includes: - package/ GL-XE3000 kit-busrouter (opkg) - scripts/provision.sh (GL) and provision-synology.sh (Synology) - syno-balance/ — new WAN balancer replacing aiwanbal (SmartWAN adapter) - kit-connect/ — unified connectivity SPK (Tailscale + reverse SSH) - docs/deployment/synology-rt2600ac-checklist.md — 62-point checklist - docs/provisioning/device-identity.md — fleet identity spec - docs/pilot/checklist.md — field pilot validation - x4078_20260721.dss — reference config backup Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
18 KiB
GL-XE3000 Device Reconnaissance (Task 0.2)
Date: 2026-06-30
Unit: bench bus01 — GL.iNet GL-XE3000 (Puli AX)
Access path: LAB → kit-fleet-hub (DO droplet 167.172.237.162, WG 10.88.0.1) → jump SSH
ssh -i /root/.ssh/busfleet root@10.88.0.2 (router WG IP 10.88.0.2).
Access confirmed: yes — ubus call system board returns "model": "GL.iNet GL-XE3000".
1. Resolved facts table (feeds later plan tasks)
| Fact | Value |
|---|---|
| Cellular ifname (WAN1 member) | rmnet_mhi0 (QMI proto qcm, control node /dev/mhi_QMI0, UCI iface modem_0001, APN fast.t-mobile.com) |
| Starlink WAN ifname (WAN2) | eth0 (UCI iface wan, proto dhcp). NOTE: currently DOWN/NO-CARRIER — no dish attached to bench unit. eth1 = LAN (br-lan). |
| mwan3 present? | NO — /etc/config/mwan3 absent, mwan3 package not installed → Task 3.0 / Phase 2 must opkg install mwan3. GL ships its own multiwan (gl-sdk4-ui-multiwan, kmod-gl-sdk4-kmwan, ubus hotplug.kmwan) which we are NOT using. |
| Signal read command | Preferred: ubus call modem.signal get_signals '{"time":1}' → JSON {rsrp,rsrq,sinr,slot,network_type,strength,mode}. AT fallback: gl_modem -B 1-1.2 AT AT+QCSQ → +QCSQ: "NR5G",<rssi>,<sinr>,<rsrp>. uqmi is NOT usable (see Implications). |
| SIM select command | Read: gl_modem -B 1-1.2 AT AT+QUIMSLOT? → +QUIMSLOT: 1. Switch: gl_modem -B 1-1.2 AT AT+QUIMSLOT=2 (confirmed slot-query works; switch command not exercised to avoid dropping the live link). |
| GPS read command | gl_modem -B 1-1.2 AT AT+QGPSLOC=2 — but GNSS must be enabled first: AT+QGPS=1. Currently AT+QGPS? = 0 (disabled), so QGPSLOC returns +CME ERROR: 505 (no fix). |
| grpcurl available? | NO — command -v grpcurl = MISSING → Phase 3 (Starlink) must bundle a static arm64 grpcurl binary in the opkg package. |
| Firewall zone of WG client iface | wgclient1 — its own dedicated zone named wgclient1 (network wgclient1, input DROP). NOT wan. The access-fix rule is firewall.@rule[22] name Allow-WG-mgmt-SSH, src='wgclient1', src_ip='10.88.0.0/24', dest_port 22, ACCEPT. |
Modem USB bus (for gl_modem -B) |
1-1.2 (Quectel RM520N-GL, 4 USB interfaces at 1-1.2:1.0-1.3) |
2. Raw command outputs
ubus call system board
{
"kernel": "5.4.211",
"hostname": "GL-XE3000",
"system": "ARMv8 Processor rev 4",
"model": "GL.iNet GL-XE3000",
"board_name": "glinet,xe3000-emmc",
"release": {
"distribution": "OpenWrt",
"version": "21.02-SNAPSHOT",
"target": "mediatek/mt7981"
}
}
/etc/config/network (relevant excerpts)
config device # LAN bridge
option name 'br-lan'
list ports 'eth1'
config interface 'lan'
option device 'br-lan'
option ipaddr '192.168.8.1'
config device
option name 'eth0'
config interface 'wan' # Starlink WAN port
option device 'eth0'
option proto 'dhcp'
option metric '10'
config interface 'modem_0001' # Cellular / 5G
option apn 'fast.t-mobile.com'
option ip_type 'IPV4V6'
option proto 'qcm'
option device '/dev/mhi_QMI0'
option metric '40'
option iccid '8901260417776090740F'
config interface 'wgclient1' # WireGuard out-of-band mgmt
option proto 'wgclient'
option config 'peer_2001'
option ip4table '1001'
(Also present but unused/disabled: wwan, secondwan, guest, various GL vpn policy rules.)
/etc/config/mwan3
NO mwan3
opkg list-installed (filtered: mwan3|grpc|speedtest|modem|gl-|uqmi|quectel)
Notable entries (full list captured in session):
gl-sdk4-modem git-2025.290.37125-7c3efcc-1
gl-sdk4-ui-multiwan git-2025.259.31350-8a2a5c6-1
gl-sdk4-ui-modemsignallog git-2024.303.14204-4241be7-1
kmod-gl-pcie-modem 5.4.211+20221009-1
kmod-gl-sdk4-kmwan 5.4.211+git-2025.304.07170-bb470c0-1
uqmi 2020-11-22-0a19b5b7-3
No mwan3, no grpcurl, no speedtest (Ookla) package installed.
ip -br addr
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 DOWN # Starlink WAN — no carrier
eth1 UP # LAN member
ra0 UP # 2.4G wifi
rmnet_mhi0 UP 100.112.80.45/30 # Cellular (CGNAT)
br-lan UP 192.168.8.1/24
wgclient1 UNKNOWN 10.88.0.2/32 # WG mgmt tunnel
ip route
default via 100.112.80.46 dev rmnet_mhi0 proto static src 100.112.80.45 metric 40
100.112.80.44/30 dev rmnet_mhi0 proto static scope link metric 40
192.168.8.0/24 dev br-lan proto kernel scope link src 192.168.8.1
Modem device nodes
/dev/mhi_BHI /dev/mhi_DIAG /dev/mhi_DUN /dev/mhi_LOOPBACK /dev/mhi_QMI0
/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3
No /dev/cdc-wdm* — modem is on the MHI/PCIe transport, not USB-CDC.
Tool availability
gl_modem: /usr/bin/gl_modem
uqmi: /sbin/uqmi
grpcurl: MISSING
mmcli: MISSING
gpsd: MISSING
gl_modem usage
gl_modem -B <bus> AT <AT_CMD> # send AT command; bus for this unit = 1-1.2
Signal — ubus (preferred)
// ubus call modem.signal get_signals '{"time":1}'
{
"signals": [{
"sinr": 13, "slot": 1, "network_type": "NR5G-SA",
"timestamp": 1782917019, "strength": 4,
"rsrp": -96, "rsrq": -10, "mode": 5
}]
}
ubus -v list modem.signal → methods get_signals {time:Integer}, upload_cloud_signals {action:String}.
Signal — AT fallback
gl_modem -B 1-1.2 AT AT+QCSQ
+QCSQ: "NR5G",-97,14,-10 # <sysmode>,<rssi>,<sinr>,<rsrp>
SIM slot
gl_modem -B 1-1.2 AT AT+QUIMSLOT?
+QUIMSLOT: 1 # active SIM = slot 1
GPS / GNSS
gl_modem -B 1-1.2 AT AT+QGPS?
+QGPS: 0 # GNSS engine currently OFF
gl_modem -B 1-1.2 AT AT+QGPSLOC=2
+CME ERROR: 505 # no fix (engine not enabled)
Starlink WAN port link
eth0 DOWN <NO-CARRIER,BROADCAST,MULTICAST,UP>
Firewall (WG mgmt zone + access-fix rule)
firewall.wgclient1 = zone
firewall.wgclient1.name='wgclient1'
firewall.wgclient1.network='wgclient1'
firewall.wgclient1.input='DROP'
firewall.@rule[22].name='Allow-WG-mgmt-SSH'
firewall.@rule[22].src='wgclient1'
firewall.@rule[22].src_ip='10.88.0.0/24'
Zone[1] wan groups: wan wan6 wwan secondwan modem_0001 modem_0001_4 (input DROP).
3. Implications for the plan
-
mwan3 is NOT installed (plan flagged this as possible).
/etc/config/mwan3is absent and nomwan3opkg is present. Phase 2 / Task 3.0 mustopkg install mwan3(verify it exists in GL's feed formediatek/mt7981@ 21.02-SNAPSHOT; if not, add the upstream OpenWrt 21.02 feed or bundle it). GL ships its ownkmwan/multiwan stack (kmod-gl-sdk4-kmwan,gl-sdk4-ui-multiwan, ubushotplug.kmwan) — the spec locks onmwan3, so we intentionally do NOT use GL's kmwan. Watch for conflict/overlap between the two multiwan systems on the same box (routing marks, hotplug). -
Firewall zone assumption in Task 0.1 was WRONG. Task 0.1 Step 1 guessed
src='wan'(with a "verify / adjust if wgclient" note). The WG client interface is actually in its own zonewgclient1, and that is what the working fix rule uses (@rule[22] src='wgclient1' src_ip='10.88.0.0/24'). Any future mgmt/telemetry firewall rules must target zonewgclient1, notwan. -
uqmiis effectively unusable for signal reads.uqmi -d /dev/mhi_QMI0 --get-signal-infohangs (timeout) — GL's modem manager holds the QMI control channel (single-client QMUX). Use the GL ubus pathmodem.signal get_signals(clean JSON, no device contention) as the primary signal source, withgl_modem -B 1-1.2 AT AT+QCSQas the AT fallback. Phase 4 Task 4.1 should target ubus first, not uqmi as the plan's example suggested. -
gl_modemrequires a-B <bus>argument (the plan's examplesgl_modem AT AT+...omit it and would fail). The bus on this unit is1-1.2. This must be discovered per-unit (parse from/sys/bus/usb/devicesor GL config) rather than hard-coded, since it could differ across units — record for the provisioning profile (Phase 8). -
GPS/GNSS is present but OFF by default.
AT+QGPS?= 0, soAT+QGPSLOC=2errors+CME ERROR: 505. Phase 5 Task 5.1 (gps_fix) must enable GNSS first (AT+QGPS=1, one-time / on boot) and handle the "no fix yet" (error 505/516) state gracefully before location is available. GPS command form is confirmed as QuectelAT+QGPSLOC=2(decimal degrees). -
grpcurlabsent (plan flagged this). Phase 3 Task 3.3 must bundle a static arm64 (ARMv8)grpcurlbinary in the opkg package for Starlink gRPC. -
No Starlink dish on the bench unit (
eth0NO-CARRIER). WAN2 (Starlink) integration and any two-WAN balancing test cannot be validated on this bench unit as-is — need a dish oneth0, or test WAN2 with a substitute uplink. The cellular WAN (rmnet_mhi0) is the only live uplink right now, and it carries the WG mgmt tunnel (CGNAT100.112.80.45/30, T-Mobile). -
Ookla speedtest CLI not installed — Phase 3 Task 3.2 must bundle/install it (or rely on the curl fallback), consistent with the plan's fallback design.
4. Task 2.0 — mwan3 / kmwan changeover (2026-07-01)
Performed on bench bus01 via the WG mgmt path. This section feeds the Phase 8 provisioning profile.
GL multiwan (kmwan) — exact init name + stand-down
- GL multiwan init script name:
kmwan—/etc/init.d/kmwan(the ONLYmwan|kmwaninit on the box; confirms recon §1kmod-gl-sdk4-kmwan). It is a/etc/rc.commonscript,START=21, sourcing/lib/functions/kmwan.sh. It was enabled but had no running daemon (routing is managed via hotplug scripts / a single static default route, not a persistent process). - Exact commands used to stand it down (Phase 8 must run these):
Verify with
/etc/init.d/kmwan disable # rc=0 → NOW_DISABLED (won't start on boot) /etc/init.d/kmwan stop # rc=0/etc/init.d/kmwan enabled(returns non-zero = disabled) andps w | grep -i mwan | grep -v grep(no kmwan process).
mwan3 install
- Installed from GL's own feed (no third-party/upstream feed needed):
opkg update && opkg install mwan3→mwan3 2.8.15-2(mwan3_2.8.15-2_all.ipk, feedglinet_gli_pub@ v21.02.3). Binary at/usr/sbin/mwan3. - mwan3 ships a stock default
/etc/config/mwan3(dated 2022) with interfaceswan/wwan/tethering(+v6). NONE of these match our live cellular uplink (rmnet_mhi0, UCI ifacemodem_0001), so mwan3 is currently inert:mwan3 statusshows every interface "offline and tracking is down", no active policies, no active user rules. mwan3'smwan3rtmonroute-monitor daemon runs but does not touch the default route (member config is a later task).
Safety / recovery notes for Phase 8
setsidandnohupare BOTH ABSENT on this GL firmware (BusyBox build). For detached/background processes usestart-stop-daemon -S -b -x <prog> -- <args>(present at/sbin/start-stop-daemon). Note:start-stop-daemon -b -x /bin/shwill refuse to launch if it matches an already-running/bin/sh("… is already running") — give the watchdog a unique interpreter/exec name or wrap it so the-xmatch is unique.- Post-change verification (mgmt access + default route SURVIVED): after
kmwan disable/stop, a fresh probe returned"model": "GL.iNet GL-XE3000", default route unchanged (default via 100.116.118.126 dev rmnet_mhi0 … metric 40— CGNAT src rotated from the recon-day100.112.80.45to100.116.118.125, normal T-Mobile lease change), andwgclient1 10.88.0.2/32up. The WG mgmt routing rules (from/to 10.88.0.2 lookup 1001, table 1001) are intact. Disabling kmwan did not disturb routing — expected, since kmwan had no active daemon and the live path is a single static default onrmnet_mhi0.
5. Task 2.1 pre-check — second uplink (ATT Fiber on eth0) added (2026-07-01)
Status: BLOCKED (device shell unreachable) — but the load-bearing WG-underlay question is answered from hub-side WireGuard evidence.
What happened
The operator plugged ATT Fiber into eth0 (WAN2 stand-in for Starlink). Immediately afterward the
router's SSH shell (via the WG mgmt tunnel 10.88.0.2:22) became unreachable from the fleet hub,
even though the WireGuard tunnel itself is healthy. All on-device commands (eth0 lease, ip route
metrics, ip route get, ip rule, mwan3 status) therefore could not be run. No device state was
mutated. The facts below are what the hub can observe about the tunnel from 10.88.0.1 (wg0).
THE KEY ANSWER — WG mgmt path now rides eth0 (ATT Fiber)
The WireGuard peer endpoint changed from the T-Mobile CGNAT 100.x.x.x seen in §2/§4 to a public,
non-CGNAT address, which is the ATT Fiber public IP:
# hub: wg show wg0 endpoints
0Xn7aGlyxHQ/X9uo8lsOeI3EFA7KO+zlO+qIhrzsAF8= 99.163.124.171:44881
# latest-handshakes: 6 s ago (fresh); transfer: 335 KiB rx / 244 KiB sent (growing)
99.163.124.171 is a routable public IP (not 100.64.0.0/10 CGNAT), i.e. the router is now sourcing its
WG handshakes out eth0 / ATT Fiber, not rmnet_mhi0 / cellular. The WG mgmt underlay rides eth0
(ATT Fiber). Corollary: downing eth0 would sever the WG mgmt path / SSH access.
Safe failover-test direction
- DO NOT bring down
eth0(ATT Fiber) for the failover drill — it carries the WG underlay; downing it cuts mgmt/SSH. rmnet_mhi0(cellular) is the SAFE interface to bring down for the mwan3 failover test — it does NOT currently carry the WG underlay handshake. (This is the inverse of the pre-fiber state in §2/§4, where cellular carried mgmt and eth0 was NO-CARRIER.)
Current mgmt-reachability fault (flag for operator — needs on-device follow-up)
The WG control plane is up but no in-tunnel application traffic returns from the router:
# hub, over the tunnel:
ip route get 10.88.0.2 -> 10.88.0.2 dev wg0 src 10.88.0.1 (route correct)
ping -c3 10.88.0.2 -> 100% loss (even 56-byte payloads)
nc -vz 10.88.0.2 22 -> timed out ("Operation now in progress")
# 4 s window: wg 'sent' climbed 244308 -> (SYN retransmits reach router), 'rx' FLAT
Interpretation: the router receives hub traffic (encrypted sent counter grows) but replies with
nothing — its in-tunnel return path for ICMP/TCP is broken. Most likely the policy-routing that steers
tunnel replies (table 1001, from/to 10.88.0.2 lookup 1001, per §4) or the WG interface's reverse-path
was disturbed when the fiber default route (metric 10) came up and displaced cellular (metric 40) as the
preferred default. This must be diagnosed/repaired on-device via a console/LAN path (the WG path is
the very thing that's down) before Task 2.1 routing changes proceed. Not attempted here (read-only,
no-mutate scope).
Facts NOT captured (require device shell — blocked)
- eth0/wan DHCP lease (ATT subnet / gateway / netmask), eth0 carrier flag from the device side.
- Full
ip routedefault table with metrics (expected eth0 metric 10 preferred over cellular metric 40 — strongly implied by the endpoint move to fiber, but not directly read). rmnet_mhi0current CGNAT IP,ip route get 167.172.237.162/ip route get 99.163.124.171.mwan3 status/mwan3 interfacesview of the two ifaces,ip ruletable listing.
Re-run §5 in full once console/LAN access to bus01 is restored and the WG mgmt path is repaired.
6. WG mgmt tunnel root cause + fix (2026-07-01)
Symptom: after ATT Fiber plugged into eth0 (metric 10 preferred default), WG handshakes were fresh but all in-tunnel data (ICMP/SSH from 10.88.0.1 to 10.88.0.2) returned 100% loss.
Root cause: mwan3 adds all directly-connected networks to the mwan3_connected ipset.
10.88.0.0/24 is a directly-connected route (wgclient1), so mwan3's iptables mwan3_connected
chain marks packets destined for that prefix with 0x3f00. The ip rule
9000: not from all fwmark 0/0xf000 lookup main then routes them via the main table,
which has no route for 10.88.0.0/24 (wgclient1 routes are only in table 1001 due to
ip4table='1001' in UCI). The packet is silently dropped — never reaches WG.
Fix (applied 2026-07-01): Add an ip rule at priority 99 (before any mwan3 fwmark rule) routing packets to 10.88.0.0/24 via table 1001:
ip rule add to 10.88.0.0/24 priority 99 lookup 1001
Persisted as a UCI network rule so netifd recreates it on every boot:
uci add network rule
uci set network.@rule[-1].lookup='1001'
uci set network.@rule[-1].dest='10.88.0.0/24'
uci set network.@rule[-1].priority='99'
uci commit network
Also applied for robustness: wgclient1 mtu=1280, rp_filter=2 persisted via /etc/sysctl.conf
and /etc/hotplug.d/iface/99-busrouter-rpfilter (re-asserts on WAN flap).
Failover note: scripts/diag/wg-mgmt-diagnose.sh and scripts/diag/wg-mgmt-fix.sh in
this repo implement the fix. The underlying keepalive (persistent_keepalive=25) was already
configured from a prior session; the new ip rule was the missing piece.
Phase 8 provisioning profile must include this UCI rule — without it, any device with mwan3 configured will have the WG mgmt tunnel silently drop in-tunnel data.
7. Task 2.1 bench-test findings (2026-07-01)
mwan3 configured with modem_0001 (cellular) + wan (fiber/eth0), policy balanced.
Both interfaces tracked online (1.1.1.1 + 8.8.8.8, reliability 1, down=3, up=3 x 5s interval).
Failover drill result:
ip link set rmnet_mhi0 down-> mwan3 declaresmodem_0001 errorwithin ~15s (checked)- Policy shifts to
wan (100%)automatically (checked) - WG mgmt tunnel (riding eth0) stays alive throughout failover (checked)
- Recovery gotcha:
ip link set rmnet_mhi0 updoes NOT restore the QMI data session. Must useifdown modem_0001 && ifup modem_0001to reconnect. After ~20s both interfaces return tobalanced: wan(50%) modem_0001(50%)(checked)
Phase 9 pilot: fiber failover (bring fiber down, WG must ride cellular) should be tested once the provisioning robustness fix (section 6 ip rule) is confirmed to survive both uplink combos.