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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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 ...
- This check is read-only. Printing the current qdisc needs no elevated privileges.
- Verify the name first. Confirm
eth0withip linkor your normal network inventory before you swap in a real interface name. - Bandwidth here means the link, not the class. The value you give CBQ as
bandwidthdescribes the underlying interface or parent, not the rate you want a child class to get.
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 ...
- Caps the class.
rateis the maximum for this class and its children. - Sets the order.
priochooses precedence: lower numbers get served first. - Bounds a round.
allotis how much a class can dequeue in one round. The manpage says it's silently raised to at least one and a half timesavpktwhen needed.
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.
- Controls burst tolerance.
maxburstsets how many average packets CBQ can send when it's accumulated idle time. Higher tolerates bigger bursts. - Controls overlimit sleep.
minburstchanges how CBQ sleeps and releases packets when overlimit. Larger values improve long-term shaping but make millisecond-scale bursts bigger.
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
- Right interface, right assumption. You recorded the correct interface and the underlying bandwidth figure.
- Root qdisc confirmed.
tc -s qdisc showdisplays the expected CBQ root and handle. - Class configured with intent. The class has a deliberate rate, priority and packet-size estimate.
- Traffic counters checked. They prove whether packets actually reached the class.
- Cleaned up or documented. You either removed the test hierarchy or recorded the exact restoration command.