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.

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