Per-VLAN/SSID Bandwidth Priority on GL.iNet Routers — A Weekend Project That Actually Works

By WickedYoda

Tested on: Flint 3, Flint 2, Flint 4 / GL-BE14000, Slate 7, Slate 7 Pro, Beryl 7, Flint 3e / GL-BE6500

Repo: https://github.com/wickedyoda/Glinet-Bandwidth-script

If you’ve ever had one person on your network max out every bit of upload bandwidth while everyone else’s video calls stuttered, you’ve felt the pain of “flat” networking. Every device gets equal treatment — until one of them doesn’t.

This weekend I built a script that changes that. It gives each VLAN and SSID on your GL.iNet router its own bandwidth bucket with a configurable priority, so your work laptops can keep working even when someone else is uploading a 4K video.

Here’s what QoS and SQM actually mean, and how the script implements them on OpenWrt.

Part 1: What QoS and SQM Actually Are

QoS (Quality of Service)

QoS is traffic shaping with priority. Imagine your internet connection is a highway:

  • Without QoS: Every car drives in the same lane. One semi-truck going slow blocks everything behind it.
  • With QoS: You get an HOV lane for emergency vehicles, a passing lane for fast traffic, and a slow lane for trucks. Each lane has its own speed limit, but they all share the same road.

In router terms, QoS divides your WAN upload/download bandwidth into queues (HTB classes) and assigns each queue a priority. High-priority traffic (VoIP, SSH, gaming) gets forwarded first. Low-priority traffic (background updates, cloud backups) gets the leftovers.

The two most common QoS mechanisms on OpenWrt:

Mechanism What it does Best for
HTB (Hierarchical Token Bucket) Divides bandwidth into parent/child classes with hard rate limits and priorities Per-VLAN or per-device priority
fq_codel Fights bufferbloat by dynamically managing queue depth Per-queue fairness within a class

SQM (Smart Queue Management)

SQM is a specific type of QoS that focuses on one thing: reducing bufferbloat.

Bufferbloat happens when your router’s queue fills up with packets waiting to be sent. A single large download can fill the queue, and every other packet — even a tiny VoIP packet — has to wait behind it. The result: ping spikes from 20ms to 500ms+ while a download is running.

SQM solves this with CAKE (Common Applications Kept Enhanced), which:

  1. Measures your actual WAN bandwidth (not the ISP-advertised number)
  2. Sets a rate limit slightly below your real throughput
  3. Uses a smart queueing discipline that prioritizes small, interactive packets
  4. Keeps ping stable regardless of how much bandwidth is being used

The net result: your download can max out your connection and your Zoom call stays smooth.

The Key Difference

Feature QoS SQM
Granularity Per-VLAN, per-interface, per-device Per-WAN interface
Priority Explicit priority levels (high/med/low) Implicit — small packets win
Best use case Multi-VLAN homes, offices, IoT segregation Single-WAN bufferbloat reduction
Complexity Higher — requires class/queue config Lower — one setting per WAN

Most people need both: SQM for the WAN link, and QoS to make sure the guest network doesn’t eat the IoT network’s lunch.

Part 2: How the Script Modifies QoS/SQM for Priority

What the script does

The script (glinet-vlan-qos.sh) is a pure shell/OpenWrt implementation. No Python, no Node, no extra dependencies beyond what’s already on the router. It touches three subsystems:

  1. tc — traffic control (HTB qdisc + classes + CAKE leaves)
  2. nft — firewall marks (classify traffic by ingress interface)
  3. Persistence hooks/etc/gl-switch.d/ and /etc/rc.local

The HTB hierarchy

When you run glinet-vlan-qos.sh start, it builds an HTB tree on each bridge/interface:

root qdisc (handle 1: on br-lan, 2: on br-iot, 3: on br-guest, 4: on tailscale0)

├── class 1:1 parent/root class, rate = full interface bandwidth

  ├── class 1:10 LAN + Tailscale, prio 1 (highest)

    └── CAKE leaf (besteffort triple-isolate) fq_codel-style fairness

  ├── class 1:20 IoT, prio 2 (medium)

    └── CAKE leaf (besteffort)

  └── class 1:30 Guest, prio 3 (lowest)

      └── CAKE leaf (besteffort)

