Route Traffic into Classes with tc flow
You will finish with a repeatable way to attach the flow traffic-control classifier, either hashing several packet keys across buckets or mapping one key into predictable classes. The examples use iproute2 6.1.0, installed here as package 6.1.0-1ubuntu6.4.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes, plus time to understand the qdisc hierarchy already configured on the host. You need tc, a network interface, and a classful parent qdisc with classes ready to receive packets. Most inspection is unprivileged, but adding, changing or deleting a filter normally requires root. These examples change live traffic classification, so use a test interface or maintenance window where possible.
1. Check the installed syntax
Start with read-only checks. They confirm the binary and the exact classifier grammar without touching a qdisc:
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ tc filter add flow help
Usage: ... flow ...
[mapping mode]: map key KEY [ OPS ] ...
[hashing mode]: hash keys KEY-LIST ... [ perturb SECS ]
[ divisor NUM ] [ baseclass ID ] [ match EMATCH_TREE ]
[ action ACTION_SPEC ]
The command has two modes. map transforms one key into a class ID. hash runs jhash2 over a comma-separated key list and uses the result as a class ID, optionally reduced by divisor. The syntax probe above is a checkpoint: if your installed output differs, follow it rather than copying a command from another release.
2. Inspect the target hierarchy
Choose a real device and parent before constructing a filter. This is ordinary, read-only inspection:
$ ip -br link
$ tc qdisc show dev eth0
$ tc class show dev eth0
$ tc filter show dev eth0 parent 1:
Replace eth0 with the interface that carries the traffic. Replace 1: with the handle of the classful qdisc and confirm that the classes you intend to use are below it. The filter's resulting class ID must make sense in that hierarchy. A flow filter does not create a qdisc or any classes for you.
Checkpoint: record the device, parent handle and class range. If tc class show returns no usable classes, stop here. Adding a classifier before its destination classes exist is a configuration error, not a harmless test.
3. Hash complete flows into buckets
Hashing is useful when you want related packet fields to spread traffic across a fixed number of classes. This example follows the classic SFQ-style five-key set and limits the result to 1024 buckets:
# tc filter add dev eth0 parent 1: protocol ip pref 10 flow hash \
keys src,dst,proto,proto-src,proto-dst divisor 1024
Run the command as root, or through your normal privileged administration method. src and dst are addresses; proto is the layer-four protocol number; and the last two keys are source and destination ports where available. The classifier hashes the list and takes the result modulo 1024. The selected value is then used as a class ID, subject to the class hierarchy and any baseclass offset.
Verify the installed filter:
$ tc filter show dev eth0 parent 1:
filter protocol ip pref 10 flow chain 0
hash keys src,dst,proto,proto-src,proto-dst divisor 1024
The display can include additional fields or differ in spacing. Look for the device, parent, preference, flow, the five keys and divisor 1024. A filter listing proves that the rule was accepted; it does not prove that packets are reaching the intended classes.
Warning
perturb changes the entropy source at the requested interval. A flow can therefore move between class IDs over time. Omit it when stable classification matters, and use it only when that movement is an intentional trade-off.
4. Map a destination address predictably
Mapping is better when a key should lead to a class by a rule you can calculate. For a destination range 192.168.0.0/24, subtract the network address and reduce the result to 256 values:
# tc filter add dev eth0 parent 1: protocol ip pref 20 flow map \
key dst addend -192.168.0.0 divisor 256
The addend operation is applied before the divisor. For source and destination address keys, its numeric argument may be written in IP address notation, and a leading minus sign requests subtraction. The alternative below keeps only the low eight bits and is equivalent for an IPv4 /24 mapping:
# tc filter add dev eth0 parent 1: protocol ip pref 20 flow map \
key dst and 0xff
Do not install both examples with the same preference on the same parent while experimenting. Use one mapping rule, inspect it, and remove it before replacing it with the other.
5. Choose keys with their limits in mind
The key names describe different packet or socket values. iif uses the incoming interface index, mark uses the netfilter firewall mark, priority uses the packet priority, vlan-tag uses the VLAN ID, and rxhash uses the flow hash. sk-uid and sk-gid apply to locally generated packets.
For NAT-aware traffic, use the nfct-* variants when the conntrack values before NAT are what should determine the class:
# tc filter add dev eth0 parent 1: protocol ip pref 30 flow hash \
keys nfct-src,nfct-dst,proto,nfct-proto-src,nfct-proto-dst \
divisor 1024
IPv6 source and destination addresses are folded into a 32-bit value by XORing their four 32-bit words. That is a practical detail with a collision consequence: different IPv6 addresses can produce the same folded key. The filter is not a full policy language, and it does not provide a guarantee of one class per address.
6. Remove a test rule and recover
Deleting a filter is a live configuration change. First list the rules and identify the exact preference and protocol. Then remove the rule using the same selector:
$ tc filter show dev eth0 parent 1:
# tc filter del dev eth0 parent 1: protocol ip pref 20
$ tc filter show dev eth0 parent 1:
If the deletion reports that no matching filter exists, re-check the parent, protocol and preference. If traffic classification becomes wrong after an addition, remove the new preference immediately and restore the previously documented rule. Keep a transcript of the original tc qdisc show, tc class show and filter output before making changes. Do not delete the parent qdisc as a shortcut: that can remove its classes and disrupt every flow using it.
Common failure points
- A missing or non-classful parent handle means there is nowhere useful for the resulting class ID to go.
- A typo in a key list or operation is rejected by
tc; compare it withtc filter add flow help. - A hash result is not predictable in advance, especially when
perturbis enabled. Use mapping when reproducibility is the requirement. - A successful add only confirms that the kernel accepted the filter. Check counters with the relevant qdisc and filter statistics, then test with traffic you can identify.
Done means
- The installed iproute2 version and flow syntax were checked.
- The device, parent qdisc and destination classes were recorded before any privileged change.
- The chosen rule is either a deliberate hash with a documented bucket range or a mapping whose arithmetic is understood.
tc filter showconfirms the intended filter, keys, preference and divisor.- A tested rule can be removed by its exact device, parent, protocol and preference without deleting the parent qdisc.