Build a Deficit Round Robin Queue with tc
Attach tc's Deficit Round Robin (DRR) scheduler to an interface and it has no idea which packet belongs where. tc-drr(8) only describes the scheduler, not a classifier policy. This guide builds a classful DRR queue with two child classes ready for traffic, plus the checks that show whether packets are actually being classified. The examples use the installed tc from iproute2 6.1.0-1ubuntu6.4, and the filter step is kept deliberately separate.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes, plus time to choose a real traffic classification rule.
- iproute2 and an interface name.
- Root privileges for queue changes.
- A maintenance window if the interface carries live traffic. DRR is a packet scheduling change: attaching it to a production device can alter latency and packet loss immediately.
1. Check the installed implementation
First confirm the package and command. These are read-only checks and do not need elevated privileges:
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ dpkg-query -W -f='\${Package} \${Version}\n' iproute2
iproute2 6.1.0-1ubuntu6.4
The manpage's syntax is tc qdisc ... add drr [ quantum bytes ]. Its important default is the interface MTU: unless you provide quantum, each class starts with one MTU's worth of deficit. The minimum explicit quantum is 1 byte. A quantum is not a rate limit and does not reserve bandwidth by itself.
Checkpoint
Write down the interface you will change. Substitute it for IFACE below; do not paste that placeholder literally.
2. Inspect the existing root queue
Look before changing anything:
$ tc qdisc show dev IFACE
You may see a device-specific root qdisc, or a line such as qdisc noqueue 0: root. Record the output. The examples below install a new root qdisc, so any existing root configuration needs a deliberate replacement plan.
There is no harmless dry run for a complete qdisc replacement. If this is a live interface, save the current output and arrange a rollback before continuing.
3. Attach DRR as the root qdisc
This privileged command uses the interface MTU as the quantum:
# tc qdisc add dev IFACE handle 1: root drr
An explicit quantum is also valid. This example makes the scheduling unit visible, but the value must suit the traffic and interface:
# tc qdisc add dev IFACE handle 1: root drr quantum 1500
Use one of those commands, not both. If the command succeeds it normally prints nothing. Verify the qdisc:
# tc qdisc show dev IFACE
qdisc drr 1: root
The displayed line can include additional counters or options.
Recovery
If you get RTNETLINK answers: File exists, you already have a root qdisc; stop rather than deleting it blindly.
4. Add child classes
DRR has no built-in queues and no default class. Add at least two classes beneath handle 1::
# tc class add dev IFACE parent 1: classid 1:1 drr
# tc class add dev IFACE parent 1: classid 1:2 drr
These classes are scheduler entries. They do not yet tell tc which packets belong to either entry. A child qdisc can be attached below a class when you need a particular queueing policy, but the installed manual's central warning still applies: DRR is a pure scheduler and does not itself delay packets.
Check the result before writing a filter:
# tc class show dev IFACE
Checkpoint
You should be able to see both 1:1 and 1:2. If either is missing, fix the class layout before troubleshooting classification.
5. Add a filter policy for the classes
Classification is the part that depends on your network policy. The manual shows a filter assigning packets to a class, but its example deliberately uses protocol and classifier placeholders. Choose a real classifier and verify its own tc filter documentation before applying it.
Warning
Do not assume that unclassified traffic will use a default class. DRR has no default class: a packet that cannot be classified is dropped. That makes a missing filter a packet-loss fault, not merely an unfair scheduling result.
After installing a policy, inspect exactly what is active:
# tc filter show dev IFACE parent 1:
Then generate only the test traffic you intended and inspect statistics:
# tc -s class show dev IFACE
# tc -s qdisc show dev IFACE
Look for packets and bytes increasing on the expected class. A zero counter can mean that the traffic does not match the filter, the filter is attached at the wrong parent, or no traffic has passed yet.
6. Understand the round-robin behaviour
DRR keeps an active list containing classes whose child qdiscs are non-empty. Each class starts with a deficit counter equal to the quantum. If the packet at the head of a class fits within that counter, it can be dequeued. If it does not fit, DRR adds another quantum and moves to the next active class.
This is why a larger quantum can let a class send more bytes per turn, while a small quantum may require several visits before a larger packet fits. It is not a promise that every class receives an identical byte rate. Packet sizes, offered load, child queue limits and the presence of active classes all affect the result.
The manual specifically warns against attaching a non-work-conserving qdisc such as TBF directly as a DRR child. If the child cannot dequeue, other active-list behaviour can be affected. If you need shaping around a scheduler, place DRR inside a suitable hierarchy such as HTB or HFSC and test the resulting topology.
7. Remove the test configuration safely
Warning
Deleting the root qdisc changes live packet handling and removes its child classes and filters. Do this only when the interface can tolerate the interruption:
# tc qdisc del dev IFACE root
Restore the previous root configuration from the output you recorded in step 2, using its original command or configuration manager. Do not guess a replacement. Verify the final state:
# tc qdisc show dev IFACE
# tc class show dev IFACE
# tc filter show dev IFACE
If the interface is managed by NetworkManager, systemd-networkd or another service, make the persistent configuration change there as well. A manual tc command may disappear at the next link restart.
Done means
- Baseline recorded. The installed iproute2 version and the interface's original qdisc were recorded.
- Quantum decided. One DRR root qdisc was attached with a deliberate quantum, or the MTU default was accepted knowingly.
- Classes visible. At least two child classes are visible below the DRR handle.
- Filter chosen. A real filter policy has been chosen and its active attachment was checked.
- Counters moved. Class and qdisc counters increase for the traffic you intended to test.
- Rollback ready. You know that unclassified packets are dropped and have a tested rollback command.