In Practice: Bucket is a Finite Queue that Outputs at a Finite Rate
The theoretical leaky bucket is clean: infinite bucket, constant drain. In reality, every implementation uses a finite queue (bounded buffer) and a finite, configurable drain rate. These two constraints define the practical behavior — and the failure modes.
The Finite Queue (Bounded Buffer)
The "bucket" in practice is a FIFO queue with a maximum depth — measured in packets or bytes:
Queue depth B: maximum packets (or bytes) the buffer holds
Drain rate R: packets per second output to the network
Arrival rate A: incoming packet rate (variable)
If A <= R: queue stays empty or small, no drops
If A > R: queue fills at rate (A - R)
If queue full AND A > R: OVERFLOW — incoming packets are dropped
The finite queue means burst absorption is limited. If a burst exceeds the bucket depth, packets are lost. This is by design: the algorithm enforces a contract.
Overflow Behavior: Drop vs Mark
When the finite queue is full, two options:
Drop (Tail Drop)
The simplest — incoming packets that arrive at a full queue are discarded silently. The sender has no signal other than eventual timeout or NACK.
# Tail drop is the default in Linux tc qdisc
# View drops with
ip -s link show eth0
# TX: bytes packets errors dropped overrun mcast
Active Queue Management (AQM) — Mark Instead of Drop
Modern implementations use AQM like RED (Random Early Detection) or CoDel to:
- Start randomly dropping (or ECN-marking) packets before the queue is full
- Signal congestion early to TCP senders so they reduce cwnd before a full overflow
- Prevent "synchronization" where all TCP flows back off simultaneously
# Use CoDel (Controlled Delay) AQM instead of tail-drop
sudo tc qdisc add dev eth0 root codel
# Use FQ-CoDel (Fair Queue CoDel) — better for multi-flow environments
sudo tc qdisc add dev eth0 root fq_codel
# Check parameters
tc qdisc show dev eth0
Linux tc Implementation: tbf (Token Bucket Filter)
Linux's primary rate-shaping qdisc is tbf. Despite the name, it implements leaky bucket behavior for shaping (and token bucket internally for the math):
# Add tbf qdisc: rate 10mbit, burst 32kbit, max latency 50ms
sudo tc qdisc add dev eth0 root tbf \
rate 10mbit \
burst 32kbit \
latency 50ms
# Parameters explained:
# rate = drain rate R (desired output rate)
# burst = minimum burst size the kernel can handle (relates to HZ)
# latency = max time a packet waits in queue before being dropped
# (determines effective queue depth: latency * rate)
# Queue depth in bytes = latency * rate
# 50ms * 10Mbit = 0.05s * 1,250,000 B/s = 62,500 bytes
# View stats including drops and overlimits
tc -s qdisc show dev eth0
# Output:
# qdisc tbf 8001: root refcnt 2 rate 10Mbit burst 32Kb lat 50.0ms
# Sent 8192000 bytes 5461 pkt (dropped 1200, overlimits 1200 requeues 0)
# rate 9.84Mbit 8Kpps
# backlog 0b 0p requeues 0
Policing vs Shaping
These are two different uses of the finite queue + finite rate model:
| Shaping | Policing | |
|---|---|---|
| Mechanism | Queue packets, delay them | Drop or remark packets immediately |
| Burst handling | Absorbs into queue | Drops immediately on overflow |
| Adds delay | Yes (queue latency) | No |
| Location | Typically egress | Typically ingress |
| Linux tool | tc tbf, tc htb |
tc police, iptables -m limit |
# SHAPING on egress (delay packets to conform to rate)
sudo tc qdisc add dev eth0 root tbf rate 10mbit burst 32kbit latency 50ms
# POLICING on ingress (drop packets that exceed rate)
sudo tc qdisc add dev eth0 handle ffff: ingress
sudo tc filter add dev eth0 parent ffff: protocol ip u32 match ip src 0.0.0.0/0 \
police rate 10mbit burst 32kbit drop flowid :1
# iptables rate limiting (simpler policing, per-connection)
sudo iptables -A INPUT -p tcp --dport 80 -m limit --limit 100/second --limit-burst 200 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j DROP
Finite Rate and Clock Precision
The output rate is only as accurate as the system scheduler's clock resolution. Linux uses HZ (timer interrupt frequency — typically 250 or 1000 Hz) as the minimum timing granularity:
# Check kernel HZ setting
grep CONFIG_HZ /boot/config-$(uname -r)
# -> CONFIG_HZ=250 means 4ms timer resolution
# At 1Mbit, packet spacing = 1.5ms → timer resolution matters
# Use HFSC or FQ schedulers for sub-HZ accuracy
Netem — Finite Queue with Exact Rate Control
For testing and emulation (not production shaping), netem gives fine-grained control:
# Simulate 1Mbit link with finite queue of 100 packets
sudo tc qdisc add dev eth0 root netem \
rate 1mbit \
limit 100 # queue depth = 100 packets (finite!)
# Add packet loss on overflow behavior (already default with limit)
sudo tc qdisc add dev eth0 root netem \
rate 1mbit \
limit 100 \
loss 0.1% # also add 0.1% random loss to simulate real link
# Show queue stats
tc -s qdisc show dev eth0
Reference
- Linux tc-tbf man page:
man tc-tbf - Linux tc-netem:
man tc-netem - lartc.org — Linux Advanced Routing and Traffic Control HOWTO
- RFC 2697 / 2698 — Three Color Marking (leaky bucket extensions)