Shape a Link with a CBQ Hierarchy Using tc

tc-cbq builds a small Class Based Queueing hierarchy you can inspect, test and tear down cleanly. CBQ is an older classful discipline: it shapes classes and shares spare capacity, but its timing isn't exact at short timescales.

Allow about fifteen minutes, plus time to confirm the interface's real capacity. You'll need a root shell or sudo, the tc command from iproute2 6.1.0, installed here as package version 6.1.0-1ubuntu6.4, and an interface you're allowed to reconfigure. This changes live packet scheduling: don't run the add commands on a production interface without a maintenance window and a recovery path.

1. Identify the interface and its real link rate

Pick an interface and write down the physical or parent link rate before doing anything else:

$ IFACE='eth0'
$ tc qdisc show dev "$IFACE"
qdisc ...

Checkpoint: Stop if the interface is a bridge, tunnel, PPP link or anything else whose real bottleneck isn't obvious. CBQ tolerates large bandwidth errors at the cost of coarser shaping, per the manpage, but that's not licence to guess: measure it or document the assumption first.

2. Add the CBQ root qdisc

This needs elevated privileges. The root qdisc controls the whole interface, so adding it can alter scheduling immediately, save the step 1 output first in case you need to restore a non-default qdisc:

$ sudo tc qdisc add dev "$IFACE" root handle 1: cbq bandwidth 100Mbit avpkt 1000
$ sudo tc -s qdisc show dev "$IFACE"
qdisc cbq 1: root ...

bandwidth is mandatory here because CBQ's idle-time calculations need the underlying rate, and avpkt is mandatory too, supplying the average packet size those calculations use. The handle 1: gives the qdisc a major number its child classes can share.

If the add command says a qdisc already exists, don't cycle through variants hoping one sticks. Inspect what's there first, replacing it can disrupt traffic and discard a carefully chosen policy.

3. Add one shaped class

Create a child class with a 10 Mbit/s ceiling under the 100 Mbit/s root:

$ sudo tc class add dev "$IFACE" parent 1: classid 1:10 cbq \
    allot 1514 avpkt 1000 bandwidth 100Mbit rate 10Mbit prio 5
$ sudo tc -s class show dev "$IFACE"
class cbq 1:10 ... rate 10Mbit ...

Creating a class doesn't prove your traffic will land in it. Classification is a separate part of the CBQ tree, handled by filters or by CBQ's priority and defmap mechanisms described in tc-cbq(8). If nothing sends a packet to a child, CBQ just enqueues it at the node it reached. Check the counters while testing rather than assuming a configured rate is actually being used.

4. Choose burst behaviour deliberately

For a first test, leave maxburst and minburst alone unless you have a measured reason to tune them. If you set either, the class also needs a bandwidth value, as above.

Tweaking both because a short-term graph looks uneven is a classic distraction, it can make the short-term behaviour worse, not better. CBQ uses an exponentially weighted moving average, so its timing is sensitive to recent traffic, and the manpage warns that ordinary kernel timing can make shaping imprecise over a few seconds even when the long-term rate is close. Treat the result as a measured policy, not a hard real-time guarantee.

5. Verify counters and the live hierarchy

Check the structure and stats while the relevant traffic is actually running:

$ sudo tc -s qdisc show dev "$IFACE"
$ sudo tc -s class show dev "$IFACE"
class cbq 1:10 ... Sent ... bytes ...

Counters and detail vary by kernel and traffic. What you're checking for: the CBQ qdisc and class exist, and the class counters move when traffic has actually been classified there. If they sit at zero, chase the classification path before you touch rates.

6. Remove the test configuration

Warning: Deleting the root qdisc is a live network change. It removes the CBQ hierarchy, the child class with it, and returns the interface to the kernel's default queueing. Only do this when you're ready for the transition:

$ sudo tc qdisc del dev "$IFACE" root
$ tc qdisc show dev "$IFACE"
qdisc noqueue 0: root ...

Recovery: That last line is only an example of what a simple idle interface might show, check your own output. If the interface had a previous non-default qdisc, restore that policy from your change record rather than guessing. tc doesn't provide an automatic undo for an earlier configuration.

Done means