Posted on
Artificial Intelligence

Artificial Intelligence Packet Capture Automation

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Packet Capture Automation with Bash

Smarter PCAPs, less noise, faster answers.

If you’ve ever tailed gigabytes of packet captures at 3 a.m. hunting for a weird spike, you know the pain: sprawling PCAPs, short-lived indicators, and never enough time. The value of packet capture is indisputable, but “always-on, capture-everything” quickly becomes “store-nothing-useful.”

This post shows how to use Linux + Bash to automate packet capture with AI-assisted triage so you:

  • Capture the right packets at the right time (ring buffers and triggers).

  • Extract flow features in seconds (tshark).

  • Surface outliers automatically (IsolationForest).

  • Summarize and escalate when it matters.

You’ll get a working, end-to-end pipeline you can schedule with cron/systemd, with installation commands for apt, dnf, and zypper.


Why AI-driven capture automation is worth it

  • Relevance over volume: Ring buffers and event-driven triggers (burst detection, port scans) keep high-signal traffic while shedding noise.

  • Reduced toil: AI flags outliers from recent captures, pointing you to the odd flows instead of making you skim everything.

  • Faster incident response: Automatic pivoting—tighten filters around suspicious hosts/ports, extend capture windows, and archive relevant slices.


Prerequisites and installation

We’ll use tcpdump (capture), tshark (parsing), jq (JSON), and Python with scikit-learn (AI anomaly detection). Optional: Zeek for richer logs.

Packages and service names vary a bit by distro; use the matching block below.

  • Debian/Ubuntu (apt)
sudo apt update
sudo apt install -y tcpdump tshark jq python3 python3-venv python3-pip cron
# Optional (if available)
sudo apt install -y zeek
# Enable cron (usually enabled by default)
sudo systemctl enable --now cron
  • Fedora/RHEL/CentOS Stream (dnf)
sudo dnf install -y tcpdump wireshark-cli jq python3 python3-pip python3-virtualenv cronie
# Optional (availability varies by distro/version; use COPR/EPEL if not found)
sudo dnf install -y zeek || echo "Zeek not in repo; install from Zeek.org or COPR/EPEL"
# Enable cron
sudo systemctl enable --now crond
  • openSUSE Leap/Tumbleweed (zypper)
sudo zypper refresh
sudo zypper install -y tcpdump wireshark-cli jq python310 python310-pip python310-virtualenv cron
# Optional (if present)
sudo zypper install -y zeek || echo "Zeek not in repo; install from Zeek.org or build from source"
# Enable cron
sudo systemctl enable --now cron

Allow non-root capture (optional but recommended):

# tcpdump capability
sudo setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump 2>/dev/null || \
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/tcpdump

# tshark uses dumpcap for capture paths
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/dumpcap

Create a project directory:

mkdir -p ~/ai-pcap/{pcap,out,logs,scripts,venv}
cd ~/ai-pcap

Set up a Python environment and install ML deps:

# Ubuntu/Debian
python3 -m venv venv
# openSUSE with python310
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install numpy pandas scikit-learn
deactivate

Step 1: Safe, continuous capture with a ring buffer

We’ll start a rolling capture that:

  • Keeps a small time window (e.g., last 60 minutes).

  • Excludes routine noise (e.g., SSH to your jump host).

  • Uses a safe snaplen to cut payload bloat.

Create scripts/capture.sh:

#!/usr/bin/env bash
set -euo pipefail

IFACE="${IFACE:-eth0}"
OUTDIR="${OUTDIR:-$HOME/ai-pcap/pcap}"
ROTATE_SEC="${ROTATE_SEC:-300}"   # rotate every 5 min
WINDOW_FILES="${WINDOW_FILES:-12}" # keep 12 files => 60 min window
SNAPLEN="${SNAPLEN:-128}"

# Example BPF: ignore SSH to/from 10.0.0.0/8; capture everything else IP
BPF_FILTER="${BPF_FILTER:-ip and not port 22 and not net 10.0.0.0/8}"

mkdir -p "$OUTDIR"

# File naming with UTC timestamp
FNAME="$OUTDIR/cap-%Y%m%dT%H%M%SZ.pcap"

