Manage Shared tc Actions Without Losing Track of State
A tc action can sit on a host doing nothing for months and still be the thing a filter quietly depends on. This is a cautious workflow for creating one, finding it again by type and index, checking its counters, and removing it cleanly. The examples use the installed iproute2 6.1.0 command and the action type gact, but the same object workflow applies to types such as mirred, bpf and police.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need iproute2, a shell, and usually root privileges or CAP_NET_ADMIN for changes to kernel traffic-control state. This guide does not attach a filter to a live interface. That boundary matters: a standalone action does nothing until a classifier uses it.
1. Check the installed command
Start with read-only checks. They do not need elevated privileges:
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ dpkg-query -W iproute2
iproute2 6.1.0-1ubuntu6.4
$ tc actions help
The local syntax groups operations into add, change, replace, get, delete, list and flush. The action table is selected by its type. An index is unique within that type, not across every action on the host.
Checkpoint
Write down the action type and index you intend to manage before you run a command. A copied index from a different type can identify nothing, or worse, a different object than you expected.
2. Inspect existing actions first
Listing is the safest way to establish the current state. The manpage spells the selector as action ACTNAME:
$ tc actions list action gact
$ tc -s actions list action gact
The first command lists all stored gact actions. The -s form asks for statistics as well. An empty result is useful: it means there is no action of that type to reuse, subject to the permissions and network namespace in which you ran the command.
For a large table, restrict the dump to recently used actions. The value is milliseconds since the action was last hit, so 20,000 means roughly the last twenty seconds:
$ tc actions list action gact since 20000
This is a time filter, not a polling interval and not an index. It can omit an action that exists but has not recently processed a packet.
3. Create one action with a deliberate index
Choose an unused index and record it in your change notes. The following creates a generic action that drops packets and assigns index 41001:
# tc actions add action gact drop index 41001
Run this as root, or through your approved privilege mechanism. It changes kernel networking state, and it becomes operationally significant as soon as a filter refers to it. The command is intentionally not attached to an interface here. If the index is already assigned, stop and inspect the existing object instead of changing it by accident.
When no index is supplied on an add, the kernel chooses one:
# tc actions add action gact drop
That is convenient for one-off interactive work, but awkward for automation because you must discover the assigned index before later commands can address the action. Prefer an explicit index for a managed configuration.
Checkpoint
Confirm the object exists before doing anything else:
$ tc actions get action gact index 41001
$ tc -s actions get action gact index 41001
get requires an index. With -s, the dump includes statistics for that action. Exact counters and formatting depend on the kernel and action implementation.
4. Understand the options shared by action types
The action-specific part comes immediately after action. Common options follow it. A cookie is a 128-bit, kernel-opaque value useful for correlating the action with your own records:
# tc actions add action gact drop index 41002 cookie change-20260927
Do not treat the cookie as a secret or as a rule understood by the kernel. It is metadata stored with the action. Use a value that your operational tooling can recognise, and keep the index as the primary lookup key.
Hardware-oriented deployments can select hardware statistics behaviour:
# tc actions add action gact drop index 41003 hw_stats disabled
immediate requests current device statistics during a dump, delayed permits driver-maintained values that may lag, and disabled requests no hardware statistics. If omitted, the driver and its resources choose the counter type. These settings describe hardware statistics; they do not make a software action hardware-offloadable.
skip_sw requires hardware processing and fails when the action has no hardware offload support. skip_hw prevents hardware processing. Treat either flag as a deployment constraint, not as a performance hint.
5. Change or replace an existing action
Use the same type and index to modify an object:
# tc actions change action gact drop index 41001
replace is the alternative operation named by the interface:
# tc actions replace action gact drop index 41001
Action parameters and whether a particular change is accepted are defined by that action type and the kernel. Read its dedicated manual page before changing a live action. A replacement can affect every filter that references the object, so treat it as a service-impacting change even though it is one command.
Afterwards, inspect the object again:
$ tc -s actions get action gact index 41001
6. Remove one action, then clean up carefully
Delete by type and index:
# tc actions delete action gact index 41001
Deletion removes the action object. It does not delete a classifier that referred to it, but the classifier may no longer behave as intended. Check references and traffic impact before deleting a production action. If you need recovery, recreate it with the same action parameters, index and cookie recorded from your change notes; statistics are not restored by that process.
Warning
flush deletes every action in the selected type table:
# tc actions flush action gact
Do not use this as a troubleshooting shortcut on a shared host. There is no single undo command for a flush. Export or record the existing list first, and use individual deletes when you know the exact objects to remove.
7. Diagnose the common failures
A missing action kind, a missing index and insufficient privileges are different failures. Start with the read-only list command in the same network namespace as the intended change:
$ tc actions list action gact
$ id -u
$ ip netns identify "$$" 2>/dev/null || true
An empty list does not prove that another namespace is empty. A permission error means the process lacks the capability needed to query or change the relevant state; adding sudo may help only when policy permits it and the command then runs in the correct namespace.
If skip_sw fails, the requested hardware path is unavailable for that action. Remove the flag only if software processing is acceptable and the resulting behaviour has been tested. If statistics are absent with hw_stats, check the driver and hardware support rather than assuming the action was not hit.
Done means
- Version and table checked: you confirmed the installed iproute2 version and the current action table.
- Identity recorded: you noted the action type, index and any cookie before changing state.
- Object verified: you used
getand-sto check the object and its counters. - Standalone understood: you know an unattached action does nothing until a filter uses it.
- Risk respected: you treated
change,replace,deleteand especiallyflushas potentially service-disrupting. - Recreation ready: you have a recorded recreation command for every action you remove or replace.