Skip to content

QoS and Traffic Shaping

Networks have finite bandwidth. When the pipe is full, packets queue and then get dropped - this affects all traffic equally by default. Quality of Service (QoS) lets you prioritize some traffic over others, ensuring critical traffic (VoIP calls, video conferencing) isn’t degraded by bulk traffic (backups, downloads).

Without QoS: With QoS:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ VoIP (20 Kbps) β”‚ ──mix──▢ 🚧 β”‚ VoIP (20 Kbps) β”‚ ──fast lane──▢ βœ…
β”‚ Backup (100Mbpsβ”‚ β”‚ Backup (100Mbps) β”‚ ──slow lane──▢ βœ…
β”‚ All suffer! β”‚ β”‚ Both work! β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

DSCP is a 6-bit field in the IP header (within the former Type of Service byte). Routers read this field to determine how to treat the packet. The sender marks the packet, and every router along the path can honor that marking.

IPv4 Header - Type of Service (ToS) Byte:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”
β”‚ DSCP (6 bits) β”‚ ECN β”‚ β”‚
β”‚ Differentiated Services β”‚ (2b)β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”˜
PHBDSCP ValueBinaryTraffic TypeDrop Probability
Default (BE)0000000Best Effort - no priorityStandard
CS18001000Background (lower than default)High
AF1110001010Bulk data - low priorityLow
AF2118010010Bulk data - medium priorityLow
AF3126011010Business critical appsLow
AF4134100010Video conferencingLow
CS540101000SignalingLow
EF46101110Expedited Forwarding - VoIPVery Low
CS648110000Network control (routing protocols)Very Low
CS756111000Network managementVery Low

Before a packet can be prioritized, it must be identified:

MethodHow
DSCP/TOS markingRead the DSCP field set by the sender
Port-basedTCP/UDP port number (SSH=22, SIP=5060)
IP addressSource or destination IP
ProtocolTCP, UDP, ICMP
Deep Packet Inspection (DPI)Look inside the payload (detects apps using non-standard ports)

Queuing determines what to do when the link is congested:

AlgorithmHow it worksBest for
FIFO (First In, First Out)No priority, pure orderSimple links, no QoS
WFQ (Weighted Fair Queuing)Multiple queues, each gets bandwidth shareGeneral QoS
CBWFQ (Class-Based WFQ)Admin-defined classes each get minimum bandwidth %Enterprise QoS
LLQ (Low-Latency Queuing)Strict priority queue for real-time + CBWFQ for restVoIP environments
HTB (Hierarchical Token Bucket)Linux’s class-based queuingLinux servers/routers
HFSCHierarchical fair service curvesComplex multi-class scenarios
MechanismWhat it doesEffect on excess
Traffic ShapingBuffers excess traffic and sends later (smooth the burst)Delay (no drop if buffer not full)
Traffic PolicingDrops or re-marks packets exceeding the rateDrop or DSCP downgrade
Shaping: β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆ β†’ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
(smoothed, delayed)
Policing: β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆ β†’ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ
(excess dropped)

The tc command (from iproute2) implements QoS on Linux. It can:

  • Set up queuing disciplines (qdiscs)
  • Create traffic classes
  • Add filters to classify traffic
Terminal window
# Limit outbound traffic to 10 Mbit/s total
sudo tc qdisc add dev eth0 root handle 1: htb default 10
# Add a class with 10 Mbit/s ceiling
sudo tc class add dev eth0 parent 1: classid 1:10 htb rate 10mbit
# Apply to all traffic on eth0
sudo tc filter add dev eth0 protocol ip parent 1:0 u32 match ip dst 0.0.0.0/0 flowid 1:10
# View current qdisc setup
tc qdisc show dev eth0
tc class show dev eth0
tc filter show dev eth0
Terminal window
# Create 3-priority prio qdisc
sudo tc qdisc add dev eth0 root handle 1: prio priomap 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2
# Band 0 (highest) = DSCP EF (VoIP)
# Band 1 (medium) = DSCP AF
# Band 2 (lowest) = best effort
# Filter: DSCP 46 (EF) β†’ priority band 0
sudo tc filter add dev eth0 parent 1:0 protocol ip handle 0x2e/0xfc fw flowid 1:1
# View stats (bytes/packets per queue)
tc -s qdisc show dev eth0
Terminal window
# Limit a single IP to 1 Mbit/s (rate limiting for a client)
sudo tc qdisc add dev eth0 root handle 1: htb default 10
sudo tc class add dev eth0 parent 1: classid 1:10 htb rate 10mbit # default class
sudo tc class add dev eth0 parent 1: classid 1:20 htb rate 1mbit # limited class
# Filter: traffic to 192.168.1.100 β†’ limited class
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 \
u32 match ip dst 192.168.1.100/32 flowid 1:20
# Clean up
sudo tc qdisc del dev eth0 root

Terminal window
# Mark packets for QoS classification using iptables (DSCP marking)
# Mark all SSH traffic as DSCP CS6 (network control)
sudo iptables -t mangle -A OUTPUT -p tcp --dport 22 -j DSCP --set-dscp-class CS6
# Mark VoIP (SIP port 5060) as EF
sudo iptables -t mangle -A OUTPUT -p udp --dport 5060 -j DSCP --set-dscp 46
# Mark backup traffic as CS1 (background, below best-effort)
sudo iptables -t mangle -A OUTPUT -p tcp --dport 873 -j DSCP --set-dscp-class CS1 # rsync
# View marked packets
sudo iptables -t mangle -L OUTPUT -n -v

Terminal window
# Check if packets are being queued/dropped
tc -s qdisc show dev eth0
# Output includes: sent X bytes X pkt (dropped X, overlimits X requeues X)
# Check DSCP markings on packets leaving your machine
sudo tcpdump -nn -i eth0 -e 'ip[1] & 0xfc != 0'
# Shows all packets where DSCP != 0 (i.e., marked packets)
# Monitor per-class traffic rates
watch -n 1 'tc -s class show dev eth0'
# ss - check for large receive queues (signs of congestion)
ss -tnp | awk 'NR>1 && $2+0 > 0 {print "Recv-Q:", $2, $5}'

Bufferbloat happens when routers/modems have excessively large buffers. Traffic doesn’t get dropped fast enough, so it sits in the buffer causing high latency:

No buffer bloat: 20ms RTT
With bufferbloat: 500ms RTT (buffer filling = 480ms of queued data)

Fix: CoDel (Controlled Delay) or FQ-CoDel manages queues intelligently:

Terminal window
# Replace default qdisc with FQ-CoDel (reduced bufferbloat)
sudo tc qdisc replace dev eth0 root fq_codel
# Or cake (even better, does fairness + bufferbloat management together)
sudo tc qdisc replace dev eth0 root cake bandwidth 100mbit