The priority numbers (prio 1, prio 2, prio 3) are what control the actual scheduling order. HTB always dequeues from the lowest-numbered class first. So if LAN and Guest both have packets waiting, LAN goes first.

Each class has a rate (guaranteed minimum) and a ceil (maximum burst). By default on a Flint 3:

Class Rate Ceil Priority
LAN + Tailscale 200 Mbps 200 Mbps 1 (highest)
IoT 50 Mbps 50 Mbps 2
Guest 20 Mbps 20 Mbps 3 (lowest)

These are configurable in /etc/gl-qos-vlan.conf — edit the QOS_*_BW_UP and QOS_*_BW_DOWN variables to whatever your plan actually delivers.

The nft marks

HTB classes handle egress shaping, but we also need to make sure packets are classified into the right class when they enter the router. That’s where nft comes in:

table inet gl-qos {

    chain preraw {

        type filter hook prerouting priority mangle; policy accept;

        iifname “br-iot”  meta mark set 0x00020000   IoT class

        iifname “br-guest” meta mark set 0x00030000  Guest class

        iifname “tailscale0” meta mark set 0x00040000 Tailscale class

        iifname “br-lan”   meta mark set 0x00010000   LAN class

    }

}

When a packet arrives on br-iot, the nft rule stamps it with the mark 0x00020000. The HTB u32 filter (on Flint 3 / tc-full) then matches that mark and routes the packet into class 1:20. On Flint 2-class devices without u32 support, the classification happens at the bridge level instead — the script detects this automatically and skips the u32 filter.

The CAKE leaf

Each HTB class gets a CAKE qdisc attached as a leaf. CAKE isn’t just for WAN — it works on LAN bridges too, and it provides:

  • Flow isolation: Each TCP/UDP flow gets its own internal queue, so one device can’t monopolize a class
  • AQM: Active Queue Management drops packets before the queue fills, keeping latency low
  • Triple-isolate mode: On the LAN class, this isolates flows by both source and destination, preventing cross-device interference

SQM mode

If you choose SQM mode in the setup wizard, the script skips the per-VLAN HTB hierarchy entirely and instead applies a single CAKE qdisc on the WAN interface (eth0 or wan). The rate is set to 95% of your measured WAN bandwidth to avoid oversubscription.

SQM mode is simpler but doesn’t give you per-VLAN priority. It’s ideal if you only have one flat LAN and just want the bufferbloat gone.

Persistence

The script offers two persistence options:

Option 1 — Persistent (PERSISTENT=1, default):

  • Creates /etc/gl-switch.d/vlan-qos.sh — GL.iNet’s network-restart hook runs this after every network change
  • Appends a start call to /etc/rc.local — runs on boot
  • Survives firmware upgrades, UI config changes, and network restarts
  • Does not survive factory reset (expected)

Option 2 — Manual only (PERSISTENT=0):

  • No auto-start hooks installed
  • Survives UI config changes but not reboots
  • Use for testing or temporary QoS

Part 3: Testing on Multiple Models

I tested this over the weekend on every GL.iNet model I could get my hands on. The script auto-detects the model at runtime and adjusts accordingly.

What works on every model

  • HTB root qdisc on br-lan, br-iot, br-guest
  • HTB classes with rate/ceil/prio
  • CAKE leaf qdiscs
  • nft mark rules
  • /etc/gl-qos-vlan.conf configuration file
  • Persistence hooks
  • start|stop|restart|status|detect|install|uninstall commands

Tested Model-specific differences

Model OpenWrt tc variant u32 filters Notes
Flint 3 23.05-SNAPSHOT tc-full ✅ Yes Full feature set, tested extensively
Flint 2 21.02-SNAPSHOT tc-tiny ❌ No Falls back to bridge-level classification
Flint 4 / GL-BE14000 21.02-SNAPSHOT tc-tiny ❌ No Same path as Flint 2
Slate 7 23.05-SNAPSHOT tc-full ✅ Yes Same as Flint 3
Slate 7 Pro / GL-BE10000 21.02-SNAPSHOT tc-tiny ❌ No Same as Flint 2
Beryl 7 / GL-MT3600BE 21.02-SNAPSHOT tc-tiny ❌ No Same as Flint 2
Flint 3e / GL-BE6500 23.05-SNAPSHOT tc-full ✅ Yes Same as Flint 3

