There may be situations as part of regular network admin work (or red team assignments perhaps) where we would want to copy incoming/outgoing traffic from one network interface (NIC) to another. In other words, replicating the SPAN (aka port mirroring) functionality offered by managed switches.

Introducing tc – Linux Traffic Control

One way of achieving just that is to use the little-known but incredibly versatile tc utility in Linux. Unless you are using an exotic distro, it should already be bundled on your system as part of iproute2, so there is nothing new to install.

Create a virtual interface for testing

Your computer most likely has only one NIC, so for testing purposes we are going to create a virtual interface of type dummy using iproute2. The commands (to be run as root) would be:

ip link add dummy0 type dummy
ip link set dummy0 up

At this point, you can already start sniffing on the newly-created dummy0 interface using Wireshark, tcpdump, tshark or another tool of your choosing. You shouldn’t be seeing a lot of packets yet, but you could observe some ICMPv6 traffic. The reason is that the dummy interface was assigned an IPv6 address automatically. To see details, run:

ip address

# or in short form
ip a

An alternative approach to create our dummy0 interface is to use Network Manager, which is probably default on your system unless you’re using Ubuntu:

nmcli connection add type dummy ifname dummy0 ipv4.method disabled ipv6.method disabled

You now have a new interface with no IP address, that operates at layer 2 only, which is sufficient for our needs. It essentially behaves like a loopback-like device. Note that the NOARP flag is set, so this interface should be pretty silent on its own, which is actually better for our purpose (we don’t really want to see unrelated traffic being injected into our capture).

As said earlier, you can start sniffing on the interface as soon as it created.

Setting up tc rules

Here comes the magic sauce (borrowed from here):

# mirror ingress traffic
tc qdisc add dev $source_if ingress;:
tc filter add dev $source_if parent ffff: \
protocol all \
u32 match u8 0 0 \
action mirred egress mirror dev $dest_if;:
# mirror egress traffic
tc qdisc add dev $source_if handle 1: root prio;:
tc filter add dev $source_if parent 1: \
protocol all \
u32 match u8 0 0 \
action mirred egress mirror dev $dest_if;:

Source interface obviously has to be renamed if different than eth0. Note the separate rules for ingress and egress, which makes it even more flexible if you are interested in seeing traffic in one direction only.


Let’s go back to our capture tool (Wireshark or whatever). Packets should already be flowing in on your screen, but it doesn’t hurt to verify the setup by pinging a host and watching the exchange.

To stop port mirroring simply run:

tc qdisc del dev $source_if ingress;:
tc qdisc del dev $source_if root;:

Notes and gotchas

  • tc rules are not persistent, it is your responsibility to restore them as needed
  • Connection profiles created in Network Manager will on the other hand persist, but they may be disabled by default, and enabled on demand
  • When coupling a pair of physical interfaces, traffic could be blocked if one interfaces goes down or is unplugged. To work around that, you could add a bridge on the two interfaces

Other use cases

  • advanced traffic manipulation techniques are possible, like stripping 802.1Q (VLAN) tags
  • tc can be used to limit bandwidth – it is after all a traffic shaping tool
  • tc is also an excellent testing tool. In particular, the netem queue discipline can be used to simulate network faults such as delay, packet loss, etc in order to test software behavior under adverse network conditions