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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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:
setchanges one or more Ethernet fields:dmac,smacandetype.swap macexchanges the destination and source MAC addresses.ecnchanges ECT IP packets to CE. Non-ECT packets are not changed.
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
- Wrong parent, silent no-op. A valid action on the wrong parent does nothing useful. Check whether the filter is on ingress, egress or a classful parent.
- Do not carry over pedit offsets.
skbmodedits complete supported fields, unlike the byte-orientedpeditexamples it replaces. - One mode per action. Only one of
set,swapandecnbelongs in one action. Chain distinct actions only after confirming their order. - Quote and validate values. MAC addresses and ethertypes are packet data, not shell variables. Quote variables in generated commands and validate them before applying a rule.
- Acceptance is not correctness. Successful filter installation proves syntax and kernel acceptance, not that packets match or that the resulting frame is operationally safe.
Done means
- Contract checked. You confirmed the installed iproute2 version and the action's help output.
- Placement correct. You selected the correct qdisc parent, protocol and filter preference.
- One mode used. Exactly one skbmod mode was applied per action.
- Result verified. You checked filter counters and, where needed, captured the changed traffic.
- Rollback ready. You recorded a safe removal or restoration command before changing live traffic.