Cell phone hotspot to OpenWRT One as backup to main ISP {REDUX}

In another post I created a hotspot monitor. I used the OpenWRT One’s built-in radio0 to wait for a signal from a specific SSID, then it would spin up a secondary Client Mode wireless network to connect to it. That way, if our main ISP went down, all I had to do was turn on my cellphone hotspot, and the entire lan had internet again.

It worked well enough, but I wanted something that didn’t require tearing down and restarting radio0 each time the hotspot connected or disconnected (which had to be done due to glitches). I also wanted the ability to pick up 5 GHz hotspots (which can’t be done with the in-built radio1, there’s not enough bandwidth to serve both the usual 5 GHz WiFi and a 5 GHz hotspot… that’s just a limitation of 5 GHz due to its default 80 MHz bandwidth).

I also wanted some way of connecting to any number of available hotspots, not just one. With the new setup, I can configure any number of Client Mode wireless networks, each for a certain hotspot. So even if I’m not home, anyone can start their own cellphone’s hotspot, and the lan will have internet access.

I bought an EDUP WiFi 6E AX3000M Wireless USB Adapter with the MediaTek MT7921AU chip.

I installed the necessary MediaTek MT7921 drivers and the USB utilities:

Name Version Size Description
kmod-mt7921-common 6.12.94.2026.03.19~39c960c3-r2 29.22 KiB MediaTek MT7921 wireless driver common code
kmod-mt7921-firmware 6.12.94.2026.03.19~39c960c3-r2 501.63 KiB MediaTek MT7921 firmware
kmod-mt7921u 6.12.94.2026.03.19~39c960c3-r2 3.96 KiB MediaTek MT7921U wireless driver
kmod-mt792x-common 6.12.94.2026.03.19~39c960c3-r2 15.61 KiB MediaTek MT792x wireless driver common code
kmod-mt792x-usb 6.12.94.2026.03.19~39c960c3-r2 3.70 KiB MediaTek MT792x wireless driver USB code
kmod-usb3 6.12.94-r1 10.20 KiB Kernel support for USB3 (XHCI) controllers
usbutils 019-r1 54.64 KiB USB devices listing utilities

I also bought a USB hub (the Rosonway RSH-A104, which purportedly can have power to its USB ports programmatically controlled, which I’ll use to turn on and off a cooling fan for the router).

I plugged the USB hub into the router, plugged the power supply into the USB hub, plugged the external USB hard drive into the USB hub, plugged the cooling fan into the USB hub, and plugged the AX3000M WiFi adapter into the USB hub.

As soon as I fired it all up, the router recognized the external WiFi adapter as radio2. Then it was a simple matter of setting it up as a Client Mode wireless network with the associated interfaces.

Here are my settings. Yours may vary, depending upon your setup.

Network >> Interface >> Devices >> phy2-sta0 >> Configure >> General Device Options:

Device Type: Network device
Existing device: phy2-sta0
MTU: 1500
MAC ADDRESS: blank
TX queue length: blank
Enable IPv6: Automatic 
IPv6 MTU: blank
DAD transmits: blank

Network >> Interface >> Devices >> phy2-sta0 >> Configure >> Advanced Device Options:

Enable promiscuous mode: Automatic
Reverse path filter: disabled
Accept local: Automatic
Send ICMP redirects: Automatic
Honor gratuitous ARP: Automatic
Drop gratuitous ARP: Automatic
Neighbor validity cache: blank
Stale neighbor cache timeout: blank
Minimum ARP validity time: blank
Enable IPv6 segment routing: Automatic
Drop unsolicited NA: Automatic
Enable multicast support: Automatic
Force IGMP version: No enforcement
Force MLD version: No enforcement

Network >> Interfaces >> wwan >> edit >> General Settings:

Protocol: DHCP client
Device: phy2-sta0
Disable this interface: unchecked
Bring up on boot: checked
Hostname to send when requesting DHCP: Do not send a hostname

Network >> Interfaces >> wwan >> edit >> Advanced Settings:

Force link: unchecked
Use broadcast flag: unchecked
Preferred client ID: Automatic
Client ID to send when requesting DHCP: blank
Vendor class to send when requesting DHCP: blank
Use default gateway: checked
Use DNS servers advertised by peer: unchecked
Use custom DNS servers: 127.0.0.1
NOTE: The above setting is for if you are using https-dns-proxy and my custom Port 53-blocking technique.
https://forum.banana-pi.org/t/https-dns-proxy-doh-port-53-completely-locked-down-no-router-dead-locks-on-reboot/27924/1
DNS weight: blank
Use gateway metric: 50
Multi-path TCP: Off
Override IPv4 routing table: blank
Override IPv6 routing table: blank
Delegate IPv6 prefixes: checked
IPv6 assignment length: disabled
IPv6 prefix filter: blank
IPv6 suffix: blank
IPv6 preference: 50

Network >> Interfaces >> wwan >> edit >> Firewall Settings:

Create / assign firewall zone: wan | wan6 | wwan | wwan6

Network >> Interfaces >> wwan >> edit >> DHCP server >> General setup:

Ignore interface: checked

Network >> Interfaces >> wwan >> edit >> DHCP server >> IPv6 settings:

Designated master: unchecked
RA-Service: disabled
DHCP6-Service: disabled
NDP-Proxy: disabled

Network >> Interfaces >> wwan6 >> edit >> General Settings:

Protocol: DHCPv6 client
Device: phy2-sta0
Disable this interface: unchecked
Bring up on boot: checked
Request IPv6-address: try
Request IPv6-prefix: Automatic
Extend prefix: unchecked
Do not send a Release when restarting: checked

Network >> Interfaces >> wwan6 >> edit >> Advanced Settings:

Force link: unchecked
Preferred client ID: Automatic
Client IDs to send when requesting DHCP: blank
Use default gateway: checked
Use DNS servers advertised by peer: unchecked
Use custom DNS servers: ::1
NOTE: The above setting is for if you are using https-dns-proxy and my custom Port 53-blocking technique.
https://forum.banana-pi.org/t/https-dns-proxy-doh-port-53-completely-locked-down-no-router-dead-locks-on-reboot/27924/1
DNS weight: blank
Use gateway metric: blank
Multi-Path TCP: off
Override IPv4 routing table: blank
Override IPv6 routing table: blank
IPv6 source routing: checked
Delegate IPv6 prefixes: checked
IPv6 assignment length: disabled
IPv6 prefix filter: blank
IPv6 suffix: blank
IPv6 preference: 50

Network >> Interfaces >> wwan6 >> edit >> Firewall Settings:

Create / Assign firewall-zone: wan | wan6 | wwan | wwan6

Network >> Interfaces >> wwan6 >> edit >> DHCP server:

No DHCP server configured for this interface

Network >> Wireless >> radio2 >> Device Configuration >> General Setup:

Operating frequency: AX
Band: 2.4 GHz
Channel: auto
Width: 20 MHz
Allow legacy 802.11b rates: unchecked
Maximum transmit power: Driver default
County code: { ENTER YOUR COUNTRY HERE }

Network >> Wireless >> radio2 >> Device Configuration >> Advanced Settings:

Coverage cell density: Disabled
Distance optimization: blank
Fragmentation threshold: blank
RTS/CTS Threshold: blank
Force 40MHz mode: unchecked
Beacon interval: 100
Rx LDCP: checked
Tx LDCP checked

Network >> Wireless >> radio2 >> Interface Configuration >> General Setup:

Mode: Client
ESSID: { PUT THE SSID OF YOUR HOTSPOT HERE }
BSSID: blank
Network: wwan, wwan6

Network >> Wireless >> radio2 >> Interface Configuration >> Wireless Security:

Encryption: WPA2-PSK/WPA3-SAE mixed mode
Key: { PUT THE PASSPHRASE OF YOUR HOTSPOT HERE }
802.11w Management Frame Protection: Optional
802.11w retry timeout: blank
Operating Channel: Disabled

Network >> Wireless >> radio2 >> Interface Configuration >> Advanced Settings:

Multi to Unicast: unchecked
Isolate Bridge Port: unched
Interface name: phy2-sta0
MAC address: Driver default
Short preamble: checked
DTIM interval: blank
Time interval for relaying GTK: blank
Disable inactvity Polling: unchecked
Station inactivity Limit: blank
Maximum allowed listen interval: blank
Dissociate on Low Acknowledgement: checked

Now, if the main ISP goes down or its routing goes wonky, I start my cellphone hotspot, the AX3000M connects and the lan still has internet connectivity.

I’m using metrics to direct packet flow… the main ISP has a metric of 10, the Client Mode wireless network has a metric of 100. So if both the main ISP and the hotspot are connected, data preferentially flows to the main ISP.

If the main ISP drops, the router automatically shuts down those interfaces (wan, wan6), and data flows through the hotspot via the wwan and wwan6 interfaces.

But there is one circumstance in which the main ISP could appear to be up, but it’s really not. The data still won’t flow to the hotspot, because the router thinks the main ISP is still up. If the main ISP’s routing goes hinky or a cable outside is cut or unplugged, that can (and has) happened.

So I created a script which pings a server on an interval (otherwise known as a Canary Ping) via the eth0 device (the 2.5 Gb/s wired port, connected to the modem for the main ISP). If that ping fails and a hotspot is available, the script routes data to the hotspot. Should the ping succeed again (the main ISP comes back up), it routes to the main ISP.

First, install the iputils-arping package.

This is pasted in System >> Startup >> Local Startup (ie: /etc/rc.local), above the ‘exit 0’ line.

