Steer Traffic Metadata Safely with tc skbedit
When a later filter or routing rule needs to know something about a packet, skbedit tags it with a mark, priority or queue. You will attach a temporary traffic-control filter, use the skbedit action to add that metadata to matching packets, inspect the result, and remove the test configuration. The examples use tc from iproute2 6.1.0-1ubuntu6.4 on this machine. Allow about 15 minutes, plus a maintenance window if you are changing a production interface.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, the iproute2 package, and an interface whose traffic you understand. These commands require elevated privileges because they change kernel networking state. Replace every value in angle brackets before running a command. The examples use an ingress filter and set a firewall mark, which is useful for later policy routing or classification but does not alter packet payload bytes.
1. Check the interface and existing filters
Start with read-only inspection. Use a real interface name, not the placeholder:
$ IFACE='enp0s31f6'
$ ip -br link show dev "$IFACE"
$ tc filter show dev "$IFACE" ingress
The first command should show the selected link. The second may print no filters, or it may show filters owned by another configuration system. Do not add a test rule until you know how the current setup is managed. A network manager, container runtime or orchestration agent may recreate or remove filters after you change them.
Checkpoint: record the current output somewhere outside the terminal. It is your reference when you undo the test. If the interface is carrying important traffic, stop here and schedule the change rather than experimenting on it.
2. Confirm the transmit queues before using queue_mapping
skbedit can override the transmit queue on a multi-queue interface. The available queue numbers are exposed as tx-N directories:
$ find "/sys/class/net/$IFACE/queues" -maxdepth 1 -type d -name 'tx-*' -printf '%f\n' | sort -V
Use only a queue number that exists on the destination interface. On a single-queue device, forcing a different number is a distraction and may fail or be meaningless. Queue selection is also a scheduling decision: it can change latency and throughput, so do not use it as a harmless substitute for a mark-only test.
3. Add a temporary mark rule
The simplest useful test matches every packet arriving at the interface and sets its 32-bit mark to a value you choose. Adding the clsact qdisc provides the ingress hook:
$ sudo tc qdisc add dev "$IFACE" clsact
$ sudo tc filter add dev "$IFACE" ingress matchall \
action skbedit mark 0x1234
A successful tc command normally prints nothing. The mark is packet metadata held by the kernel. It is not inserted into the Ethernet, IPv4 or IPv6 packet, and it is not automatically visible to a remote host.
Warning: the rule above affects every ingress packet on the selected interface. Use it only on a test interface or during a controlled window. If another process already owns clsact, the first command can fail with an existing-object error. Do not delete that qdisc just to make the example fit; inspect the owner and use its configuration method.
4. Verify the action and counters
Inspect the filter and its statistics:
$ sudo tc -s filter show dev "$IFACE" ingress
You should see an ingress filter with an skbedit action and the mark value 0x1234. After packets arrive, the filter's packet and byte counters should increase. Exact formatting varies between iproute2 releases, so check for the action and the counters rather than matching a complete line in a script.
Checkpoint: generate a small amount of traffic that you can identify, then run the inspection command again. If the counters remain at zero, check that the traffic really enters this interface and that a different filter or offload path is not handling it first.
5. Apply the other skbedit fields carefully
The action has four other controls. Add only the one that serves the actual policy:
priorityoverrides packet classification. It acceptsroot,none, or a hexadecimal major class ID, optionally followed by a colon and hexadecimal minor class ID.queue_mappingoverrides the transmit queue with an unsigned 16-bit decimal value, such asqueue_mapping 2. Confirm thattx-2exists first.ptypesets the packet type tohost,otherhost,broadcastormulticast. This is a specialised receive-path operation, not a way to repair an incorrectly addressed packet.inheritdsfielduses the IPv4 or IPv6 Differentiated Services Field for the classification decision, overridingpriorityand any value supplied with it.
For marks, the mask selects the bits changed by the action. With no mask, mark 0x1234 uses the default mask 0xffffffff. A masked example that changes only the low byte is:
$ sudo tc filter replace dev "$IFACE" ingress matchall \
action skbedit mark 0x42/0xff
replace is useful when you already have a known filter handle or a uniquely replaceable rule in your configuration. If you are managing several filters, use the complete command generated by that system rather than guessing which rule a replacement will select. The mark value accepts automatically detected formats, so a decimal value can be written without a prefix and a hexadecimal value with 0x.
6. Remove the test without leaving a qdisc behind
When the test is complete, remove the filter first:
$ sudo tc filter del dev "$IFACE" ingress
This removes the ingress filters on the interface, so do not run it if the inspection in step 1 showed rules belonging to another service. Restore those rules through their owner instead. If you created the clsact qdisc solely for this test and it is now unused, remove it:
$ sudo tc qdisc del dev "$IFACE" clsact
These deletes are state-changing and can disrupt classification immediately. Keep the original inspection output and the owning service's configuration available before proceeding. If a managed service restores its own rules, let it do so and verify with tc filter show rather than repeatedly deleting them.
7. Separate skbedit from packet editing
skbedit changes metadata associated with the socket buffer. It complements pedit, which changes selected parts of packet data. If your requirement is to rewrite an address, port or header field on the wire, this action is the wrong tool. If your requirement is to label traffic for a later kernel decision, a mark or priority may be appropriate, but the later rule must actually consume that metadata.
Common failure traps are using a queue number that the interface does not expose, assuming a mark is transmitted to the peer, treating a successful command as proof that packets matched, and forgetting that inheritdsfield takes precedence over priority. Keep the scope of the match narrow when moving from a test to production, and verify counters after each change.
Done means
- Baseline recorded. You confirmed the interface and its existing filters.
- Queue validated. You used a real queue number before attempting
queue_mapping. - Action confirmed. Filter inspection showed the intended
skbeditaction, and its counters moved with matching traffic. - Cleaned up. You removed only the test rule, restored any managed configuration, and verified the final filter state.