Classify DSCP traffic with tcindex without losing the rollback path
You will attach the Linux tcindex filter to a classful queue, map DSCP 46 (Expedited Forwarding) to one class, and verify the filter before removing it again. The examples use the iproute2 package installed on this machine, version 6.1.0-1ubuntu6.4, with tc reporting iproute2 6.1.0. Allow about fifteen minutes, plus time to identify the interface and queue policy you actually want to change.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is a privileged, live network change. Run the commands as root or through sudo only when shown. Replacing a root qdisc can affect traffic immediately, so test on a maintenance interface or a disposable host first. Keep the original queue configuration available before changing a production interface.
1. Check the filter syntax and choose an interface
First confirm that the installed command includes the filter type. This read-only check needs no elevated privilege:
$ tc filter add dev lo ingress tcindex help
Usage: ... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ]
[ pass_on | fall_through ]
[ classid CLASSID ] [ action ACTION_SPEC ]
Replace IFACE below with the interface whose egress queue you intend to control. Do not copy lo into a production command just because it appears in the syntax check. Inspect the current state before doing anything privileged:
$ ip link show dev IFACE
$ tc qdisc show dev IFACE
$ tc filter show dev IFACE
Checkpoint: record the current qdisc and filters. If the interface already has a carefully managed root qdisc, stop and add the filter through that existing policy rather than installing the lab queue in the next step.
2. Create a small classful test queue
The filter needs a class to receive matching packets. On a disposable test interface, create an HTB root qdisc with two classes:
# tc qdisc add dev IFACE root handle 1: htb default 20
# tc class add dev IFACE parent 1: classid 1:10 htb rate 10mbit
# tc class add dev IFACE parent 1: classid 1:20 htb rate 1gbit
These commands change the interface and require elevated privileges. The first command makes class 1:20 the default. The second creates the class used by the example, and the third gives ordinary traffic a separate destination. The rates are only test values: choose values that match your link and policy rather than treating them as safe defaults.
Verify the queue before adding the classifier:
# tc qdisc show dev IFACE
# tc class show dev IFACE
qdisc htb 1: root refcnt 2 r2q 10 default 20
class htb 1:10 ...
class htb 1:20 ...
The counters, reference counts and rate formatting vary. The useful checkpoint is that the root handle is 1: and both classes exist. If the first command fails because a root qdisc is already present, do not force it with replace; use the existing qdisc or undo this test setup before trying another design.
3. Map DSCP 46 to class 1:10
The tcindex field is the combined DSCP and ECN value from an IPv4 or IPv6 header. DSCP occupies the upper six bits, so the example masks with 0xfc and shifts right by two. The generic filter handle is then 46, the resulting DSCP value:
# tc filter add dev IFACE parent 1: protocol ip pref 10 handle 46 \
tcindex mask 0xfc shift 2 classid 1:10 pass_on
protocol ip limits this example to IPv4. Add a separately reviewed IPv6 rule with protocol ipv6 if your policy covers IPv6 as well. The classid sends a matching result to class 1:10. pass_on means that a packet for which the filter cannot find a class is left for the next filter. The alternative, fall_through, is the default and classifies even when no class is present for the resulting ID. Choose deliberately: a fallback that silently catches traffic can hide a missing class.
Do not confuse handle 46 with a packet mark or a command-line priority. It is the tcindex value after the mask and shift. ECN bits are discarded by the mask, so packets with the same DSCP but different ECN values use the same key.
4. Verify the installed filter
Read the live filter state and check that the rule has the expected parent, protocol, priority and class:
# tc filter show dev IFACE parent 1:
# tc filter show dev IFACE parent 1: | grep tcindex
filter parent 1: protocol ip pref 10 ... tcindex ... classid 1:10
The exact display includes version-dependent fields and may show the handle in hexadecimal. Confirm the values rather than matching the whole line: protocol ip, preference 10, the tcindex handle corresponding to 46, and destination class 1:10. The filter display proves that the rule is installed; it does not prove that your application is sending DSCP 46.
For traffic testing, use a packet generator or application that you already trust, and capture on the correct interface. Queue statistics give an additional read-only check:
# tc -s class show dev IFACE
class htb 1:10 ...
Sent ... bytes ... pkt
Only counters that increase while matching traffic is actually present are evidence that packets reached the class. Do not generate test traffic against a third party without authorisation.
5. Remove the test change safely
Delete the filter first, then remove the test qdisc and its classes:
# tc filter del dev IFACE parent 1: protocol ip pref 10
# tc qdisc del dev IFACE root
The second command removes the root qdisc and the classes beneath it. It is destructive to the test queue and can briefly change how traffic is scheduled. Do not run it on an interface whose original root qdisc was not the HTB queue from step 2. If you replaced an existing policy, restore that policy using the configuration and commands recorded before the change; there is no universal undo command that can reconstruct it.
Check that the temporary state is gone:
# tc filter show dev IFACE
# tc qdisc show dev IFACE
An empty filter listing is expected for the removed rule. The qdisc output should now reflect the interface's intended policy, not necessarily an empty result.
6. Avoid the common failure modes
- A filter that never matches often has the wrong protocol, DSCP key or interface direction. The documented filter matches the tcindex value after masking and shifting; it does not inspect an application label.
- Using
mask 0xff shift 0includes ECN in the key. That can split one DSCP policy across four values, so use it only when that distinction is intentional. pass_onandfall_throughchange what happens when no class is available. A successfultc filter adddoes not validate the policy for every possible tcindex value.- An IPv4 rule does not automatically cover IPv6. Install and verify an IPv6 rule separately if it belongs in scope.
Done means
- The installed
tcversion and target interface were checked first. - The mask, shift, tcindex handle, protocol and destination class match the written policy.
tc filter showconfirms the live rule, and class counters were checked with matching traffic.- The test filter and qdisc were removed, or the original production policy was restored from a recorded configuration.