Last active
November 19, 2022 17:04
-
-
Save jas-/4a777da24e7d0dec9db44c73f3353a4f to your computer and use it in GitHub Desktop.
A semi self aware packet trace tool; supports multiple packet capture tools such as tcpdump, tshark & snoop, monitors log disk for space constraints & can be configured to run for only a specified amount of time.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
############################################################# | |
# Functional description: | |
# Automates packet captures while safely monitoring disk | |
# space constraints for a specified amount of time. | |
# | |
# Supported OS: | |
# Solaris 10/11 | |
# RHEL 5/6/7/8 | |
# Ubuntu | |
# OEL | |
# | |
# Supported packet capture tools (in order or preference): | |
# tcpdump | |
# tshark (least tested option) | |
# snoop | |
# | |
# Configurable items: | |
# - Trace network traffic for how long? | |
# - What interfaces are we expected to capture on? | |
# - What systems should we capture traffic on? | |
# - PCAP save location; i.e. /var/tmp/pcap/<date>/ | |
# - Safe disk capacity threshold? i.e. 80% capacity | |
############################################################# | |
# How long should we capture? | |
how_long="30m" | |
# We could use some interface names | |
declare -a ifaces | |
ifaces+=("enps0") | |
ifaces+=("enps2") | |
ifaces+=("enps5") | |
# What systems should we target? | |
# If you want the global include it here | |
# This list limits captures per system/zone and | |
# filters captures by host(s) on ${ifaces[@]} | |
declare -a systems | |
systems+=("host1") | |
systems+=("host2") | |
systems+=("host3") | |
systems+=("host4") | |
# Where do we save to? | |
save_path=/var/tmp/pcap | |
# At what percentage do we stop a capture? This is a percentage value | |
# Helps eliminate long running captures from exceeding disk capacity | |
capacity_threshold=80 | |
# Ensure path is robust, but isn't silly | |
declare -a proposed_paths | |
proposed_paths="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" | |
all_paths=( $(echo $PATH:${proposed_paths} | | |
cut -d: -f1 | tr ' ' '\n' | sort -u) ) | |
PATH="$(echo "${all_paths[@]}" | tr ' ' ':')" | |
############################################################# | |
# Make sure we should be operating on this system | |
############################################################# | |
# Create a filter for zoneadm list to limit scope of capture to | |
efilter="$(echo "${systems[@]}" | tr ' ' '|')" | |
# Acquire an array of zones to capture for | |
zones=( $(zoneadm list 2>/dev/null | egrep -i ${efilter}) ) | |
# Handle non Solaris as well | |
[ ${#zones[@]} -eq 0 ] && | |
zones+=( $(uname -n | cut -d"." -f1 | egrep -i ${efilter}) ) | |
# If nothing configured matches our environment we should exit early | |
if [ ${#zones[@]} -eq 0 ]; then | |
echo "Error: Configured list of systems doesn't match our running env., did you configure the systems names for packet captures?" | |
exit 1 | |
fi | |
############################################################# | |
# Functions to assist with repetitive tasks | |
############################################################# | |
# @description Kilobytes to bytes | |
# | |
# @arg ${1} Integer | |
# | |
# @stdout Integer | |
function aa_kb2b() | |
{ | |
echo "${1} * 1024" | bc 2>/dev/null | |
} | |
# @description Megabytes to bytes | |
# | |
# @arg ${1} Integer | |
# | |
# @stdout Integer | |
function aa_mb2b() | |
{ | |
echo "${1} * 1024 * 1024" | bc 2>/dev/null | |
} | |
# @description Gigabytes to bytes | |
# | |
# @arg ${1} Integer | |
# | |
# @stdout Integer | |
function aa_gb2b() | |
{ | |
echo "${1} * 1024 * 1024 * 1024" | bc 2>/dev/null | |
} | |
# @description Terabytes to bytes | |
# | |
# @arg ${1} Integer | |
# | |
# @stdout Integer | |
function aa_tb2b() | |
{ | |
echo "${1} * 1024 * 1024 * 1024 * 1024" | bc 2>/dev/null | |
} | |
# @description Petabytes to bytes | |
# | |
# @arg ${1} Integer | |
# | |
# @stdout Integer | |
function aa_pb2b() | |
{ | |
echo "${1} * 1024 * 1024 * 1024 * 1024 * 1024" | bc 2>/dev/null | |
} | |
# @description Perform conversion from requested size to bytes | |
# | |
# @arg ${1} String - Size (should end in K/M/G/T/P) | |
# | |
# @stdout Integer | |
function ab_convert() | |
{ | |
local size="${1}" | |
local bytes= | |
case "${size}" in | |
*K) bytes=$(aa_kb2b ${size/K}) ;; | |
*M) bytes=$(aa_mb2b ${size/M}) ;; | |
*G) bytes=$(aa_gb2b ${size/G}) ;; | |
*T) bytes=$(aa_gb2b ${size/T}) ;; | |
*P) bytes=$(aa_pb2b ${size/P}) ;; | |
esac | |
echo ${bytes} | cut -d"." -f1 | |
} | |
# @description Percentage | |
# | |
# @arg ${1} Integer | |
# @arg ${2} Integer | |
# @arg ${3} Integer Rounding | |
# | |
# @stdout Integer | |
function ab_percent() | |
{ | |
local total=${1} | |
local value=${2} | |
local scale=${3} | |
# Rounds decimals | |
echo "scale=${scale:=2}; 100 * ${value} / ${total}" | | |
bc 2>/dev/null | grep -v "^divide" | awk '{printf "%3.0f\n", $1}' | |
} | |
# @descripion Calculate disk capacity percentage | |
# | |
# @arg ${1} Integer - Disk total in bytes | |
# @arg ${2} Integer - Used space in bytes | |
# | |
# @stdout Integer | |
function disk_space_threshold_reached() | |
{ | |
# Locally scope our variables | |
local blob | |
local total | |
local available | |
# What is the space available for ${save_path}? | |
if [ $(uname -a | grep -c SunOS) -gt 0 ]; then | |
blob="$(zfs list -Ho space ${save_path} | awk '{printf("%s:%s\n", $2, $3)}')" | |
else | |
blob="$(df -h ${save_path} | awk 'NR>1{printf("%s:%s\n", $2, $4)}')" | |
fi | |
# Convert values to bytes for easy math | |
total=$(ab_convert $(echo "${blob}" | cut -d: -f1)) | |
available=$(ab_convert $(echo "${blob}" | cut -d: -f2)) | |
# We should bail if ${save_path} exceeds ${capacity_threshold} percentage | |
if [ $(ab_percent ${total} ${available}) -gt ${capacity_threshold} ]; then | |
echo 1 && return | |
fi | |
echo 0 | |
} | |
############################################################# | |
# Make sure we have enough disk space or build the save location | |
############################################################# | |
# Does the current save locations disk exceed space constraints? | |
if [ $(disk_space_threshold_reached) -eq 1 ]; then | |
echo "Error: Current save location disk space exceeds safe threshold" | |
echo " Save Path: ${save_path} >${capacity_threshold}% capacity" | |
exit 1 | |
fi | |
# Setup some environment for captures and meta data for files | |
ts=$(date +%Y%m%d-%H%M%S) | |
d=${save_path}/$(date +%Y%m%d) | |
[ ! -d ${d} ] && mkdir -p ${d} | |
############################################################# | |
# Make sure we have a tool to capture with | |
############################################################# | |
# What tool is available? Prefer tcpdump | |
sniffer="$(which tcpdump 2>/dev/null)" | |
# If ${sniffer} is null look for tshark | |
[ "${sniffer}" = "" ] && | |
sniffer="$(which tshark 2>/dev/null)" | |
# If ${sniffer is still null look for snoop | |
[ "${sniffer}" = "" ] && | |
sniffer="$(which snoop 2>/dev/null)" | |
# Do we even have a sniffer to use? Bail if not | |
if [ "${sniffer}" = "" ]; then | |
echo "Error: Unable to find tcpdump/tshark/snoop tool(s)" | |
exit 1 | |
fi | |
############################################################# | |
# Begin the main iterator; in most cases this will be one system | |
############################################################# | |
# Iterate ${zones[@]} and do work | |
for zone in ${zones[@]}; do | |
############################################################# | |
# Now begin iterating all defined interfaces for the capture | |
############################################################# | |
# Iterate ${ifaces[@]} | |
for iface in ${ifaces[@]}; do | |
############################################################# | |
# Seek the interface; Order of preference: dladm, ipadm, ip addr & ifconfig | |
############################################################# | |
# Prefer matching with dladm | |
[[ $(echo "${zone}" | grep -c "global") -eq 0 ]] && [[ $(zonename | grep -c "global") -eq 0 ]] && | |
interface="$(dladm 2>/dev/null | grep "^${iface}" | awk '$1 !~ /\//{print $1}')" || | |
interface="$(dladm 2>/dev/null | grep "^${zone}" | grep "${iface}" | awk '{print $1}')" | |
# Secondary use ipadm | |
[ "${interface}" = "" ] && | |
interface="$(ipadm 2>/dev/null | grep "^${iface}" | awk '{print $1}' | | |
cut -d"/" -f1 | sort -u | head -1)" | |
# Tertiary use ip | |
[ "${interface}" = "" ] && | |
interface="$(ip addr 2>/dev/null | grep "^${iface}" | awk '{print $1}' | | |
cut -d"/" -f1 | sort -u | head -1)" | |
# As a last resort check ifconfig | |
[ "${interface}" = "" ] && | |
interface="$(ifconfig -a 2>/dev/null | grep "^${iface}" | awk '{print $1}' | | |
cut -d"/" -f1 | sort -u | head -1 | sed 's/:$//g')" | |
# Skip on 0 interfaces | |
[ "${interface}" = "" ] && | |
continue | |
# Create a valid filename from ${interface} | |
fname_interface="$(echo "${interface}" | tr '/' '_')" | |
############################################################# | |
# Build the command string based on whatever tool exists; we are only capturing the packet header btw | |
############################################################# | |
# Since tools use different args build them here | |
case "${sniffer}" in | |
*tcpdump) | |
args="-i ${interface} -Z root -C 100 -s 128 -w ${d}/${zone}-${fname_interface}-${ts}.pcap" ;; | |
*tshark) | |
args="-i ${interface} -s 128 -w ${d}/${zone}-${fname_interface}-${ts}.pcap -f " ;; | |
*snoop) | |
args="-d ${interface} -s 128 -o ${d}/${zone}-${fname_interface}-${ts}.pcap" ;; | |
esac | |
############################################################# | |
# Robot does some work where applicable | |
############################################################# | |
# Since tshark is weird, pull the -f from the ${args} | |
args="$(echo "${args}" | sed 's/\.pcap \-f /\.pcap/g')" | |
# Start trace | |
${sniffer} ${args} & | |
done | |
done | |
############################################################# | |
# Monitor the disk space and bail if necessary | |
############################################################# | |
# Temporarily enable the extended debug option | |
shopt -s extdebug | |
# Generate a monitoring script to poll disk space constraints | |
cat <<_eof_ > ${save_path}/poll.sh | |
#!/bin/bash | |
# Ensure path is robust | |
PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin | |
# Variables | |
save_path=${save_path} | |
capacity_threshold=${capacity_threshold} | |
# Functions | |
$(declare -f) | |
# Poll the disk to ensure capacity is never reached and kill the pcaps if it does | |
while [ true ]; do | |
echo "Polling disk capacity: $(date +%Y%m%d-%H%M%s)" | |
if [ \$(disk_space_threshold_reached) -eq 1 ]; then | |
echo "Disk capacity threshold reached! Bye Felicia" | |
ps -ef | grep -v grep | egrep 'poll\.sh|${sniffer}' | awk '{print $2}' | sort -nr | xargs -iP kill -9 P | |
exit | |
fi | |
sleep 5 | |
done | |
_eof_ | |
# Disable the extended debug option | |
shopt -u extdebug | |
# Make sure it is executable | |
chmod +x ${save_path}/poll.sh | |
# Kick the poller into the background and log | |
${save_path}/poll.sh 2>&1 >${save_path}/${ts}.log & | |
############################################################# | |
# Sleep until our timer runs out and clean ourselves up | |
############################################################# | |
# Go to sleep robot | |
sleep ${how_long} | |
# Wake up and do some damage | |
ps -ef | grep -v grep | egrep 'poll\.sh|${sniffer}' | awk '{print $2}' | sort -nr | xargs -iP kill -9 P | |
# Clean up after yourself | |
[ -f ${save_path}/poll.sh ] && | |
rm -f ${save_path}/poll.sh | |
# bye robot | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment