How to Configure Network Port Mirroring (SPAN) in Linux Using tc and mirred

In enterprise network security, real-time traffic observability is vital for auditing infrastructure, analyzing application anomalies, and feeding network intrusion detection systems (NIDS) such as Suricata, Zeek, or Snort. On enterprise managed switches, this functionality is traditionally achieved via Switched Port Analyzer (SPAN) or port mirroring sessions. However, on Linux servers, cloud virtual machines, and software routers, purchasing dedicated hardware taps is neither practical nor necessary.

The Linux kernel includes native, wire-speed packet replication through the Traffic Control (tc) subsystem and the mirred (mirror/redirect) action. Using tc, system administrators can copy all incoming (ingress) and outgoing (egress) packets traversing a production interface and mirror them seamlessly onto a dedicated monitoring port, dummy tap, or packet capture pipe without interrupting or delaying production traffic. In this tutorial, we examine how to configure a monitoring sink, establish ingress and egress mirroring rules, filter selective traffic, and inspect operational statistics.

How tc and mirred Port Mirroring Functions

The mirred kernel action acts as a transparent packet tap within the Linux networking stack:

  • Ingress Tapping: Attaches to an ingress queuing discipline (qdisc), intercepting packets immediately after the network driver processes hardware ring buffers, before routing or iptables processing.
  • Egress Tapping: Attaches to an egress root qdisc, duplicating outbound packets right before they are dispatched onto the physical wire.
  • Zero Disruption: Unlike packet redirection (which diverts packets away from their destination), the mirror action duplicates packet buffers in kernel space, transmitting the original packet unimpeded while dispatching an identical twin to your monitoring sink.

Step 1: Preparing a Destination Monitoring Interface

You can mirror traffic to a secondary physical network adapter connected to a dedicated analysis workstation, or to a local virtual dummy interface for analysis with tcpdump or Suricata on the same host.

To create and activate a virtual dummy monitoring interface named mon0:

sudo ip link add name mon0 type dummy
sudo ip link set mon0 up

Verify that mon0 is online: ip link show mon0.

Step 2: Mirroring Incoming (Ingress) Traffic

To capture all traffic arriving on your production network interface (for instance, eth0) and mirror it to mon0:

  1. Attach an ingress queue discipline to eth0 with handle ffff::
    sudo tc qdisc add dev eth0 handle ffff: ingress
  2. Add a universal filter matching all incoming IP packets and directing the mirred action to mirror them to mon0:
    sudo tc filter add dev eth0 parent ffff: \
        protocol all \
        u32 match u32 0 0 \
        action mirred egress mirror dev mon0

Key parameters explained:

  • parent ffff:: Binds the rule to the ingress qdisc.
  • protocol all: Intercepts all Layer 3 protocols, including IPv4, IPv6, and ARP.
  • u32 match u32 0 0: A universal 32-bit bitmask filter that matches every packet without exception.
  • action mirred egress mirror dev mon0: Instructs the kernel to clone the packet and transmit it out of the mon0 interface.

Step 3: Mirroring Outgoing (Egress) Traffic

Capturing outbound traffic dispatched from the server requires attaching a root queue discipline (such as prio) on eth0:

  1. Create a root priority qdisc on eth0:
    sudo tc qdisc add dev eth0 root handle 1: prio
  2. Attach the egress mirroring filter:
    sudo tc filter add dev eth0 parent 1: \
        protocol all \
        u32 match u32 0 0 \
        action mirred egress mirror dev mon0

With both ingress and egress rules active, mon0 now receives a complete, bidirectional mirror of all network transactions traversing eth0.

Step 4: Selective Traffic Mirroring (Port or Protocol Filtering)

In high-throughput environments, mirroring an entire 10GbE pipe can easily overwhelm monitoring disks. You can refine your tc filter to mirror only specific protocols or ports.

For example, to mirror exclusively HTTP and HTTPS web traffic (destination ports 80 and 443):

sudo tc filter add dev eth0 parent ffff: \
    protocol ip \
    u32 match ip protocol 6 0xff \
    match ip dport 443 0xffff \
    action mirred egress mirror dev mon0

Step 5: Inspecting Statistics and Capturing Mirrored Packets

To verify that packets are actively flowing through the kernel mirror, query the filter statistics:

tc -s filter show dev eth0 ingress

Output displays the exact packet count and byte throughput processed by the mirred action:

filter parent ffff: protocol all pref 49152 u32 chain 0 
filter parent ffff: protocol all pref 49152 u32 chain 0 fh 800: ht divisor 1 
filter parent ffff: protocol all pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid ??? not_in_hw 
  match 00000000/00000000 at 0
        action order 1: mirred (Egress Mirror to dev mon0) pipe
        index 1 ref 1 bind 1 installed 42 sec used 0 sec
        Action statistics:
        Sent 1854200 bytes 14502 pkt (dropped 0, overlimits 0 requeues 0)

Launch tcpdump on the destination interface to observe the live mirrored stream:

sudo tcpdump -i mon0 -nn

Step 6: Deleting Port Mirroring Rules

When monitoring is complete, remove the queue disciplines from eth0 to release kernel resources and terminate packet cloning:

# Remove ingress mirroring
sudo tc qdisc del dev eth0 ingress

# Remove egress mirroring
sudo tc qdisc del dev eth0 root

If you used a temporary dummy interface, delete it using ip link:

sudo ip link delete mon0

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.