Capturing VM Switch‑Port Traffic with a simple script

When I’m troubleshooting a virtual machine’s network behavior, the first thing I reach for is a packet capture. In theory it’s simple: locate the switch‑port the VM is attached to, start a capture on that port, and save the results for later analysis. In practice I kept forgetting the exact commands—especially the quirky combination of pktcap-uw and tcpdump-uw that ESXi require.

To solve that, I wrote a tiny shell script that does everything in one go. Below I walk through the script, explain each step, so you can use it in your own environment.

 

The script can be found here over on Codeberg.org

 

Why This Script Exists

  • Forgetfulness is real. Every time I needed a capture I’d open a terminal, remember the command net‑stats -l or using esxcli and whatever parameters is needed, extract the port number, remember the right flags for pktcap-uw, pipe it into tcpdump-uw, and finally name the output file and forget to give it a new one when doing multiple takes.
  • Consistency matters. Having a reproducible command chain eliminates human error—no more “oops, I used the wrong flag or command”
  • Convenience. With a single argument (<vm-name>) the script does all the heavy lifting, timestamps the output, ready for you to analyze the .pcapoutput.

 

How to Use It

chmod +x capture_vm_switchport_traffic.sh
./capture_vm_switchport_traffic.sh my-vm
 

Replace my-vm with any unique substring of the VM’s name. The script will:

  1. Find the correct switch‑port.
  2. Start capturing traffic.
  3. Save a timestamped .pcap under /tmp.

When you’re done, simply press Ctrl‑C. The capture ends, and you’ll see the location of the file printed to the console.

The Script, Line by Line

#!/bin/sh # --------------------------------------------------------------- #
capture_vm_switchport_traffic.sh
# # Usage: ./capture_vm_switchport_traffic.sh <vm-name>
# # What it does:
# 1️⃣ Finds the switch‑port ID (PortNum) for the given VM
# using `net‑stats -l`. The VM name appears in column 6;
# we also capture the exact name reported by the system.
# 2️⃣ Starts a packet capture on that switch‑port.
# 3️⃣ Saves the capture file under /tmp with a timestamp.
# ---------------------------------------------------------------
 

The header explains purpose, usage, and the three core steps.

set -euo pipefail # safer scripting
 

Enables strict error handling: abort on any failure, treat unset variables as errors, and propagate failures through pipelines.

1️⃣ Argument Validation

if [ "$#" -ne 1 ]; then
  echo "Error: Exactly one argument (the VM/client name) is required."
  echo "Usage: $0 <vm-name>"
exit 1 fiINPUT_VM=$1 # what the user typed (may be partial)
 

We insist on a single argument – the (partial) VM name – and store it for later pattern matching.

2️⃣ Locate the Switch‑Port

NET_OUT=$(net-stats -l)

MATCH=$(printf "%s\n" "$NET_OUT" | awk -v pat="$INPUT_VM" 'tolower($6) ~ tolower(pat) {print $1, $6}' | head -n1)

PORT_NUM=$(printf "%s" "$MATCH" | awk '{print $1}') REAL_VM=$(printf "%s" "$MATCH" | awk '{print $2}' | cut -d '.' -f1)

 
  • net‑stats -l lists all live interfaces.
  • We pipe the output through awk, performing a case‑insensitive match against column 6 (the VM name).
  • The first match gives us two pieces of data: the PortNum (column 1) and the exact VM identifier.
  • cut -d '.' -f1 strips any domain suffix that might appear.

If nothing matches, we bail out with a clear error:

if [ -z "${PORT_NUM:-}" ] || [ -z "${REAL_VM:-}" ]; then
echo "Error: No matching entry found for client \"$INPUT_VM\"." exit 2fi
 

3️⃣ Timestamped Filename

TIMESTAMP=$(date +%Y%m%d_%H%M%S)PCAP_FILE="/tmp/${REAL_VM}_${TIMESTAMP}.pcap"
 

A unique filename prevents accidental overwrites and makes it easy to locate captures later.

4️⃣ Run the Capture

echo "Starting capture on switch‑port $PORT_NUM → $PCAP_FILE"
echo "Press Ctrl‑C to stop the capture."
( pktcap-uw --switchport "$PORT_NUM" --dir 2 -o - | tcpdump-uw -r - -w "$PCAP_FILE")
 
  • pktcap-uw grabs raw packets from the specified switch‑port (--dir 2 means both inbound and outbound).
  • The -o - flag streams the output to stdout, which we pipe directly into tcpdump-uw.
  • tcpdump-uw reads from stdin (-r -) and writes a proper .pcap file (-w).

Because the whole pipeline runs inside a subshell, pressing Ctrl‑C cleanly terminates both processes.

5️⃣ Wrap‑Up Message

echo "Capture finished. PCAP saved to: $PCAP_FILE"
echo "You can inspect it with:"
echo " tcpdump-uw -r $PCAP_FILE"
echo " wireshark $PCAP_FILE # after copying to a workstation with Wireshark"
 

A friendly reminder of the next steps: analyze with tcpdump on the host or pull the file to a workstation and open it in Wireshark.

 

Takeaways

  • One source of truth. By encapsulating the lookup and capture logic, you eliminate the mental overhead of remembering several unrelated commands.
  • Safety first. set -euo pipefail ensures the script stops on any unexpected condition, protecting you from generating empty or misleading captures.
  • Reusability. Ready to troubleshoot VMs instantly.

Next time you’re staring at a VM and need to see what’s really happening on the wire, just run the script and let it do the heavy lifting. Happy sniffing! 🐶

This article was updated on 29 Dec 2025