The tc-tiny vs tc-full distinction matters because tc-tiny strips out the u32 filter module to save flash space. The script detects this at runtime — if tc filter add … u32 fails, it falls back to nft-based classification only. No user intervention needed.

Verification

After applying QoS, run:

glinet-vlan-qos.sh status

You should see:

  • HTB qdisc on each bridge
  • CAKE leaf qdiscs on each class
  • nft table inet gl-qos with mark rules

Or check manually:

# HTB classes

tc class show dev br-lan

# CAKE qdiscs

tc qdisc show dev br-lan

# nft marks

nft list table inet gl-qos

Part 4: What This Means in Practice

Here’s a real scenario. My home network has:

  • br-lan: Work laptops, gaming PC, phones — needs low latency
  • br-iot: Smart bulbs, thermostats, cameras — tolerates delays
  • br-guest: Visitors’ devices — lowest priority, bandwidth-gated
  • tailscale0: Remote admin/mesh VPN — same priority as LAN

Without QoS, a firmware update on 12 IoT devices could saturate upload and make SSH sessions lag. With QoS:

  • SSH into the router stays snappy (LAN class, prio 1)
  • IoT devices can still talk to their cloud services (IoT class, 50 Mbps guaranteed)
  • A guest streaming Netflix gets shaped to 20 Mbps (Guest class, prio 3)
  • Tailscale tunnels stay responsive (same class as LAN)

The bandwidth numbers are configurable per-model. The defaults are conservative — you can tune them to match your actual plan.

Part 5: Installation

# One-liner

curl -fsSL https://raw.githubusercontent.com/wickedyoda/Glinet-Bandwidth-script/main/install.sh | sh

# Run the setup wizard

glinet-vlan-qos-setup.sh

The wizard walks you through model selection, mode selection (QoS vs SQM), persistence, and VLAN/SSID priority ordering.

Requirements

  • GL.iNet Flint 2, Flint 3, Flint 4, Slate 7, Slate 7 Pro, Beryl 7, or Flint 3e
  • OpenWrt 21.02+
  • tc-full or tc-tiny, kmod-sched-cake, sqm-scripts
  • Root SSH access

Uninstall

glinet-vlan-qos.sh uninstall

Removes the script, persistence hooks, HTB qdiscs, and nft marks.

Part 6: The Weekend Testing Notes

This wasn’t a quick hack. I spent the actual weekend testing on real hardware:

  • Model auto-detection was the hardest part. GL.iNet’s board.json model IDs are inconsistent across firmware versions, so the script tries three detection methods: board.json ID, /etc/openwrt_release target strings, and /etc/glversion firmware version matching. If all three fail, it falls back to Flint 2-safe defaults and warns you.
  • Persistence through firmware upgrades required GL.iNet’s /etc/gl-switch.d/ hook directory, which runs scripts after every network config change. Combined with /etc/rc.local for boot, the QoS config survives everything except factory reset.
  • The u32 filter problem on tc-tiny devices required a complete fallback path. Instead of u32 source-IP matching, the script relies entirely on nft ingress marks. This is actually cleaner — nft marks are set once on ingress and the HTB classes reference them.
  • The setup wizard iterates on available interfaces at runtime. If br-iot doesn’t exist on your device, it’s simply skipped — no errors, no manual config editing needed.

Troubleshooting

Model detection fails:

cat /etc/board.json | grep model

export QOS_MODEL=flint3   # or flint2

glinet-vlan-qos.sh start

QoS not applying on Flint 2/Flint 4:

tc filter show dev br-lan   # expect no u32 filters — this is normal

tc class show dev br-lan    # should show HTB classes

Persistence not working:

ls -la /etc/gl-switch.d/vlan-qos.sh

/etc/gl-switch.d/vlan-qos.sh   # test manually

Disclaimer

This is an unofficial script. I’ve tested it on the models and firmware versions listed above, but you’re responsible for any changes to your router. By using this script, you understand the risks and accept full responsibility.

Full disclaimer: Privacy Policy, Terms of Use, Disclaimer and Limitation of Liability

If you hit issues, open a ticket on GitHub: https://github.com/wickedyoda/Glinet-Bandwidth-script/issues

License

GPLv3 — see LICENSE for details.

Built over a weekend. Tested on real hardware. Priority guaranteed (maybe ;).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.