Skip to content

WAN Optimization for Latency-Sensitive Applications (POS, VoIP, Video)

A retail store with a 20 Mbps broadband connection and a 4G backup is running POS terminals, CCTV upload, a firmware update, and a staff member streaming music. Without traffic prioritization, a POS transaction — the thing that actually makes money — gets the same treatment as every other packet. That is a problem.

This post walks through how we configure QoS on Hopbox SD-WAN devices, from traffic classification to queue disciplines, with real config snippets from OpenWrt.

Bandwidth is not the bottleneck. Latency and jitter are.

A POS terminal sending a payment authorization request generates maybe 2–5 KB of data. On a 10 Mbps link, that takes less than a millisecond to transmit. But if that packet sits behind 500 KB of CCTV footage in a queue, it waits. If the link is saturated by a firmware download, the POS packet competes for buffer space and may be dropped, triggering a TCP retransmit.

The result: a transaction that should take 200ms takes 2 seconds. Or times out. The cashier retries. The customer waits. Multiply by hundreds of transactions per day across hundreds of stores.

QoS ensures that latency-sensitive traffic (POS, VoIP, video conferencing) gets priority access to the wire, regardless of what else is happening on the link.

Before you can prioritize traffic, you need to identify it. There are three main approaches, each with trade-offs.

The simplest method. POS terminals talk to payment gateways on known ports. VoIP uses SIP (5060/5061) and RTP (dynamic range, typically 10000–20000).

Terminal window
# nftables rules for traffic classification by port
table inet qos_classify {
chain prerouting {
type filter hook prerouting priority -150; policy accept;
# POS traffic — payment gateway ports
tcp dport { 443, 8443 } ip saddr 192.168.10.0/24 meta priority set 1:10
# VoIP signaling
udp dport { 5060, 5061 } meta priority set 1:10
# VoIP media (RTP)
udp dport 10000-20000 meta priority set 1:20
# CCTV upload — known NVR destinations
tcp dport 554 meta priority set 1:30
ip daddr 10.100.0.0/16 tcp dport 443 meta priority set 1:30
# Everything else — default priority
meta priority set 1:40
}
}

Port-based classification is fast and deterministic, but fragile. If the payment gateway changes its port, or if POS traffic is tunneled over a generic HTTPS connection, port matching misses it.

Differentiated Services Code Point (DSCP) uses 6 bits in the IP header’s ToS field to indicate traffic class. If your POS application or VoIP phone sets DSCP on outgoing packets, the Hopbox can classify based on those markings.

Terminal window
# Common DSCP values
# EF (46) — Expedited Forwarding: VoIP media
# AF41 (34) — Assured Forwarding: video conferencing
# AF21 (18) — Assured Forwarding: POS/transactional
# CS1 (8) — Scavenger: bulk/background traffic
# BE (0) — Best Effort: default
# nftables: classify based on DSCP
ip dscp ef meta priority set 1:10
ip dscp af41 meta priority set 1:20
ip dscp af21 meta priority set 1:15
ip dscp cs1 meta priority set 1:40

The advantage of DSCP is that the application itself declares its priority. The disadvantage is that most applications do not set DSCP, and ISPs frequently strip DSCP markings at their edge. Within your own SD-WAN overlay (over WireGuard tunnels), DSCP markings are preserved — the ISP only sees the outer WireGuard UDP packets.

In retail deployments, the simplest and most reliable approach is often source-based. POS terminals live on VLAN 10. CCTV cameras on VLAN 20. Staff devices on VLAN 30. Classification by source subnet:

Terminal window
# Classify by source VLAN/subnet
ip saddr 192.168.10.0/24 meta priority set 1:10 # POS VLAN
ip saddr 192.168.20.0/24 meta priority set 1:30 # CCTV VLAN
ip saddr 192.168.30.0/24 meta priority set 1:40 # Staff VLAN

This is what we use most often. It is not elegant, but it is robust. A POS terminal cannot accidentally end up in the wrong traffic class because it is physically (or logically via VLAN) on the priority network.

Classification assigns packets to classes. Queue disciplines decide how those classes share the available bandwidth.

Fair Queueing with Controlled Delay (fq_codel) is the default queue discipline on modern Linux kernels and OpenWrt. It combats bufferbloat by keeping queue depth low and fairly distributing bandwidth across flows.

Terminal window
# Check current qdisc on an interface
root@hopbox:~# tc qdisc show dev eth0
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514
target 5ms interval 100ms memory_limit 4Mb ecn drop_batch 64

fq_codel is excellent for general fairness but does not provide strict prioritization. A POS packet and a firmware download packet get “fair” treatment — which is not what we want.

Common Applications Kept Enhanced (cake) is an evolution of fq_codel designed specifically for home and small-office routers. It includes built-in traffic shaping, per-host fairness, and DSCP-based priority tiers.

Terminal window
# Apply CAKE with bandwidth shaping and priority tiers
tc qdisc replace dev eth0 root cake bandwidth 20mbit diffserv4 nat wash
# diffserv4 creates 4 tiers based on DSCP:
# Tin 0 (Bulk): CS1, LE — 1/16 bandwidth share
# Tin 1 (Best Effort): CS0, default — 4/16 bandwidth share
# Tin 2 (Video): CS2-CS5, AFxx — 4/16 bandwidth share
# Tin 3 (Voice): CS6, CS7, EF — 1/16 bandwidth share (but strict priority)

