Skip to main content
NJannasch.Dev

Beryl AX OpenWrt Mobile Office Router with Antigravity CLI

· 7 min read
OpenWrtNetworkingMobile WorkHomelabAntigravity CLI

TL;DR: I turned a GL.iNet GL-MT3000 into a mobile-office router for garden and travel work. Pixel 8 over USB-C as WAN, Mobile_Net as my private Wi-Fi, SQM CAKE for calls, encrypted DNS, and ad blocking. The interesting part: I set almost all of it up through Antigravity CLI over SSH, while debugging real router state instead of copy-pasting forum commands.

I wanted a small router I could throw into a bag, bring to the garden, connect to my phone, and use as a real work network.

Not a phone hotspot.

A small private network with my own SSID, DNS filtering, encrypted DNS, and traffic shaping for calls.

The hardware was already sitting on my desk: a GL.iNet GL-MT3000, also known as the Beryl AX. It runs OpenWrt, has Wi-Fi 6, USB 3.0, a 2.5G port, and enough CPU for a proper travel-router setup.

The phone was a Pixel 8.

The workflow was the part that made it fun. I did not sit there with a pile of wiki tabs and a terminal full of half-remembered UCI commands. I drove the setup through Antigravity CLI. It SSH’d into the router, checked packages, read dmesg, installed drivers, wrote UCI config, and verified each change from the actual device.

I knew the architecture I wanted. Antigravity CLI handled the repetitive execution and the boring diagnostics.

The final shape is simple:

Pixel 8 USB tether -> OpenWrt GL-MT3000 -> Mobile_Net -> laptop

Because this is OpenWrt, that simple diagram hides a lot of useful machinery.

The Result, Up Front

The final setup:

FeatureStatus
Android USB tetheringWorking via usb0
Phone testedPixel 8
RouterGL.iNet GL-MT3000 / Beryl AX
OpenWrt25.12.3 sysupgrade image
Local Wi-FiMobile_Net on 2.4 GHz and 5 GHz
SQMCAKE on usb0
Default SQM profilebalanced, 100/25 Mbit/s
DNShttps-dns-proxy + OpenWrt adblock

The router still had plenty of headroom: about 95% CPU idle, 346 MB RAM available, and 198 MB overlay storage free after installing the packages.

The Workflow

The useful part of Antigravity CLI was not that it generated commands. It kept checking the machine in front of it.

It SSH’d into the router, discovered the actual platform, and found:

Model: GL.iNet GL-MT3000 (Beryl AX)
Architecture: MediaTek Filogic / aarch64_cortex-a53
OpenWrt: 25.12.3

That mattered because the old tutorials I had found were not written for this stack. They assumed:

  • older OpenWrt
  • opkg
  • iptables
  • often rooted Android
  • often RNDIS tethering

My router had:

  • OpenWrt 25.x
  • apk
  • firewall4
  • nftables
  • a Pixel 8 using CDC-NCM

Antigravity CLI compared the guide against the router, noticed the package manager difference, checked kernel logs, and adjusted.

The loop became:

inspect state
apply one change
reload service
verify output
adjust when reality disagrees

That loop is the difference between generated router commands and a working router.

The First Wall: Android USB Tethering Is Not Just USB Tethering

The old guides I found were mostly written for older OpenWrt releases and rooted Android phones. They used opkg, iptables, and RNDIS assumptions.

That was already outdated for this setup.

OpenWrt 25.x uses apk, and firewall handling is now firewall4 with nftables. The Pixel 8 also did not use the old path I expected. It needed CDC-NCM support.

The package set that mattered was:

apk update
apk add kmod-usb-net-rndis kmod-usb-net-cdc-ether kmod-usb-net-cdc-ncm

The important one for the Pixel 8 was kmod-usb-net-cdc-ncm. Without it, the phone could be physically connected but never appear as a useful network interface.

Once the driver was in place, OpenWrt saw the phone as usb0:

usb0 UP 10.91.215.62/24

That was the first real milestone. The router had a WAN interface coming from the phone.

The annoying part before that: the first cable produced USB enumeration errors:

device descriptor read/64, error -71
unable to enumerate USB device

That was not an OpenWrt problem. It was the cable. Switching to a better USB-C cable made the router see a SuperSpeed USB device, and only then did the driver question matter.

The OpenWrt config after that was straightforward: create a DHCP interface on usb0, add it to the WAN firewall zone, and let the router NAT local Wi-Fi clients behind the phone.

For TTL normalization, the old iptables mangle rule becomes an nftables include under firewall4:

oifname "usb0" ip ttl set 65
oifname "usb0" ip6 hoplimit set 65

I am deliberately calling this TTL normalization here rather than pretending it is just a magic performance tweak. Depending on your mobile plan and carrier terms, this may or may not be allowed. Know what you are doing before copying this part.