# Run as root or with setcap; use nice/ionice to be gentle
exec nice -n 10 ionice -c2 -n7 tcpdump \
  -i "$IFACE" -s "$SNAPLEN" -nn -tt \
  -G "$ROTATE_SEC" -W "$WINDOW_FILES" \
  -w "$FNAME" \
  $BPF_FILTER

Run it:

chmod +x scripts/capture.sh
sudo IFACE=eth0 OUTDIR=$HOME/ai-pcap/pcap ./scripts/capture.sh

Tip: Adjust BPF filters to your environment. Good defaults often exclude chatty internal nets and known management ports.


Step 2: Event-driven capture triggers (optional, powerful)

Instead of always-on, you can start/stop or widen/narrow capture based on real-time signals: bandwidth spikes, sudden port fan-out, or connection bursts.

A lightweight approach: watch /proc/net/dev bytes per second to trigger “enhanced” capture for a short window.

Create scripts/triggered-capture.sh:

#!/usr/bin/env bash
set -euo pipefail

IFACE="${IFACE:-eth0}"
THRESHOLD_MBPS="${THRESHOLD_MBPS:-100}"     # trigger when >100 Mbps
CHECK_INTERVAL="${CHECK_INTERVAL:-5}"        # seconds between checks
BURST_WINDOW_SEC="${BURST_WINDOW_SEC:-120}"  # enhanced capture duration
OUTDIR="${OUTDIR:-$HOME/ai-pcap/pcap}"

read_bytes() {
  awk -v iface="$IFACE" '$1 ~ iface":" {print $2}' /proc/net/dev
}

prev=$(read_bytes)
prev_t=$(date +%s)

while true; do
  sleep "$CHECK_INTERVAL"
  now=$(read_bytes)
  now_t=$(date +%s)
  diff=$((now - prev))
  dt=$((now_t - prev_t))
  bps=$((diff / dt))
  mbps=$((bps * 8 / 1000000))
  echo "$(date -Is) $IFACE throughput ~ ${mbps}Mbps"

  if (( mbps > THRESHOLD_MBPS )); then
    echo "$(date -Is) Burst detected: ${mbps}Mbps > ${THRESHOLD_MBPS}Mbps; starting enhanced capture..."
    # Start a short, tighter-snaplen capture with full IP payload for key ports
    ENHANCED_BPF='ip and (tcp port 443 or tcp port 22 or udp port 53)'
    timeout "$BURST_WINDOW_SEC" sudo env IFACE="$IFACE" OUTDIR="$OUTDIR" SNAPLEN=256 \
      BPF_FILTER="$ENHANCED_BPF" "$HOME/ai-pcap/scripts/capture.sh" &
  fi

  prev=$now
  prev_t=$now_t
done

Run it in a tmux/screen session or as a service to layer bursts on top of your base ring buffer.


Step 3: Extract features with tshark and flag anomalies with AI

We’ll convert the most recent PCAP to lightweight flow features, then run an IsolationForest to score outliers.

Create scripts/pcap_to_csv.sh:

#!/usr/bin/env bash
set -euo pipefail

PCAP="$1"
OUTCSV="$2"

# Extract per-packet fields; we’ll aggregate in Python.
tshark -r "$PCAP" -T fields -E header=y -E separator=, -E quote=d \
  -e frame.time_epoch \
  -e ip.src -e ip.dst -e ip.proto \
  -e tcp.srcport -e tcp.dstport \
  -e udp.srcport -e udp.dstport \
  -e frame.len \
  2>/dev/null > "$OUTCSV"

Create scripts/ai_detect.py:

#!/usr/bin/env python3
import sys, json, os
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest

if len(sys.argv) < 2:
    print("Usage: ai_detect.py features.csv", file=sys.stderr)
    sys.exit(2)

csv_path = sys.argv[1]
df = pd.read_csv(csv_path)

# Normalize columns
for col in ['tcp.srcport','tcp.dstport','udp.srcport','udp.dstport']:
    if col not in df.columns: df[col] = np.nan

# Build 5-tuple-like flow key (handle udp/tcp variants)
def pick_sport(row):
    return row['tcp.srcport'] if not np.isnan(row['tcp.srcport']) else row['udp.srcport']
