{ EDIT: Updated Hotspot Monitor to fix boot-time operation, and to ensure operation under all circumstances. }
{ EDIT 2: Changed verbiage printed to the log }
{EDIT 3: Revised the procedure to make it a bit easier to set up}
I first tried Travelmate, but it caused CPU spikes. I then tried MultiWAN Manager, but it was slow, clunky and kept misconfiguring the connections.
So I scripted my own.
This script monitors for a certain SSID on radio0 (2.4 GHz). That SSID comes from a cell phone hotspot. If your main ISP goes down, you can feed data to/from your LAN through your cell phone’s hotspot.
The script monitors periodically (using the main 2.4GHz WiFi) for the TARGET_SSID. When the script sees the TARGET_SSID, it enables the wireless network which is set up to receive the cell phone hotspot WiFi signal; and when it no longer sees that TARGET_SSID, it disables that wireless network and its associated interface, and brings radio0 back up so the 2.4 GHz signal remains stable.
This is due to a bug that causes radio0 (2.4GHz) to go down and stay down after you stop the hotspot signal, usually requiring a manual restart of radio0. There’s also a bug wherein the wireless network set up to receive the hotspot signal loses the interface it’s assigned to when the hotspot WiFi is disabled… this script assigns that interface just before bringing up the wireless network set up to receive the hotspot signal.
DO NOT use radio1 (5GHz) for receiving a hotspot signal. It just doesn’t work. radio1 goes down when it tries, requiring a manual restart of radio1. Set your cellphone’s hotspot to only transmit on 2.4GHz.
This problem relates to bandwidth… the 20MHz wide 2.4GHz radio0 has sufficient bandwidth to both receive WiFi signals and transmit WiFi signals. The 80MHz wide 5 GHz radio1 does not.
The script below goes into your /etc/rc.local file, accessible in the LuCI interface at System >> Startup >> Local Startup. Put it below the two comment lines, but above the ‘exit 0’ line.
Network >> Interfaces >> Devices tab: Click 'Add device configuration' button General device options tab: Device type: Network device Existing device: type: phy0-sta0 MTU: 1500 MAC address: blank TX queue length: blank Enable IPv6: automatic (disabled) Advanced device options tab: Enable promiscuous mode: automatic (disabled) Reverse path filter: disabled Accept local: automatic (disabled) Send ICMP redirects: automatic (disabled) Honor gratuitous ARP: automatic (disabled) Drop gratuitous ARP: automatic (disabled) Neighbour cache validity: blank State neighbour cache timeout: blank Minimum ARP validity time: blank Enable multicast support: automatic (enabled) Force IGMP version: No enforcement Force MLD version: No enforcement Network >> Interfaces: Click the “Add new interface” button. Add new interface… dialog box: Name: {CHOOSE A NAME} I chose hotspot. Protocol: DHCP client Device: phy0-sta0 (you might have to directly type phy0-sta0 in, rather than select it from a list) Click the “Create Interface” button. Back in the list of interfaces, find the interface you just created, and click its “Edit” button. General Settings tab: Protocol: DHCP client Device: phy0-sta0 Disable this interface: unchecked Bring up on boot: unchecked Hostname to send when requesting DHCP: Do not send a hostname Advanced Settings tab: 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: 1.1.1.1; 1.0.0.1; 9.9.9.9; 149.112.112.112 NOTE: If you’re using DoH (DNS-over-HTTPS) to reroute DNS requests to HTTPS, leave Advanced Settings >> Use custom DNS Servers blank. DNS weight: 0 Use gateway metric: 100 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: blank Firewall Settings tab: Create/Assign firewall-zone: wan + wan6 DHCP Server tab: No DHCP Server configured for this interface. Click the “Save” button. Back at Network >> Interfaces, click the “Add new interface…” button again. Add new interface…: Name: {CHOOSE A NAME} I chose hotspot6. Add new interface…: Protocol: DHCPv6 client Add new interface…: Device: Alias Interface: @{whatever you named the first interface} Mine would be: Alias Interface: “@hotspot” Click the “Create Interface” button. Back in the list of interfaces, find the interface you just created, and click its “Edit” button. General Settings tab: Protocol: DHCPv6 client Device: @{whatever you named the first interface} Mine would be: @hotspot General Settings: 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 Advanced Settings tab: Force link: unchecked Preferred client ID: Automatic Client ID to send when requesting DHCP: blank Use default gateway: checked Use DNS servers advertised by peer: unchecked Use custom DNS servers: 1.1.1.1; 1.0.0.1; 9.9.9.9; 149.112.112.112 NOTE: If you’re using DoH (DNS-over-HTTPS) to reroute DNS requests to HTTPS, leave Advanced Settings >> Use custom DNS Servers blank. DNS weight: 0 Use gateway metric: 100 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: blank Firewall Settings tab: Create/Assign firewall-zone: wan + wan6 DHCP Server tab: No DHCP Server configured for this interface. Network >> Wireless Find the 2.4 GHz radio (radio0) and click the 'Add' button Device Configuration >> General Setup tab: Operating frequency >> Mode: AX Operating frequency >> Channel: auto Width: 20 MHz Allow legacy 802.11b rates: unchecked Maximum transmit power: driver default Country Code: {YOUR COUNTRY} Device Configuration >> Advanced Settings tab: Coverage cell density: Disabled Distance Optimization: blank Fragmentation Threshold: blank RTS/CTS Threshold: blank Force 40MHz mode: unchecked Beacon Interval: 300 Rx LDPC: checked Tx LDPC: checked Interface Configuration >> General Setup tab: Mode: client ESSID: {YOUR HOTSPOT'S SSID} Mine is Teracube. BSSID: blank Network: {THE NETWORK YOU JUST CREATED} In my case, it's hotspot. Interface Configuration >> Wireless Security tab: Encryption: WPA2-PSK/WPA3-SAE Mixed Mode (strong security) Key: {THE SSID'S PASSPHRASE} 802.11w Management Frame Protection: Optional 802.11w maximum timeout: blank 802.11w retry timeout: blank Operating Channel Validation: Disabled Interface Configuration >> Advanced Settings tab: Multi To Unicast: unchecked Isolate Bridge Port: unchecked Interface name: {THE DEVICE YOU CREATED} Mine would be phy0-sta0. MAC address: driver default Short Preamble: checked DTIM Interval: blank Time interval for rekeyinng GTK: blank Disable Inactivity Polling: unchecked Station inactivity limit: blank Maximum allowed Listen Interval: blank Disassociate on Low Acknowledgement: checked Click the Save button. Start up your cell phone’s hotspot. Under Network >> Interfaces, for the two interfaces you created (in my case, hotspot and hotspot6), click the 'Restart' buttons. Go to Network >> Wireless, and for the new wireless network you just created, if its button says “Enable”, click to enable the wireless network, then click the “Save & Apply” button. Wait for the router to connect to the cellphone hotspot. You’ll see the bars in the signal strength meter for that network turn blue, and numbers denoting the signal strength will display. In Services >> Terminal, enter: uci show wireless | grep .device= It will display something like this: wireless.default_radio0.device='radio0' wireless.default_radio1.device='radio1' wireless.travel_2g.device='radio0' That last entry should be the wireless network you set up to connect to the hotspot. To be absolutely sure, in Services >> Terminal, enter: uci show wireless.travel_2g It will display something like this: wireless.travel_2g=wifi-iface wireless.travel_2g.device='radio0' wireless.travel_2g.network='hotspot' wireless.travel_2g.mode='sta' wireless.travel_2g.ssid='{SSID OF YOUR HOTSPOT}' wireless.travel_2g.encryption='sae-mixed' wireless.travel_2g.key='{PASSPHRASE FOR THAT SSID}' If that is your hotspot, you’ll then change the code below to: INTERFACE=“travel_2g” NET_INTF=“hotspot” Go to System >> Startup >> Local Startup. Edit the code below to reflect your TARGET_SSID, your INTERFACE, your NET_INTF, your RADIO, the maximum_sleep you want (used to reduce CPU utilization) and whether you want logging (logging=1) or not (logging=0). To see the logs in real-time, in Services >> Terminal, you’d enter: logread -f | grep hotspot_monitor At System >> Startup >> Local Startup, paste the code below the first two comment lines, but before the last (exit 0) line and click the “Save” button. ## SET UP HOTSPOT MONITOR ## To read Hotspot Monitor logs, issue either: # logread -f | grep hotspot_monitor # while true; do clear; logread | grep "hotspot_monitor"; sleep 5; done ( # SETTINGS TARGET_SSID="Teracube" INTERFACE="travel_2g" NET_INTF="hotspot" RADIO="radio0" current_sleep=20 missed_checks=0 minimum_sleep=20 maximum_sleep=120 logging=1 # Give the MT7981 driver time to fully initialize on boot sleep 30 # Log the startup logger -t hotspot_monitor "Hotspot Monitor: $RADIO monitoring for SSID: $TARGET_SSID" while true; do # Check if the interface config is currently enabled in wireless settings IS_ENABLED=$(uci get wireless.$INTERFACE.disabled 2>/dev/null) # Check if the network interface layer is actually bound and running traffic INTF_UP=$(ubus call network.interface.$NET_INTF status 2>/dev/null | jsonfilter -e '@.up') if [ "$IS_ENABLED" = "0" ]; then # ------------------------------------------------------------- # IF CONNECTED: Trust UBUS network status. NEVER scan the airwaves! # ------------------------------------------------------------- if [ "$INTF_UP" = "true" ]; then HOTSPOT_FOUND=1 else HOTSPOT_FOUND=0 fi else # ------------------------------------------------------------- # IF DISCONNECTED: Safe to scan because the client layer is disabled! # ------------------------------------------------------------- if iwinfo phy0-ap0 scan 2>/dev/null | grep -q "ESSID: .$TARGET_SSID."; then HOTSPOT_FOUND=1 else HOTSPOT_FOUND=0 fi fi # ------------------------------------------------------------- # STATE ENGINE EXECUTION # ------------------------------------------------------------- if [ "$HOTSPOT_FOUND" -eq 1 ]; then missed_checks=0 if [ "$IS_ENABLED" = "1" ] || [ -z "$IS_ENABLED" ]; then if [ "$logging" = "1" ]; then logger -t hotspot_monitor "Hotspot Monitor: Hotspot $TARGET_SSID Found! Binding $RADIO to $NET_INTF and enabling client interface $INTERFACE." fi uci set wireless.$INTERFACE.disabled='0' uci set wireless.$INTERFACE.network="$NET_INTF" uci commit wireless wifi reload $RADIO # Force netifd to clear the operational "Disabled" state # This kicks the network interface back into active scanning mode. /sbin/ifup $NET_INTF missed_checks=0 current_sleep=$minimum_sleep sleep 45 else if [ "$logging" = "1" ]; then logger -t hotspot_monitor "Hotspot Monitor: Hotspot $TARGET_SSID stable. Sleeping for $current_sleep seconds." fi sleep "$current_sleep" current_sleep=$((current_sleep + current_sleep)) if [ "$current_sleep" -gt "$maximum_sleep" ]; then current_sleep=$maximum_sleep fi fi else missed_checks=$((missed_checks + 1)) if [ "$missed_checks" -gt 10 ]; then missed_checks=10 fi if [ "$missed_checks" -ge 2 ]; then if [ "$IS_ENABLED" = "0" ]; then if [ "$logging" = "1" ]; then logger -t hotspot_monitor "Hotspot Monitor: Hotspot $TARGET_SSID dropped! Disabling interface $INTERFACE to keep $RADIO stable." fi uci set wireless.$INTERFACE.disabled='1' uci commit wireless wifi reload $RADIO current_sleep=$minimum_sleep sleep 20 else if [ "$logging" = "1" ]; then logger -t hotspot_monitor "Hotspot Monitor: $RADIO monitoring for SSID: $TARGET_SSID. Sleeping for $current_sleep seconds." fi sleep "$current_sleep" current_sleep=$((current_sleep + 1)) if [ "$current_sleep" -gt "$maximum_sleep" ]; then current_sleep=$maximum_sleep fi fi else sleep 20 fi fi done ) &
Then to start the script, either reboot the router, or issue the command:
sh /etc/rc.local
… in Services >> Terminal.
Note that I’m using metrics… the cellphone hotspot has a metric of 100, whereas the main ISP has a metric of 1. So if both the main ISP and the cellphone hotspot are connected, data preferentially flows to/from the main ISP.
If you want to make this survive firmware updates, in the LuCI interface, go to System >> Backup / Flash Firmware >> Configuration tab, and put /etc/rc.local in that list, then click the Save button.