The bandwidth parameter is critical. Set it to slightly below your actual link speed (e.g., 18mbit for a 20 Mbps link). This ensures the queue is on your device, not in your ISP’s buffer — giving you control over what gets dropped and what gets prioritized.

Hierarchical Token Bucket (HTB) for Fine-Grained Control

Section titled “Hierarchical Token Bucket (HTB) for Fine-Grained Control”

When CAKE’s four tiers are not enough, we use HTB with explicit class hierarchy:

Terminal window
# HTB setup with guaranteed minimums per class
tc qdisc add dev eth0 root handle 1: htb default 40
# Root class — total link bandwidth
tc class add dev eth0 parent 1: classid 1:1 htb rate 20mbit ceil 20mbit
# POS traffic — guaranteed 5mbit, can burst to full link
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit ceil 20mbit prio 0
tc qdisc add dev eth0 parent 1:10 handle 10: fq_codel
# VoIP — guaranteed 2mbit, strict priority
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 2mbit ceil 5mbit prio 1
tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
# CCTV — guaranteed 8mbit, lower priority
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 8mbit ceil 15mbit prio 2
tc qdisc add dev eth0 parent 1:30 handle 30: fq_codel
# Everything else — gets whatever is left
tc class add dev eth0 parent 1:1 classid 1:40 htb rate 2mbit ceil 20mbit prio 3
tc qdisc add dev eth0 parent 1:40 handle 40: fq_codel

The key design choices:

  • POS gets prio 0 — highest priority scheduling. When POS packets are in the queue, they go first.
  • POS rate is 5mbit — even under full congestion, POS traffic gets at least 5 Mbps. It does not need it (POS is low-bandwidth), but the guaranteed allocation means it never starves.
  • POS ceil is 20mbit — it can use the full link if no one else needs it.
  • CCTV gets 8mbit guaranteed — enough for continuous upload, but capped at 15mbit so it does not saturate the link.
  • Each leaf class uses fq_codel — fair queueing within each priority tier, preventing a single flow from dominating its class.

VoIP is uniquely sensitive to jitter. It is not just latency — it is variation in latency. A consistent 100ms one-way delay is fine for voice; a delay that swings between 50ms and 200ms is not.

The ITU-T G.114 recommendation sets a one-way delay target of 150ms for voice. The typical jitter budget breakdown:

Component Budget
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Codec processing 20ms
Jitter buffer 40ms
Network transit 60ms
De-jitter + playback 30ms
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total 150ms

Network transit gets 60ms. If your WAN link has 30ms baseline latency, you have 30ms of headroom for jitter. If jitter exceeds that, the jitter buffer underruns and you hear choppy audio.

QoS policies for VoIP must:

  1. Guarantee low queue delay — strict priority scheduling, small queue depth
  2. Limit competing traffic — shape bulk traffic below link capacity so VoIP never sits behind a full buffer
  3. Minimize packet loss — VoIP uses UDP; a lost packet is a gap in audio, not a retransmit

QoS configuration without measurement is guesswork. We measure before and after applying policies:

Terminal window
# Measure bufferbloat with the ICMP-under-load test
# Terminal 1: saturate the link
iperf3 -c speedtest.hopbox.in -t 30 -P 4
# Terminal 2: measure latency during saturation
ping -c 30 8.8.8.8
# Without QoS (typical):
# round-trip min/avg/max = 12.4/347.2/892.1 ms
# With CAKE shaping (typical):
# round-trip min/avg/max = 12.8/18.4/31.2 ms

The difference is dramatic. Without QoS, latency under load spikes to nearly a second — that is a POS transaction timeout. With CAKE shaping the link to just below capacity, latency stays within 30ms even under full saturation.

Every Hopbox device reports per-class queue statistics to Prometheus:

Terminal window
# Exported metrics
hopbox_tc_class_bytes_total{class="pos",interface="eth0"} 1248576
hopbox_tc_class_packets_total{class="pos",interface="eth0"} 8724
hopbox_tc_class_drops_total{class="pos",interface="eth0"} 0
hopbox_tc_class_overlimits_total{class="bulk",interface="eth0"} 42891

If drops_total for the POS class ever goes above zero, we have a problem — it means even the guaranteed bandwidth allocation is insufficient, or classification is misassigning traffic. This triggers an alert for the NOC to investigate.

A complete QoS configuration for a retail Hopbox site involves:

  1. VLAN segmentation — POS, CCTV, staff on separate VLANs
  2. nftables classification — source-subnet-based, marking packets with class IDs
  3. CAKE or HTB shaping — on the WAN egress interface, shaped to 90% of measured link speed
  4. Per-link tuning — primary fiber link gets different shaping than 4G backup (different bandwidth, different latency characteristics)
  5. Monitoring — per-class drop/queue metrics exported to Prometheus
  6. Ansible-managed — QoS configs generated from site inventory, pushed via management plane

The result: a POS transaction completes in under 200ms even when the store’s CCTV system is uploading footage and an employee is watching a training video. The network does not need more bandwidth — it needs better scheduling.

v1.7.9