def pick_dport(row):
    return row['tcp.dstport'] if not np.isnan(row['tcp.dstport']) else row['udp.dstport']

df['sport'] = df.apply(pick_sport, axis=1)
df['dport'] = df.apply(pick_dport, axis=1)

# Drop rows without IPs
df = df.dropna(subset=['ip.src','ip.dst','ip.proto'])

# Aggregate per flow
grp = df.groupby(['ip.src','ip.dst','ip.proto','sport','dport'])
flows = grp.agg(
    pkt_count=('frame.len','count'),
    byte_sum=('frame.len','sum'),
    byte_mean=('frame.len','mean'),
    byte_std=('frame.len','std')
).reset_index()
flows['byte_std'] = flows['byte_std'].fillna(0.0)

# Feature matrix
X = flows[['pkt_count','byte_sum','byte_mean','byte_std']].astype(float)

# IsolationForest for unsupervised outlier detection
seed = int(os.environ.get('AI_SEED', '42'))
model = IsolationForest(
    n_estimators=200,
    contamination=float(os.environ.get('AI_CONTAM', '0.02')),
    random_state=seed
)
model.fit(X)
scores = -model.score_samples(X)  # higher => more anomalous
flows['anomaly_score'] = scores

# Top-N anomalies
topn = int(os.environ.get('AI_TOPN', '10'))
sus = flows.sort_values('anomaly_score', ascending=False).head(topn)

# Produce a compact JSON summary
out = []
for _, r in sus.iterrows():
    out.append(dict(
        src=str(r['ip.src']),
        dst=str(r['ip.dst']),
        proto=int(r['ip.proto']),
        sport=None if pd.isna(r['sport']) else int(r['sport']),
        dport=None if pd.isna(r['dport']) else int(r['dport']),
        pkt_count=int(r['pkt_count']),
        byte_sum=int(r['byte_sum']),
        score=float(r['anomaly_score'])
    ))

print(json.dumps({"anomalies": out}, indent=2))

Make scripts executable:

chmod +x scripts/pcap_to_csv.sh scripts/ai_detect.py

Create scripts/triage.sh to tie it together:

#!/usr/bin/env bash
set -euo pipefail

BASE="$HOME/ai-pcap"
PCAP_DIR="$BASE/pcap"
OUT_DIR="$BASE/out"
LOG_DIR="$BASE/logs"
VENVDIR="$BASE/venv"

mkdir -p "$OUT_DIR" "$LOG_DIR"

# Find the newest PCAP
PCAP=$(ls -1t "$PCAP_DIR"/cap-*.pcap 2>/dev/null | head -n1 || true)
if [[ -z "${PCAP}" ]]; then
  echo "$(date -Is) No PCAPs found in $PCAP_DIR" | tee -a "$LOG_DIR/triage.log"
  exit 0
fi

CSV="$OUT_DIR/$(basename "$PCAP" .pcap).csv"
JSON="$OUT_DIR/$(basename "$PCAP" .pcap)-anomalies.json"

# Convert to CSV
"$BASE/scripts/pcap_to_csv.sh" "$PCAP" "$CSV"

# Run AI
source "$VENVDIR/bin/activate"
AI_TOPN="${AI_TOPN:-10}" AI_CONTAM="${AI_CONTAM:-0.02}" python3 "$BASE/scripts/ai_detect.py" "$CSV" > "$JSON"
deactivate

# Pretty print summary with jq
echo "Latest anomalies from $(basename "$PCAP"):"
jq -r '.anomalies[] | "score=\(.score|tostring) src=\(.src) sport=\(.sport) -> dst=\(.dst) dport=\(.dport) proto=\(.proto) pkts=\(.pkt_count) bytes=\(.byte_sum)"' "$JSON" \
  | tee -a "$LOG_DIR/triage.log"

Run triage manually:

chmod +x scripts/triage.sh
./scripts/triage.sh

Expected output example:

Latest anomalies from cap-20240722T144000Z.pcap:
score=0.812 src=192.0.2.55 sport=51514 -> dst=203.0.113.10 dport=53 proto=17 pkts=184 bytes=22308
score=0.701 src=10.1.2.3 sport=445 -> dst=10.8.9.40 dport=62222 proto=6 pkts=12 bytes=1894
...