If anything goes wrong and you’re locked out of the ssh terminal and the LuCI interface, connect to the router via the USB-C interface, log into the router in a terminal window via the ‘screen’ application (for Linux): sudo screen /dev/ttyACM0 115200), then issue: vi /etc/rc.local and press i to enter editing mode, then delete the Hotspot Monitor script. You’ll likely have to go to the bottom and backspace to the top of the script to erase it. Then press Esc to exit editing mode, press :wq to save the file and exit, type exit at the terminal window to exit the USB-C connection to the router, then reboot the router by pulling power, waiting 15 seconds, then reapplying power. I had to do this a few times in perfecting this script.

# HOTSPOT MONITOR FOR EXTERNAL (USB) RADIO
# NOTE: This script requires the iputils-arping package.
# NOTE: Place this script in /etc/rc.local (LuCI Interface: System >> Startup >> Local Startup)
# NOTE: Find and kill any prior instances:
# NOTE: ps | grep rc.local then kill the process with '/etc/rc.local' (the process with 'grep rc.local' is the search you just did).
# NOTE: Then run it: sh /etc/rc.local
(
    logger -t hotspot_monitor "[HOTSPOT MONITOR]: Initialized"
    sleep 30 # Allow external USB WiFi adapter and drivers to cleanly start

    PRIMARY_DEV="eth0"			# SET THIS TO THE PORT YOU HAVE YOUR MODEM PLUGGED INTO.
    LOGGING=1				# 1: ENABLE; 0: DISABLE

    MIN_INTERVAL=15			# ADAPTIVE TIMEOUT MINIMUM
    MAX_INTERVAL=300			# ADAPTIVE TIMEOUT MAXIMUM
    INTERVAL_STEP=1			# ADAPTIVE TIMEOUT STEP SIZE

    # DO NOT CHANGE ANYTHING BELOW THIS LINE!
    CURRENT_INTERVAL=$MIN_INTERVAL
    PRIMARY_STATUS="UP"
    SAVED_ISP_GW=""

    while true; do

        # 1. GET AND STORE THE ISP GATEWAY IP ADDRESS
        IFACE_GW=$(ip route show dev "$PRIMARY_DEV" table main | awk '/default via/ {print $3; exit}')
        if [ -n "$IFACE_GW" ]; then
            SAVED_ISP_GW="$IFACE_GW"
        fi

        # 2. Briefly bring up WAN to satisfy arping requirements
        if [ "$PRIMARY_STATUS" = "DOWN" ]; then
            ubus call network.interface.wan up 2>/dev/null
            sleep 10
        fi

        # 3. ASSIGN PROBE TARGET (Prioritize the ISP gateway)
        if [ -n "$SAVED_ISP_GW" ]; then
            TARGET_MODEM="$SAVED_ISP_GW"
        else
            # Fallback if cold-booting with no wire attached
            TARGET_MODEM="192.168.100.1" 
        fi

        # 4. EXECUTE LAYER-2 ARP PROBE
        arping -I "$PRIMARY_DEV" -c 1 -w 3 "$TARGET_MODEM" >/dev/null 2>&1
        ARP_STATUS=$?

        if [ "$LOGGING" -eq 1 ]; then
            [ "$ARP_STATUS" -eq 0 ] && HEARTBEAT="SUCCESS" || HEARTBEAT="FAILED"
            logger -t hotspot_monitor "[HOTSPOT MONITOR] Mode: $PRIMARY_STATUS | Probe: $HEARTBEAT | Target: $TARGET_MODEM"
        fi

        # 5. STATE EVALUATION BLOCK
        if [ "$ARP_STATUS" -ne 0 ]; then
            CURRENT_INTERVAL=$MIN_INTERVAL # Frequent check interval on link failure

            if [ "$PRIMARY_STATUS" = "UP" ]; then
                logger -t hotspot_monitor "[HOTSPOT MONITOR] PRIMARY down. Routing to BACKUP."
                ubus call network.interface.wan down 2>/dev/null
                ubus call network.interface.wan6 down 2>/dev/null
                conntrack -F 2>/dev/null
                PRIMARY_STATUS="DOWN"
            else
                # Allow ubus wan to time out so we don't spam the network interface layer.
                ubus call network.interface.wan down 2>/dev/null
            fi
        else
            if [ "$PRIMARY_STATUS" = "DOWN" ]; then
                logger -t hotspot_monitor "[HOTSPOT MONITOR] PRIMARY up. Routing to PRIMARY."

                # Bring up wan and wan6
                ubus call network.interface.wan up 2>/dev/null
                ubus call network.interface.wan6 up 2>/dev/null

                conntrack -F 2>/dev/null
                PRIMARY_STATUS="UP"
                CURRENT_INTERVAL=$MIN_INTERVAL
            else
                # Connection is long-term stable, step back check interval
                CURRENT_INTERVAL=$((CURRENT_INTERVAL + INTERVAL_STEP))
                [ "$CURRENT_INTERVAL" -gt "$MAX_INTERVAL" ] && CURRENT_INTERVAL=$MAX_INTERVAL
            fi
        fi

        sleep "$CURRENT_INTERVAL"
    done
) &