Build a Safe CBQ Class Hierarchy with tc
This walks through a working tc CBQ hierarchy: a root qdisc, a shaped child class, and the sibling rules for borrowing spare capacity. CBQ is the Class Based Queueing discipline in the locally installed iproute2 6.1.0-1ubuntu6.4 package.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow 20 minutes if you already know tc, longer if you still need to map your interface and link rate. These commands need root privileges because they change kernel traffic-control state, so use a test interface or a maintenance window where you can. Attaching a root qdisc can affect every packet leaving the interface, and deleting an existing one can remove someone else's shaping policy.
1. Set the values before touching the interface
Choose an interface, its actual link bandwidth, and an average packet size. Replace the placeholders in this shell snippet, then check them before continuing:
IFACE=eth0
LINK_RATE=100Mbit
AVG_PACKET=1000
CLASS_RATE=10Mbit
printf 'interface=%s link=%s average-packet=%s class-rate=%s\n' \
"$IFACE" "$LINK_RATE" "$AVG_PACKET" "$CLASS_RATE"
ip link show dev "$IFACE"
bandwidth is not the rate you're giving the class. On a root CBQ qdisc it describes the underlying physical interface, or the parent qdisc, so CBQ can work out idle time. The class's own rate is the actual limit for that class and its children.
Checkpoint
The interface must exist, and LINK_RATE has to describe the path as configured, not a guessed internet speed. Wrong interface name, stop here.
2. Record the current qdisc
Save the current root qdisc in your terminal or change log before making anything:
$ tc qdisc show dev "$IFACE"
That output is your baseline for recovery. An existing root qdisc may already be doing shaping, queueing or something a service depends on. Don't run the add command below over it unless you own that configuration and have an undo plan.
3. Add the CBQ root qdisc
CBQ needs an average packet size and the underlying bandwidth. The handle is optional, but giving it 1: makes the hierarchy's major number obvious:
$ sudo tc qdisc add dev "$IFACE" root handle 1: cbq \
avpkt "$AVG_PACKET" bandwidth "$LINK_RATE"
This creates the CBQ instance. On its own it doesn't impose any class rate, it just supplies the link-sharing machinery. Shaping happens on the classes below it.
Check the kernel accepted the change:
$ tc -s qdisc show dev "$IFACE"
Look for a root qdisc whose kind is cbq and whose handle is 1:. The statistics are counters, so their values depend on traffic already sent.
4. Add a shaped class
Create a child class with a ten megabit rate:
$ sudo tc class add dev "$IFACE" parent 1: classid 1:10 cbq \
allot 1514 rate "$CLASS_RATE" prio 1 avpkt "$AVG_PACKET" \
bandwidth "$LINK_RATE"
- Bounds a round.
allotis how much a class can dequeue in one scheduling round. - Sets the order.
priopicks which class gets tried first, lower numbers win. - Required again.
avpktis required in the class syntax, same as on the root.
The class rate applies to this class and any children, but it only sees packets that classification actually sends to 1:10. A filter, socket priority or another supported CBQ classification method has to direct traffic there, creating the class doesn't move anything automatically.
Checkpoint
Inspect the hierarchy and counters:
$ tc class show dev "$IFACE"
$ tc -s class show dev "$IFACE"
You should see class 1:10. Counters stay at zero until traffic is actually classified into it, that's not evidence the rate is being ignored.
5. Add more classes only when their policy is clear
Sibling classes share the parent's scheduling rounds. Their weight values are compared as ratios, while prio controls which priority group is tried first. A second class might look like this:
$ sudo tc class add dev "$IFACE" parent 1: classid 1:20 cbq \
allot 1514 rate 20Mbit prio 2 avpkt "$AVG_PACKET" \
bandwidth "$LINK_RATE"
- Stops borrowing. Use
boundedwhen a class must never borrow unused capacity from siblings. - Stops lending. Use
isolatedwhen it must never lend unused capacity to siblings.
Neither flag repairs a wrong bandwidth value or classifies packets, they only affect sharing. Keep the first test small: CBQ's idle-time calculation depends on packet timing and the physical link's behaviour, so a virtual, tunnelled or poorly behaved device can be far less predictable than a straightforward physical link.
6. Monitor without changing the policy
Watch the installed hierarchy and its traffic counters while generating a known workload:
$ watch -n 1 'tc -s qdisc show dev eth0; tc -s class show dev eth0'
Swap eth0 for the value of IFACE. Confirm the intended class's byte and packet counters climb. If they don't, chase classification before you tune CBQ parameters, the most common distraction is fiddling with minburst, maxburst or priorities when no packets are reaching the class at all.
CBQ's ewma setting smooths the measured idle time. Its default log value is 5, and the documented range is 0 to 31; lower values are more sensitive. Leave it at the default for a first test. Burst parameters change timing trade-offs, not who owns the packets, and should be measured against the workload rather than copied blindly.
7. Remove the test configuration
Warning
Removing a root qdisc removes its child classes too and may briefly change the interface's queueing behaviour. Confirm the interface one more time before running this:
$ printf 'About to remove the root qdisc from %s\n' "$IFACE"
$ sudo tc qdisc del dev "$IFACE" root
$ tc qdisc show dev "$IFACE"
Recovery
To bring back a previous policy, reapply the configuration recorded in step 2 through the owning service's documented method. tc qdisc show output is evidence of the old state, not a complete configuration file, don't assume it captured every filter or child-qdisc setting.
Done means
- Checked before configuring. The interface and underlying bandwidth were confirmed before any change.
- Root qdisc confirmed. It shows as
cbqwith the expected handle. - At least one class shaped properly. It has the intended rate, packet size, priority and parent.
- Classification checked separately. Traffic counters were checked, with classification errors kept apart from shaping errors.
- Cleaned up or documented. The test qdisc was removed, or its owner has a recorded configuration to restore.