Use tc vlan Actions to Add, Replace or Remove 802.1Q Tags
You will attach a tc VLAN action to an ingress filter, then use it to add, replace or remove an 802.1Q tag. The examples target the installed iproute2 6.1.0 package and take about 15 minutes to adapt and test. They change packet handling on a live interface, so use a maintenance window and keep a second access path to the host.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide covers the vlan action in tc, not the persistent VLAN interfaces created with ip link add link ... type vlan. The action edits packets as they pass through a traffic-control hook.
1. Check the tool and choose the interface
Replace IFACE with the interface receiving the packets. Do not use a guessed interface name: applying a filter to the wrong device can be just as disruptive as applying the right filter with the wrong match.
$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0
$ IFACE=eth0
$ ip link show dev "$IFACE"
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
The qdisc, filter and actions changes below require elevated privileges. Reading the current configuration does not:
$ tc qdisc show dev "$IFACE"
$ tc filter show dev "$IFACE" ingress
Checkpoint: record the existing output. You need it when checking that your filter is the only new rule and when restoring the previous configuration.
2. Understand the four packet operations
push adds an outer VLAN tag and requires id VLANID. modify replaces an existing 802.1Q tag and also requires an ID. pop removes the outermost VLAN encapsulation and takes no further VLAN argument. These operations describe the packet at the point where the action runs, so a filter that sees an untagged packet cannot use modify as if a tag were already present.
The optional protocol selects the VLAN protocol. The installed manual documents 802.1Q and 802.1ad. The optional priority is a decimal value from 0 to 7. The action's default control is pipe, which continues to the next action. Use reclassify when you deliberately want classification to restart from the first filter attached to the parent.
There are also Ethernet-header operations. pop_eth removes a plain base Ethernet header after all VLANs have been removed. push_eth adds one with dst_mac and src_mac; it does not stack a second MAC header. These are specialised packet-reconstruction operations, so the examples below stay with VLAN tags.
3. Add an ingress hook without adding a filter yet
The manpage examples use the ingress qdisc handle ffff:. Adding it is a state change, and it can fail if another ingress qdisc already exists. Inspect first, then run this as root or through sudo:
$ sudo tc qdisc add dev "$IFACE" handle ffff: ingress
$ sudo tc qdisc show dev "$IFACE"
qdisc ingress ffff: parent ffff:fff1 ----------------
If the add command says that the qdisc already exists, do not delete it. It may belong to another filter set. Inspect its filters and integrate with the existing hook instead.
Recovery if you created an unused hook and are certain it has no filters you need:
$ sudo tc qdisc del dev "$IFACE" ingress
That deletion removes the ingress qdisc and every filter attached to it. Treat it as destructive and confirm the device name before pressing Enter.
4. Push a tag onto a narrowly matched packet
This example matches incoming ICMP packets from 10.0.0.2 and pushes VLAN ID 123. It follows the documented u32 filter shape. Change the address and VLAN ID to values valid for your network; do not copy them into a production rule unchanged.
$ sudo tc filter add dev "$IFACE" parent ffff: pref 11 protocol ip \
u32 match ip protocol 1 0xff flowid 1:1 \
match ip src 10.0.0.2 flowid 1:1 \
action vlan push id 123
$ sudo tc filter show dev "$IFACE" ingress
A successful add normally prints nothing. The show command should list a filter with an action containing vlan push id 123. The command changes live traffic immediately. Test with one known packet source before widening the match.
To undo this exact rule, delete it by its preference:
$ sudo tc filter del dev "$IFACE" parent ffff: pref 11
If preference 11 is already in use, choose a different value after inspecting the existing filters. Do not delete by preference until you know which rule owns it.
5. Pop an outer tag and restart classification
For incoming VLAN traffic, match protocol 802.1Q and remove the outer tag. The reclassify control sends the now-plain packet back to the first filter on the parent, which is useful when later rules should see the decapsulated protocol.
$ sudo tc filter add dev "$IFACE" parent ffff: pref 20 protocol 802.1Q \
u32 match u32 0 0 flowid 1:1 \
action vlan pop reclassify
$ sudo tc filter show dev "$IFACE" ingress
The broad match u32 0 0 matches every packet reaching that filter. Keep it only when every matching VLAN packet should be decapsulated. If you need a narrower rule, add the appropriate classifier match rather than relying on the action to decide which packets are safe.
Undo it with the same preference:
$ sudo tc filter del dev "$IFACE" parent ffff: pref 20
6. Replace a tag or set priority
Use modify when a matching packet already has an 802.1Q tag and you want to replace it with another ID. The protocol and priority are optional; the ID is not.
$ sudo tc filter add dev "$IFACE" parent ffff: pref 30 protocol 802.1Q \
u32 match u32 0 0 flowid 1:1 \
action vlan modify protocol 802.1Q priority 4 id 321
$ sudo tc filter show dev "$IFACE" ingress
A priority outside 0 to 7 is invalid. VLAN IDs are accepted as unsigned 16-bit values, and the command recognises formats such as a hexadecimal value with a 0x prefix. Keep the ID and priority explicit in scripts so a reviewer can see the intended wire format.
Remove the replacement rule before testing another version at the same preference:
$ sudo tc filter del dev "$IFACE" parent ffff: pref 30
7. Verify and recover after testing
Check both the qdisc and filter state after each change. A rule being listed proves that it was installed, not that the kernel, driver or peer network accepts the resulting frame.
$ sudo tc qdisc show dev "$IFACE"
$ sudo tc filter show dev "$IFACE" ingress
$ ip -s link show dev "$IFACE"
Use a packet capture or a controlled test receiver to confirm whether the tag is present at the intended point. Do not assume that a VLAN action creates a persistent configuration: it is a live traffic-control rule and will normally need to be recreated after reboot by your chosen network-management system.
When the test is over, delete the filters you added. If the ingress qdisc was created solely for this test and is now empty, remove it too. If it pre-dated the test, leave it in place. The safe recovery sequence is therefore: remove only your known preferences, inspect again, then decide whether the qdisc itself belongs to you.
Done means
- You confirmed the interface and the installed iproute2 version before changing traffic handling.
- You inspected existing ingress qdiscs and filters rather than deleting a shared hook.
- You used
push,modifyorpopfor the packet state actually seen by the filter. - You kept VLAN IDs, protocol and priority values explicit and within the documented ranges.
- You verified the installed rule with
tc filter showand tested packet behaviour separately. - You recorded the preferences you added and can remove those rules without disturbing unrelated filters.