Broadcasting the Actual Work Wi-Fi

The router broadcasts one SSID on both bands:

Mobile_Net

Near the router, the laptop got a very good 5 GHz link:

RX rate: 1200.9 Mbit/s
TX rate: 1200.9 Mbit/s
Expected throughput: ~979 Mbit/s

At one point the laptop moved to 2.4 GHz instead:

RX rate: 154.8 Mbit/s
TX rate: 286.7 Mbit/s
Expected throughput: ~245 Mbit/s

That is still usable, but for calls and work near the router I would prefer 5 GHz. Splitting the SSIDs into Mobile_Net_5G and Mobile_Net_2G is probably the next practical tweak.

SQM: The Part That Matters for Calls

Mobile links are strange. A speed test can look good while calls still feel bad.

The problem is not only bandwidth. It is queueing and jitter.

I installed SQM with CAKE:

apk add sqm-scripts luci-app-sqm kmod-sched-cake

Then configured it on usb0. After measuring more, the useful tuning values looked like this:

ProfileDownloadUploadUse Case
stable17 Mbit/s5 Mbit/sCalls on weak signal
balanced100 Mbit/s25 Mbit/sDefault work mode
fast200 Mbit/s40 Mbit/sStrong 5G
max270 Mbit/s50 Mbit/sNear 300 Mbit/s downlink

The current default is balanced. The router has no problem handling this. The CAKE queues were clean and CPU stayed mostly idle.

The Latency Reality Check

This is where the setup became honest, but also where I do not want to over-claim. I only tested this indoors so far, not yet in the garden where I actually want to use it.

The local Wi-Fi path was fine:

Laptop -> router:
0% packet loss
avg ~10 ms
max ~34 ms

The cellular path was more variable:

Router usb0 -> 1.1.1.1:
0% loss
avg ~131 ms
max ~534 ms

The lesson is simple: the router can shape queues, filter DNS, and provide a stable Wi-Fi network. It cannot make a bad cellular moment good.

For Zoom, the real test still has to happen outside. I need to compare Vodafone vs o2/Telefonica at the actual garden location, keep the laptop on 5 GHz, and use a more conservative SQM profile during important calls.

If this becomes a serious everyday remote-work setup, a dedicated 5G CPE near the window with Ethernet into the GL-MT3000 is probably better than phone tethering.

No amount of router tuning fixes a bad radio moment.

Encrypted DNS and Ad Blocking

I also added encrypted DNS and ad blocking.

apk add https-dns-proxy luci-app-https-dns-proxy
apk add adblock luci-app-adblock luci-i18n-adblock-de

The router now forwards local DNS through DoH and blocks ad/tracker domains before they leave the network:

Client on Mobile_Net -> dnsmasq + adblock -> https-dns-proxy -> DoH upstream

The first successful adblock run loaded 273,763 blocked domains using the adguard, adguard_tracking, and certpl feeds.

The quick check:

nslookup doubleclick.net 127.0.0.1
nslookup openai.com 127.0.0.1

Ad/tracker domains returned NXDOMAIN, while normal domains still resolved.

Outlook

The setup works, but a few improvements are obvious:

  • split the SSIDs into Mobile_Net_5G and Mobile_Net_2G
  • add Mobile_Guest for visitors
  • add Travelmate for hotel/campsite Wi-Fi and captive portals
  • add WireGuard for public-Wi-Fi mode
  • test Vodafone vs o2/Telefonica outside in the actual garden
  • consider a dedicated 5G CPE if this becomes a daily setup
5G CPE near window -> Ethernet -> GL-MT3000 -> Mobile_Net

That would keep OpenWrt doing what it is good at, while a dedicated modem handles the cellular radio.

The Lesson

This project reminded me why I like OpenWrt.

The GL-MT3000 is a tiny Linux machine, but it is not pretending to be a general-purpose server. It is a network appliance with just enough programmability to become exactly the router I wanted.

The final setup is not just “phone hotspot, but with more steps”.

It is a mobile work network:

  • one SSID for my devices
  • USB tether as WAN
  • SQM for calls
  • encrypted DNS
  • ad blocking
  • a clear path toward travel Wi-Fi and captive portal handling
  • SSH-debuggable when something breaks

The remaining bottleneck is not the router. It is the cellular link.

That is a useful boundary to discover. Once the local network is stable, the next improvements are physical: phone placement, carrier choice, and eventually maybe a real 5G modem.

The Antigravity CLI part of this was not “the tool magically knew networking.” It was more mundane and more useful: it kept state, ran the checks, noticed when assumptions failed, and turned a long OpenWrt setup into an interactive debugging session.

I still had to decide what good looked like.

Antigravity CLI made getting there much faster.

The views and opinions expressed here are my own and do not reflect those of my employer.