What to do with that?

  • Tighten your live capture BPF to focus on these hosts/ports for the next N minutes.

  • Auto-archive the flagged PCAP slices.

  • Notify via Slack/email.

Example: auto-archive when high-score anomalies include external destinations:

jq -r '.anomalies[] | select(.dst|startswith("10.")|not) | .dst' "$JSON" | while read dst; do
  echo "$(date -Is) External anomaly to $dst — archiving $PCAP"
  gzip -9 -c "$PCAP" > "$OUT_DIR/$(basename "$PCAP").gz"
done

Step 4: Automate with systemd timers or cron

  • systemd timer (recommended)

Create /etc/systemd/system/ai-triage.service:

[Unit]
Description=AI PCAP triage

[Service]
Type=oneshot
User=%i
ExecStart=%h/ai-pcap/scripts/triage.sh

Create /etc/systemd/system/ai-triage@.timer:

[Unit]
Description=Run AI PCAP triage periodically for %i

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

Enable for your user (replace YOURUSER):

sudo systemctl enable --now ai-triage@YOURUSER.timer
systemctl status ai-triage@YOURUSER.timer
  • Cron alternative
*/5 * * * * /home/YOURUSER/ai-pcap/scripts/triage.sh >> /home/YOURUSER/ai-pcap/logs/cron.log 2>&1

Step 5 (optional): Natural-language summaries with an LLM

If you want a quick human-readable take, you can send the JSON to an LLM. Keep secrets out of scripts and use env vars.

Example generic curl (provider-agnostic; adapt URL/headers to your LLM service):

export LLM_API_KEY="your_api_key_here"
jq -r '.' "$HOME/ai-pcap/out/"*-anomalies.json | \
curl -s https://api.your-llm-provider.example/v1/chat/completions \
  -H "Authorization: Bearer $LLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- | jq -r '.choices[0].message.content'

Prompt hint:

Summarize these network anomalies for a SOC analyst. Flag likely port scans, DNS exfiltration, or suspicious egress. Provide concrete next steps and key IPs/ports.

For completely offline setups, consider a local model runner (e.g., llama.cpp or ollama) and point curl at localhost.


Real-world example scenarios

  • Port scan burst: trigger detects bandwidth/conn burst; triage surfaces many src->dst flows with low packets per flow but broad dport fan-out. Tighten BPF to “host attacker and tcp” for 10 minutes and archive.

  • DNS tunneling suspicion: anomalies show a single internal host with unusually high DNS byte_sum and pkt_count to a single resolver. Extend capture and add BPF focus “udp port 53 and host .”

  • Data egress spike: triage spots new external dst with large byte_sum over TCP 443 from an unusual internal source. Pivot to full-payload snaplen (careful) on that tuple, notify, and preserve chain of custody.


Hardening and performance notes

  • Snaplen tradeoffs: 96–256 bytes capture headers and a bit of payload for context; go full only on-demand.

  • BPF filters first: Drop obvious noise early (backups, monitoring subnets).

  • Storage: Use a dedicated filesystem with quotas. Consider gzip or zstd after rotation.

  • Privileges: Prefer setcap over root; restrict script permissions.

  • Reproducibility: Pin Python deps in a requirements.txt or lockfile.


Conclusion and Call to Action

Packet capture is priceless—but only when it’s timely, scoped, and explainable. With a thin layer of Bash automation and a small dose of AI, you can convert rivers of packets into prioritized leads for your next investigation.

Your next steps:

  • Install the tooling and start the ring buffer capture on your busiest interface.

  • Run triage.sh hourly and review the anomalies in logs/triage.log.

  • Add one trigger (burst or port scan) and one action (archive or notify).

  • Iterate on BPF filters that fit your environment.

Want a ready-to-fork skeleton repo of the scripts in this post? Copy the structure here into ~/ai-pcap and adapt to your network. If you get stuck or want a deeper dive (Zeek integrations, Suricata alerts -> targeted PCAP, local LLM summaries), reach out and I’ll help you extend this into a production playbook.