Computer Networks Module 18 – Data centres, cloud and the full picture
QoS, queues and traffic shaping
How DiffServ marks packets with a DSCP, why queues form at the slowest link, how a token bucket polices or shapes traffic, and how schedulers share a link.
What you will learn
- Explain DSCP marking, per-hop behaviours and the DiffServ model
- Compare policing and shaping and model both with a token bucket
- Compare FIFO, priority, round-robin and weighted fair queueing schedulers
- Measure how a queue manager such as CoDel or FQ-CoDel changes the delay a call sees during an upload
Before you start
What you’ll learn · Before you start
What you will learn
- Explain DSCP marking, per-hop behaviours and the DiffServ model
- Compare policing and shaping and model both with a token bucket
- Compare FIFO, priority, round-robin and weighted fair queueing schedulers
- Measure how a queue manager such as CoDel or FQ-CoDel changes the delay a call sees during an upload
Before you start
On this page
Quality of service (QoS) means giving some traffic better treatment than other traffic where they compete for the same link. It matters only where packets wait, so QoS is about queues: they form at the slowest link of a path, such as a home uplink or a busy office circuit. Three tools do the work. Marking labels each packet with a class in a 6-bit field of the IP header, the DSCP of the Differentiated Services (DiffServ) architecture. Metering with a token bucket checks a class against its rate: policing drops or re-marks the excess, and traffic shaping delays it until it fits. Scheduling decides which of several queues sends the next packet. This lesson shows where queues form, what the marks mean, runs one token bucket in both of its modes, and names the schedulers and queue managers that keep a call clear while a large upload runs.
Where queues form
A router can send only as fast as its output link. When packets arrive faster, they wait in a buffer, and the delay is simply the bytes waiting divided by the link’s rate. A home router with a 20 Mbit/s uplink and room for 500 full-size packets holds 500 × 1,500 = 750,000 bytes, or 6 megabits: a packet at the back waits 6 ÷ 20 = 0.3 seconds. A call’s packet stuck behind an upload’s full queue arrives a third of a second late, although the call needs a tiny share of the link.
Queues form only where traffic slows down. Gigabit switches in front of that uplink rarely queue at all, so a QoS policy on them changes nothing; the policy must sit where the queue builds. Buffers that are large and stay full are called bufferbloat, and the cure is to keep queues short rather than to make them longer.
Marking: one byte in the IP header
The QoS tools at one output port
Text description of the diagram
The diagram is a chain of steps, top to bottom, at the output port of a router or switch where packets arrive faster than the link can send them.
- Classify and mark: each packet is put in a class and its DSCP is set, for example EF for calls, AF41 for video and DF for everything else.
- Meter: a token bucket checks each class against its rate; policing drops or re-marks the packets that exceed it.
- Queue: each class waits in a queue of its own, here three queues for EF, AF41 and DF.
- Schedule: the scheduler picks which queue sends the next packet.
- Send: packets leave at the rate of the slowest link, or at a lower rate if a shaper holds them back.
DiffServ redefines the byte that IPv4 calls the TOS octet and IPv6 the Traffic Class: its first six bits carry the DSCP (RFC 2474) and the last two the ECN field, which routers use to signal congestion without dropping (RFC 3168). A DSCP selects a per-hop behaviour (PHB), the treatment each router gives the packet:
- Default forwarding (DF), DSCP 0, is ordinary best effort.
- Expedited Forwarding (EF), DSCP 46, asks for low delay and low loss, such as for voice (RFC 3246).
- Assured Forwarding has four classes with three drop levels each, AF11 to AF43 (RFC 2597).
- Class Selectors CS0 to CS7 keep the old IP precedence values working.
- Lower Effort (LE), DSCP 1, is for traffic that should only use spare capacity (RFC 8622).
A mark is a request, not a guarantee. Each network decides whether to trust it, and many re-mark what they do not recognise to zero at their border (RFC 8100).
One token bucket, two jobs
A token bucket earns tokens at a rate r, holds at most b of them, and lets a packet pass when it can pay its size in tokens. Policing drops or re-marks a packet that finds too few tokens, so it adds no delay but loses data. Traffic shaping holds such a packet in a queue until the tokens arrive, so it loses nothing but adds delay. The program sends bursts of 30 packets every 100 ms, 3.6 Mbit/s on average, against a contract of 8 Mbit/s:
"""One token bucket, two jobs: policing drops the excess, shaping delays it.
A video encoder sends a burst of 30 packets of 1,500 bytes every 100 ms, back to back at
1 Gbit/s. On average that is 3.6 Mbit/s. The contract allows 8 Mbit/s with a bucket of
15,000 bytes (10 packets). Tokens are bytes; times are whole microseconds, so the
arithmetic is exact.
"""
PACKET = 1_500
RATE = 1_000_000 # 8 Mbit/s = 1,000,000 bytes a second, so one byte of credit per microsecond
BURSTS, PER_BURST, GAP_US = 5, 30, 100_000
WIRE_US = 12 # one 1,500-byte packet at 1 Gbit/s
def arrivals():
return [b * GAP_US + i * WIRE_US for b in range(BURSTS) for i in range(PER_BURST)]
def police(times, depth):
"""Send a packet if the bucket holds enough tokens, else drop it."""
tokens, last, sent, dropped = depth, 0, [], 0
for t in times:
tokens = min(depth, tokens + (t - last) * RATE // 1_000_000)
last = t
if tokens >= PACKET:
tokens -= PACKET
sent.append(t)
else:
dropped += 1
return sent, dropped
def shape(times, depth):
"""Hold each packet in a queue until the bucket holds enough tokens: nothing is dropped."""
tokens, last, out = depth, 0, []
for t in times:
t = max(t, out[-1] if out else 0) # packets leave in order
tokens = min(depth, tokens + (t - last) * RATE // 1_000_000)
last = t
if tokens < PACKET: # wait for the missing tokens
wait = -(-(PACKET - tokens) * 1_000_000 // RATE)
t, tokens, last = t + wait, tokens + wait * RATE // 1_000_000, t + wait
tokens -= PACKET
out.append(t)
return out
def mbit(nbytes, seconds):
return nbytes * 8 / seconds / 1e6
times = arrivals()
span = BURSTS * GAP_US / 1e6
print(f"offered: {len(times)} packets, {mbit(len(times) * PACKET, span):.1f} Mbit/s on average, "
f"in bursts of {PER_BURST} at 1 Gbit/s")
for depth in (15_000, 45_000):
sent, dropped = police(times, depth)
out = shape(times, depth)
delays = [o - t for o, t in zip(out, times)]
print(f"bucket {depth:,} bytes ({depth // PACKET} packets), rate 8 Mbit/s:")
print(f" policed: {len(sent)} sent, {dropped} dropped ({dropped / len(times):.0%}), "
f"{mbit(len(sent) * PACKET, span):.1f} Mbit/s delivered")
print(f" shaped: {len(out)} sent, 0 dropped, largest delay {max(delays) / 1000:.1f} ms, "
f"last packet of a burst leaves {(out[PER_BURST - 1] - times[0]) / 1000:.1f} ms after the first arrived") Output
offered: 150 packets, 3.6 Mbit/s on average, in bursts of 30 at 1 Gbit/s bucket 15,000 bytes (10 packets), rate 8 Mbit/s: policed: 50 sent, 100 dropped (67%), 1.2 Mbit/s delivered shaped: 150 sent, 0 dropped, largest delay 29.7 ms, last packet of a burst leaves 30.0 ms after the first arrived bucket 45,000 bytes (30 packets), rate 8 Mbit/s: policed: 150 sent, 0 dropped (0%), 3.6 Mbit/s delivered shaped: 150 sent, 0 dropped, largest delay 0.0 ms, last packet of a burst leaves 0.3 ms after the first arrived
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 token_bucket.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
With a 15,000-byte bucket, the policer dropped 67 % of a stream whose average was under half the contracted rate: each burst found only ten packets’ worth of tokens. Traffic shaping delivered every packet, the last one of each burst 29.7 ms late. A bucket as large as one burst let both pass everything at once. Size the bucket for the burst, not only the rate.
Send requests one at a time or in a burst, let time pass, and change the bucket size and the refill rate. A request needs one token; without one it is rejected with 429.
At the start: The bucket holds 5 of 5 tokens and gains 1 token a second, so up to 5 requests pass at once and 1 a second keep passing after that. No requests yet.
Exercise · Medium · Python
Police and shape traffic with one token bucket
Write token_bucket(arrivals, rate, depth, mode) in bucket.py. arrivals is a list of (time_us, size) pairs in time order: when a packet arrives, in microseconds, and its size in bytes. The bucket earns rate bytes of tokens a second, holds at most depth bytes and starts full.
mode="police": return a list with one value per packet:Truewhen the packet is sent (the bucket holds at least its size in tokens, which it spends) andFalsewhen it is dropped (it spends nothing).mode="shape": return the time in microseconds at which each packet leaves. A packet waits until every packet before it has left and the bucket holds enough tokens; nothing is dropped. Round a waiting time up to the next whole microsecond. A packet larger thandepthcould never leave: raiseValueError.
Tokens accrue continuously: after t microseconds the bucket has gained t * rate / 1_000_000 bytes, never beyond depth. The starter keeps tokens multiplied by 1,000,000 so that the arithmetic stays exact with whole numbers.
With rate=1_000_000 (8 Mbit/s) and depth=3_000, three 1,500-byte packets that arrive together at time 0 are policed to [True, True, False] and shaped to [0, 0, 1500]. The five sample tests check both modes.
Starter code · bucket.py
def token_bucket(arrivals, rate, depth, mode):
"""Police (a list of True/False) or shape (departure times) packets with one token bucket.
arrivals: (time_us, size_bytes) pairs in time order; rate: bytes a second; depth: bytes.
"""
scale = 1_000_000 # tokens are kept in micro-bytes: whole numbers, exact arithmetic
tokens = depth * scale # the bucket starts full
last = 0
out = []
for time_us, size in arrivals:
# Replace these lines: add the tokens earned since `last`, then send, drop or delay the packet.
out.append(True if mode == "police" else time_us)
return out The sample tests · test_bucket.py
from bucket import token_bucket
R = 1_000_000 # bytes a second: one byte of tokens per microsecond
def test_police_a_burst():
"""sends what the full bucket covers and drops the rest of a burst"""
burst = [(0, 1_500)] * 5
assert token_bucket(burst, R, 3_000, "police") == [True, True, False, False, False]
assert token_bucket(burst, R, 7_500, "police") == [True] * 5
def test_police_refills_over_time():
"""earns rate bytes a second, never more than the depth"""
pkts = [(0, 1_500), (0, 1_500), (1_499, 1_500), (1_500, 1_500), (100_000, 1_500), (100_000, 1_500), (100_000, 1_500)]
assert token_bucket(pkts, R, 3_000, "police") == [True, True, False, True, True, True, False]
def test_police_spends_nothing_on_a_drop():
"""a dropped packet leaves the tokens for the next one"""
pkts = [(0, 1_000), (0, 1_500), (0, 1_000)]
assert token_bucket(pkts, R, 2_000, "police") == [True, False, True]
def test_shape_delays_instead_of_dropping():
"""holds packets until the tokens are there, in order, rounding waits up"""
burst = [(0, 1_500)] * 4
assert token_bucket(burst, R, 3_000, "shape") == [0, 0, 1_500, 3_000]
slow = [(0, 1_000), (0, 1_000), (10, 1_000)]
assert token_bucket(slow, 3_000, 1_000, "shape") == [0, 333_334, 666_668]
small_behind_big = [(0, 1_500), (0, 1_500), (100, 100)] # the small packet must wait its turn
assert token_bucket(small_behind_big, R, 1_500, "shape") == [0, 1_500, 1_600]
def test_shape_conforming_traffic_and_oversized_packets():
"""does not delay traffic within the rate, and refuses a packet larger than the bucket"""
steady = [(i * 2_000, 1_500) for i in range(5)]
assert token_bucket(steady, R, 1_500, "shape") == [0, 2_000, 4_000, 6_000, 8_000]
try:
token_bucket([(0, 9_000)], R, 1_500, "shape")
except ValueError:
pass
else:
raise AssertionError("a 9,000-byte packet can never fit a 1,500-byte bucket") A hint
Keep two variables: the tokens in micro-bytes and the time you last updated them. For each packet, first add (now - last) * rate micro-bytes, capped at depth * 1_000_000. In shape mode, now is the later of the arrival and the previous departure; if the tokens are short, the wait is the missing micro-bytes divided by rate, rounded up with -(-a // b).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Schedulers and queue managers, by name
- FIFO keeps one queue and sends packets in the order they arrived.
- Strict priority always empties the higher-priority queues first.
- Round robin lets the queues take turns; deficit round robin (DRR) counts bytes, so large packets earn no extra share.
- Weighted fair queueing (WFQ) sends packets in the order a bit-by-bit weighted share of the link would finish them.
- RED drops packets at random as the average queue length grows, before the buffer is full.
- CoDel drops when packets have waited longer than a target time for a while.
- FQ-CoDel gives each flow a queue of its own, served by DRR, and runs CoDel in each (RFC 8290).
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Four schedulers on one link, run and measured Pro: opens with a Pro, Premium or Ultimate pass
A scheduler decides who waits. This part runs FIFO, strict priority and deficit round robin on the same link with a call, a video stream and an upload, then breaks priority on purpose with a misbehaving source.
In this part:
- Three flows on a 10 Mbit/s link, run four ways
- Starvation reproduced, and the two ways out
- How fair sharing is computed, with worked numbers
1 runnable example (Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Marks that survive the path: DSCP plans, trust and Wi-Fi Pro: opens with a Pro, Premium or Ultimate pass
A mark helps only where a device trusts it and maps it to the right queue. This part lists the classes the standards recommend, shows the byte an application sets, what Wi-Fi does with the mark, and how a border’s re-marking can erase a congestion signal.
In this part:
- A DSCP plan from RFC 4594, and the byte an application sets
- Wi-Fi turns six bits into eight priorities, sometimes wrongly
- Trust boundaries, and what a border does to marks
- Sizing the priority class for calls, with worked numbers
1 runnable example (Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Owning the queue: shaping below the bottleneck and AQM, measured Pro: opens with a Pro, Premium or Ultimate pass
Marks and schedulers work only where your device holds the queue. This part reproduces bufferbloat with an upload and a call, shows why shaping a little below the slowest link moves the queue to a device you control, and how CoDel and FQ-CoDel keep it short.
In this part:
- Bufferbloat reproduced: an upload and a call on 20 Mbit/s
- Why shaping to 95 % moves the queue, with worked numbers
- How CoDel decides when to drop
1 runnable example (Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
What is the difference between traffic policing and traffic shaping?
Both measure traffic against a rate with a token bucket, which earns tokens at the agreed rate and holds at most a set burst. They differ in what happens to a packet that finds too few tokens. A policer drops it, or re-marks it to a lower class, at once: it adds no delay, but bursts larger than the bucket are lost, and TCP reacts to those losses by slowing down. A shaper holds the packet in a queue until enough tokens have arrived: nothing is lost while its queue has room, but packets wait, and the output leaves smoothly at the agreed rate. Providers often police at their edge to enforce a contract, and customers shape slightly below that rate on their side, so that the provider's policer has nothing to drop.
-
Fresher to mid
A home office has a connection of 100 Mbit/s down and 20 Mbit/s up. Video calls break up whenever a large file is uploading, although a call needs about 3 Mbit/s. Explain why, and say what you would change on the router.
-
Mid to senior
After video conferencing was given a strict-priority queue on a 50 Mbit/s branch link, nightly backups and some daytime file transfers began to fail during company-wide meetings. Why, and how would you configure the link instead?
-
Mid to senior
Calls between two offices are marked EF on the LAN, but captures at the far office show DSCP 0, and the calls are choppy only when the WAN link is busy. Where can the marks be lost, and what would you do?
Key takeaways
- QoS only acts where a queue forms, usually at the slowest link; the delay there is the bytes waiting divided by the link’s rate.
- DiffServ marks packets with a 6-bit DSCP: DF 0 for best effort, EF 46 for low delay, AF11 to AF43 for assured classes, LE 1 for spare capacity. Each network decides whether to trust a mark.
- A token bucket with rate r and depth b polices by dropping or re-marking the excess and shapes by delaying it; a bucket smaller than the bursts drops traffic even when the average rate is low.
- Schedulers decide who waits: FIFO, strict priority, round robin and DRR, and WFQ; queue managers such as RED, CoDel and FQ-CoDel keep the queue itself short.
References
- RFC 2474: Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers (RFC Editor (IETF))
- RFC 2475: An Architecture for Differentiated Services (RFC Editor (IETF))
- RFC 3246: An Expedited Forwarding PHB (Per-Hop Behavior) (RFC Editor (IETF))
- RFC 2597: Assured Forwarding PHB Group (RFC Editor (IETF))
- RFC 4594: Configuration Guidelines for DiffServ Service Classes (RFC Editor (IETF))
- RFC 8622: A Lower-Effort Per-Hop Behavior (LE PHB) for Differentiated Services (RFC Editor (IETF))
- RFC 8325: Mapping Diffserv to IEEE 802.11 (RFC Editor (IETF))
- RFC 8100: Diffserv-Interconnection Classes and Practice (RFC Editor (IETF))
- RFC 3168: The Addition of Explicit Congestion Notification (ECN) to IP (RFC Editor (IETF))
- RFC 8289: Controlled Delay Active Queue Management (RFC Editor (IETF))
- RFC 8290: The Flow Queue CoDel Packet Scheduler and Active Queue Management Algorithm (RFC Editor (IETF))
- RFC 7567: IETF Recommendations Regarding Active Queue Management (RFC Editor (IETF))
- RFC 3550: RTP, A Transport Protocol for Real-Time Applications (RFC Editor (IETF))
- tc(8), show and change traffic control settings (Debian manual pages (iproute2))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress