Carry tc Metadata Across Ethernet with tc-ife
tc-ife lets you stash extra socket metadata inside an Ethernet frame and pull it back out on a second host, no VLAN tag required. You'll build the encoding sender and the decoding receiver, using the installed iproute2 6.1.0-1ubuntu6.4 documented in tc-ife(8). Budget about twenty minutes for a controlled test, plus time to track down the right interface and traffic.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Confirm the installed action
- 2. Record the current traffic-control state
- 3. Prepare the receiver for decoded metadata
- 4. Add a receiver filter for the recovered mark
- 5. Configure the sender to encode the mark
- 6. Choose static metadata only when you mean it
- 7. Inspect failures before changing the design
- 8. Remove the test configuration
- Two test points. Two Linux hosts, or two network namespaces joined by a test link, both with
tcfrom iproute2. - Root on both ends. Qdisc and filter changes need it.
- A real interface name. The commands below use
eth0as a placeholder; swap in the interface actually carrying the traffic.
Warning
Do not paste these commands onto a production interface until you have checked the filter direction and have the rollback commands ready.
1. Confirm the installed action
Check the binary and package version first. These read-only commands need no elevated privileges:
$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
Then ask the action parser for its local syntax as a second check:
$ tc action add action ife help
Usage:... ife {decode|encode} [{ALLOW|USE} ATTR] [dst DMAC] [src SMAC] [type TYPE] [CONTROL] [index INDEX]
- encode runs on the sending side.
- decode runs on the receiving side.
- Match the ethertype on both ends. This guide sets it explicitly to
0xdeadrather than relying on the documented default of0xED3E, so a capture and a filter always agree.
2. Record the current traffic-control state
Run these on each host before changing anything. They're ordinary inspection commands, though the output depends on whether the interface already has an ingress or root qdisc:
$ tc qdisc show dev eth0
$ tc filter show dev eth0 ingress
$ tc filter show dev eth0 egress
Save the output somewhere outside the shell history if this is a shared system: you'll need it if the interface already has filters that must be restored. The examples below use a root prio qdisc on the sender and an ingress qdisc on the receiver. If either qdisc already exists, fold the filter into the existing arrangement instead of bolting on another one.
Checkpoint
You should know which host sends the traffic, which receives it, and which qdisc handles the relevant direction.
3. Prepare the receiver for decoded metadata
This step changes the receiver's traffic-control configuration and needs root. The ingress qdisc gives the filter a place to run:
# tc qdisc add dev eth0 handle ffff: ingress
Now match the IFE ethertype and decode the metadata. The reclassify control action tells tc to classify the packet again after decoding, so a later filter can use the recovered mark:
# tc filter add dev eth0 parent ffff: prio 2 protocol 0xdead \
u32 match u32 0 0 flowid 1:1 \
action ife decode reclassify
On success, tc normally prints nothing. Verify the filter rather than trusting the exit status alone:
# tc -s filter show dev eth0 parent ffff:
# tc -s qdisc show dev eth0
The listing should show an ife decode reclassify action. Packet and byte counters stay at zero until matching traffic arrives.
4. Add a receiver filter for the recovered mark
The sender will encode an skb mark of decimal 17. Add a later receiver filter that matches that mark. Also root-only, also state-changing:
# tc filter add dev eth0 parent ffff: prio 4 protocol ip \
handle 0x11 fw flowid 1:1 \
action ok
The priority is deliberate: the decode filter runs at priority 2, the mark filter at priority 4, after reclassification. action ok accepts the classification path without touching the packet. If your real policy needs a different class or action, change only that later rule, once you've proved the mark arrives.
5. Configure the sender to encode the mark
Warning
This changes packet handling on the sender. The example targets ICMP traffic to one destination and rewrites the destination MAC in the encoded IFE metadata. Confirm the address is a test endpoint before applying it.
# tc qdisc add dev eth0 root handle 1: prio
# tc filter add dev eth0 parent 1: protocol ip prio 10 u32 \
match ip dst 192.168.122.237/24 \
match ip protocol 1 0xff flowid 1:2 \
action skbedit mark 17 \
action ife encode type 0xdead allow mark \
dst 02:15:15:15:15:15
The first action sets the mark. The IFE action's allow mark clause whitelists that metadata for encoding. dst is an optional six-byte destination MAC carried by the action, not the same thing as the IP destination match. Keep the ethertype, mark value and MAC addresses consistent with the receiver and the real link.
Verify the sender-side filter and its counters:
# tc -s filter show dev eth0 parent 1:
# tc -s qdisc show dev eth0
Generate one known matching packet, then repeat the two inspection commands. A rising counter proves traffic matched the sender rule. It does not prove the receiver accepted the packet, so check the receiver counters too.
6. Choose static metadata only when you mean it
allow copies the selected value from the packet's current state. For a fixed value instead, the manual gives you use, which takes the value directly in the action. This encodes a fixed mark of 17:
# tc filter add dev eth0 parent 1: protocol ip prio 20 u32 \
match ip dst 192.168.122.237/24 \
action ife encode type 0xdead use mark 17 \
dst 02:15:15:15:15:15
- Do not stack this with the earlier rule unless the matches and priorities are genuinely distinct: two rules can both process the same packet and muddy your counters and captures.
- The same split applies to
prioandtcindex.allowselects existing metadata;userequires a value. - A zero mark is not encoded. The action increments its overlimits statistic and follows its configured control action instead.
7. Inspect failures before changing the design
- Receiver counter at zero, sender counter also zero. The IP or protocol match did not select the traffic. Check the exact destination prefix, the ICMP protocol match, and the filter's parent qdisc.
- Sender matches but receiver does not. Check the path and the ethertype: both IFE actions must use
0xdeadin this example. A capture on the link can confirm the packet arrived, but do not infer decoded metadata from the outer packet alone; inspect the receiver's decode and later-filter counters once the packet lands. - Filter command reports an unknown device, wrong parent or missing qdisc. Stop and correct that topology detail. Do not keep changing priorities until the parent qdisc is valid. A successful
tc filter addonly confirms the kernel accepted the configuration; it does not validate your traffic match.
8. Remove the test configuration
Removal disrupts the traffic path, so stop the test traffic first. On the sender, remove the filter then the root qdisc:
# tc filter del dev eth0 parent 1: prio 10
# tc qdisc del dev eth0 root handle 1:
On the receiver, remove the filters then the ingress qdisc:
# tc filter del dev eth0 parent ffff: prio 2
# tc filter del dev eth0 parent ffff: prio 4
# tc qdisc del dev eth0 ingress
Those deletions remove the example qdiscs, plus any other filters attached to those exact parents that you did not preserve. If the interface had an existing configuration, restore it from the output saved in step 2 rather than treating this as a universal rollback. Finish by checking:
# tc qdisc show dev eth0
# tc filter show dev eth0 ingress
# tc filter show dev eth0 egress
Done means
- Roles are right. The sender uses
ife encodeand the receiver usesife decode. - Ethertypes match. Both actions use the same explicit value,
0xdead. - Sender counter moves. It increases for a deliberately matching packet.
- Receiver counters confirm the path. Both the decode and later mark-filter counters show the metadata arriving.
- Original state is restored. Any existing qdisc and filter configuration is back in place after the test.