Edit Ethernet Headers Safely with tc skbmod

Rewriting a MAC address by hand with pedit means fiddling with byte offsets; skbmod lets you just say which field changes. You will finish with a traffic-control filter that changes a complete destination or source MAC address, swaps the two addresses, or marks eligible IP packets as Congestion Encountered. The examples use tc from iproute2 6.1.0-1ubuntu6.4, whose utility reports iproute2 6.1.0.

Allow about twenty minutes, including time to inspect the existing qdisc and filter layout. You need root or CAP_NET_ADMIN, the iproute2 package, and a test interface or maintenance window. Packet edits affect live traffic. Do not run the mutating examples on a production interface until you have a rollback command and have confirmed the matching packets.

1. Check the installed contract

Start with read-only checks. This step needs no elevation:

$ tc -V
$ tc action add skbmod help

The installed command shows set, swap and ecn. It also lists the controls reclassify, pipe, drop, continue, ok and goto chain. The local tc-skbmod(8) manpage describes the same three action types but calls the successful final control pass. Prefer the output from the installed binary when the two documents differ. In particular, do not copy a control word from another iproute2 release without checking it here.

Checkpoint: record the interface, parent and preference used by your existing traffic-control layout:

$ tc qdisc show dev ethX
$ tc filter show dev ethX ingress
$ tc filter show dev ethX parent 1:

Replace ethX and 1: with real values. If there is no suitable parent, stop here and design the qdisc separately. skbmod is an action attached to a filter; it does not create the qdisc or classifier that selects packets.

2. Choose one packet change

skbmod has three mutually exclusive modes. Use one mode per action:

Do not combine set, swap and ecn in one action. The manpage marks that combination as undefined. If a packet needs separate operations, attach separate actions with a deliberate control between them.

These operations have different boundaries. set and swap operate on Ethernet packets, while ecn operates on IP packets. A filter that selects the wrong protocol can make a correct action appear to do nothing.

3. Add a narrow MAC rewrite

The following example uses the manpage's u32 classifier shape. It selects ICMP over IPv4 and replaces the destination MAC. It requires elevation because it changes kernel traffic-control state:

$ sudo tc filter add dev ethX parent 1: protocol ip prio 10 +    u32 match ip protocol 1 0xff flowid 1:2 +    action skbmod set dmac 02:15:15:15:15:15 pipe

02:15:15:15:15:15 is an example value, not a safe universal destination. Use the address appropriate to your network. The pipe control passes the packet to the next action attached to the same filter. Omit it when the action should finish normally on this iproute2 version, or choose another control only when the surrounding filter tree requires it.

For a combined rewrite, the supported fields can appear under one set:

$ sudo tc filter add dev ethX parent 1: protocol ip prio 20 +    u32 match ip protocol 1 0xff flowid 1:2 +    action skbmod set etype 0xBEEF dmac 02:12:13:14:15:16 smac 02:22:23:24:25:26

An arbitrary ethertype such as 0xBEEF is useful for demonstrating the syntax but is unlikely to be a valid production protocol. Do not use it merely to make a packet recognisable unless the receiver is prepared for it.

4. Verify the installed filter

Immediately inspect the filter after adding it. This is read-only:

$ sudo tc -s filter show dev ethX parent 1:

Look for the expected preference, classifier and skbmod action. The statistics are the useful checkpoint: packet and byte counts should increase only when traffic matches the classifier. A zero count means the traffic did not match, the filter is attached at the wrong parent, or the path bypasses that qdisc. It does not prove that the MAC rewrite is correct.

Use a packet capture on the appropriate side of the interface when you need to prove the resulting frame. Remember that ingress and egress placement changes where the edit is observed, and hardware offload can make software counters and captures confusing. Test with a controlled packet before increasing scope.

5. Swap addresses instead of setting them

To exchange Ethernet source and destination addresses, use swap mac and keep the classifier narrow:

$ sudo tc filter add dev ethX parent 1: protocol ip prio 30 +    u32 match ip protocol 1 0xff flowid 1:2 +    action skbmod swap mac

This is not a general routing operation. It edits the Ethernet header selected by the filter; it does not rewrite IP addresses, update neighbour state or make a route valid. Treat it as a lab or controlled forwarding technique unless the surrounding design explicitly accounts for those layers.

6. Mark ECN-capable packets

To mark ECT IP packets as CE, use the separate ecn mode:

$ sudo tc filter add dev ethX parent 1: protocol ip prio 40 +    u32 match ip protocol 6 0xff flowid 1:2 +    action skbmod ecn

This example selects TCP, but the ECN action is about the IP header, not TCP itself. The action does not change Non-ECT packets. Marking CE is a congestion signal and can alter transport behaviour, so use it only with a measured policy and a rollback window. It is not a harmless packet label.

7. Remove a test rule

There is no undo flag on skbmod. Remove the filter that owns the action. Before doing so, save its exact current definition:

$ sudo tc -s filter show dev ethX parent 1:

If a test rule was the only filter at preference 10, the matching removal is:

$ sudo tc filter del dev ethX parent 1: protocol ip pref 10

That command deletes the filter at that preference, including its classifier and actions. It is destructive to that filter's traffic policy, so do not use it when preference 10 belongs to shared configuration. Restore the saved rule or the configuration managed by your network system, then verify again with tc filter show.

Common traps

Done means