#!/bin/bash set x set -euo pipefail trap 'echo "[✖] Execution halted at line $LINENO. An unexpected error occurred. Please contact your nearest bash therapist." >&2' ERR IFS=$'\n\t' source settings.env FAILURE_LOG="${TEST_FILE%.csv}-failures.log" # Check if email recipient is set if [ -z "$RECIPIENT" ]; then echo "[!] Please set the RECIPIENT variable in settings.env." exit 1 fi sudo -v while true; do sudo -n true; sleep 60; done 2>/dev/null & SUDO_KEEPALIVE_PID=$! echo "Starting kismet..." sudo systemctl start kismet echo "Saturating the capture..." sleep $LEAD_TIME # Function to get current TX failed count get_tx_failed() { iw dev $INTERFACE station dump | awk '/tx failed/ {print $3}' } freq_to_channel() { local freq=$1 local channel=0 if [ "$freq" -ge 2412 ] && [ "$freq" -le 2472 ]; then channel=$(( (freq - 2407) / 5 )) elif [ "$freq" -eq 2484 ]; then channel=14 elif [ "$freq" -ge 5180 ] && [ "$freq" -le 5825 ]; then channel=$(( (freq - 5000) / 5 )) else channel="Unknown" fi echo "$channel" } # Start test email echo -e "Subject: Test ${BOOT_ID} Started\n\nThis is to inform you that the tests have commenced for test ${BOOT_ID}." | msmtp $RECIPIENT COUNTER=0 FAILED_START=$(get_tx_failed) # Create CSV header if needed if [ ! -f "$TEST_FILE" ]; then echo "StartTimestamp,EndTimestamp,Link,Level,Noise,BSSID,TX Bitrate,RX Bitrate,$(speedtest --csv-header),TX Failures,Channel,Frequency,Packet Loss,Jitter,LocalTCPUp,LocalTCPDown,LocalUDPUp,LocalUDPDown,RemoteTCPUp,RemoteTCPDown,RemoteUDPUp,RemoteUDPDown" > "$TEST_FILE" fi while [ "$COUNTER" -lt "$NUM_TESTS" ]; do COUNTER=$((COUNTER + 1)) echo "Executing test $COUNTER of $NUM_TESTS..." for ((i=1; i<=NUM_SAMPLES; i++)); do echo " Gathering sample $i of $NUM_SAMPLES..." START_TIME=$(date -Iseconds) # Wireless stats link_level_noise=$(awk 'NR==3 {gsub(/\./, "", $3); gsub(/\./, "", $4); gsub(/\./, "", $5); print $3","$4","$5}' /proc/net/wireless) bssid_and_bitrate=$(iw dev $INTERFACE link | awk '/Connected/ {bssid=$3} /tx bitrate/ {tx=$3} /rx bitrate/ {rx=$3} END {print bssid","tx","rx}') speed_results="" for ((retry=1; retry<=MAX_RETRIES; retry++)); do echo " Attempting speed test (try $retry)..." speed_results=$(speedtest --secure --csv 2>/dev/null) if [[ -n "$speed_results" ]]; then break fi echo " [!] Speedtest failed at $(date -Iseconds). Retrying in $RETRY_DELAY seconds..." sleep $RETRY_DELAY done if [[ -z "$speed_results" ]]; then TIMESTAMP=$(date -Iseconds) echo " [!] Speedtest permanently failed at $TIMESTAMP. Skipping sample $i of test $COUNTER." # Optionally log failure to a sidecar file for later nerd rage echo "$TIMESTAMP,Test $COUNTER,Sample $i" >> "${TEST_FILE%.csv}-failures.log" continue # Skip this sample fi # TX failure delta FAILED_NOW=$(get_tx_failed) FAILED_DELTA=$((FAILED_NOW - FAILED_START)) FAILED_START=$FAILED_NOW # Update for next sample freq=$(iw dev $INTERFACE link | awk '/freq:/ {print $2}') channel=$(freq_to_channel "$freq") packet_loss=$(ping -c $PING_COUNT -q $PING_TARGET | grep -oP '\d+(?=% packet loss)') jitter=$(ping -c $PING_COUNT $PING_TARGET | grep "time=" | awk '{print $(NF-1)}' | sed 's/time=//g' | awk '{sum+=$1; sumsq+=$1*$1} END {if (NR>1) print sqrt(sumsq/NR - (sum/NR)**2); else print 0}') # iperf3 function run_iperf() { local target="$1" local mode="$2" local direction="$3" local args=("-c" "$target" "-J" "-t" "10") if [ "$mode" = "udp" ]; then args+=("-u") fi if [ "$direction" = "down" ]; then args+=("--reverse") fi local result result=$(iperf3 "${args[@]}" 2>/dev/null | jq -r ' if .error then "iperf3-error" elif has("end") | not then "no-end" elif .end | has("sum_received") then .end.sum_received.bits_per_second elif .end | has("sum") then .end.sum.bits_per_second else "unexpected-format" end' || echo "execution-failed") if [[ "$result" == "iperf3-error" || "$result" == "no-end" || "$result" == "unexpected-format" || "$result" == "execution-failed" ]]; then echo "$(date -Iseconds),iperf $mode $direction to $target failed with '$result'" >> "$FAILURE_LOG" echo "0" # Fallback value for CSV else echo "$result" fi } echo " Running iperf3 tests..." # Run them all. These are in bits per second, convert as needed later. LocalTCPUp=$(run_iperf "$IPERF_LOCAL_TARGET" tcp up) LocalTCPDown=$(run_iperf "$IPERF_LOCAL_TARGET" tcp down) LocalUDPUp=$(run_iperf "$IPERF_LOCAL_TARGET" udp up) LocalUDPDown=$(run_iperf "$IPERF_LOCAL_TARGET" udp down) RemoteTCPUp=$(run_iperf "$IPERF_REMOTE_TARGET" tcp up) RemoteTCPDown=$(run_iperf "$IPERF_REMOTE_TARGET" tcp down) RemoteUDPUp=$(run_iperf "$IPERF_REMOTE_TARGET" udp up) RemoteUDPDown=$(run_iperf "$IPERF_REMOTE_TARGET" udp down) END_TIME=$(date -Iseconds) # Log everything echo "$START_TIME,$END_TIME,$link_level_noise,$bssid_and_bitrate,$speed_results,$FAILED_DELTA,$channel,$freq,$packet_loss,$jitter,$LocalTCPUp,$LocalTCPDown,$LocalUDPUp,$LocalUDPDown,$RemoteTCPUp,$RemoteTCPDown,$RemoteUDPUp,$RemoteUDPDown" >> "$TEST_FILE" done LocalTCPUp, LocalTCPDown, LocalUDPUp, LocalUDPDown, RemoteTCPUp, RemoteTCPDown ,RemoteUDPUp, RemoteUDPDown if [ "$COUNTER" -lt "$NUM_TESTS" ]; then echo "Dozing off for $TIME_BETWEEN..." sleep $TIME_BETWEEN fi done echo "Stopping kismet..." sudo systemctl stop kismet # Let's enrich the data with passive metrics. echo "Enriching the data..." KISMET_LOG=$(find ~/kismet_logs -type f -name "*.pcapng" -printf "%T@ %p\n" | sort -n | tail -1 | cut -d' ' -f2-) if [ -z "$KISMET_LOG" ] || [ ! -f "$KISMET_LOG" ]; then echo "[!] Packet capture not found." exit 1 fi python3 "$SCRIPT_DIRECTORY/enrich.py" --csv "$TEST_FILE" --pcapng "$KISMET_LOG" --output "$ENRICHED_FILE" # Final email with attachment(s) EMAIL_BODY="The test with UID ${BOOT_ID} is complete. Please collect the probe. Data is attached." EMAIL_SUBJECT="Test ${BOOT_ID} Complete" # Construct list of attachments safely ATTACHMENTS=() if [ -f "$ENRICHED_FILE" ]; then ATTACHMENTS+=("$ENRICHED_FILE") fi if [ -f "$FAILURE_LOG" ]; then ATTACHMENTS+=("$FAILURE_LOG") echo "[+] Attaching failure log: $FAILURE_LOG" fi if [ -f "$SSID_METRICS_FILE" ]; then ATTACHMENTS+=("$SSID_METRICS_FILE") echo "[+] Attaching SSID metrics file: $SSID_METRICS_FILE" fi # Check if there's at least one file to send if [ ${#ATTACHMENTS[@]} -eq 0 ]; then echo "[!] No files to attach. Email not sent." else # Print attachments for debugging for file in "${ATTACHMENTS[@]}"; do echo "[DEBUG] Attaching: '$file'" done # Safely quote and attach ATTACHMENT_FLAGS=() for file in "${ATTACHMENTS[@]}"; do ATTACHMENT_FLAGS+=("-a" "$file") done echo "$EMAIL_BODY" | mutt -s "$EMAIL_SUBJECT" "${ATTACHMENT_FLAGS[@]}" -- "$RECIPIENT" fi echo "[+] Email sent to $RECIPIENT with attachments: ${ATTACHMENTS[*]}" sudo kill $SUDO_KEEPALIVE_PID