#!/usr/bin/env bash
# capture - easy wrapper around ssh+tcpdump

. lib.bash || exit

unset ${!arg_*}

: ${tcpdump:="tcpdump"}
arg_ui=tcpdump

while (( $# )); do
	case ${1#--} in
	on)		arg_on=$2; shift;;
	-e)		arg_ethers=1;;
	-f|filter)	arg_filter=$2; shift;;
	gui)		arg_ui=wireshark;;
	host)		arg_f_host=$2; shift;;
	vlan)		arg_vlan=$2; shift;;
	name)		arg_name=$2; shift;;
	ip6ra)		arg_filter="icmp6 and ip6[40] == 134";;
	vlans)		arg_vlans=1; shift;;
	local)		arg_ui=$2; shift;;
	*)	
		if [[ $1 == */* && ! $arg_on ]]; then
			arg_on=$1
		elif [[ ! $arg_filter ]]; then
			arg_filter=$1
		else
			err "bad arg: \"$1\""
		fi;;
	esac
	shift
done

((!errors)) || exit

declare -A hosts=() interfaces=()

alias_file=${XDG_CONFIG_HOME?}/nullroute.lt/capture.aliases

if [[ -e $alias_file ]]; then
	while IFS='=' read -r key value; do
		if [[ $key == '#'* ]]; then
			continue
		elif [[ $key == '/'* ]]; then
			names=${key#/}
			for name in ${names//,/ }; do
				debug "load iface: $current_host / $name = $value"
				interfaces["$current_host/$name"]=$value
			done
		elif [[ $key ]]; then
			current_host=$key
			if [[ $value ]]; then
				debug "load host: $current_host = $value"
				hosts["$current_host"]=$value
			fi
		fi
	done < "$alias_file" || die "could not read alias file '$alias_file'"
fi

arg_host=${arg_on%%/*}
arg_iface=${arg_on#*/}

[[ $arg_host && $arg_iface && $arg_iface != */* ]] \
	|| die "host or interface not specified"

[[ ! $arg_vlan ]] || (( ! arg_vlans )) \
	|| die "'vlan …' and 'vlans' options are mutually exclusive"

target_host=${hosts["$arg_host"]:-$arg_host}
target_iface=${interfaces["$arg_host/$arg_iface"]:-$arg_iface}
echo "Capturing on '$target_iface' at '$target_host'" >&2

## craft the capture filter

filter=""
add_filter() { filter+="${filter:+ and }($*)"; }

# VLAN tag -- changes global filter state, must be first
if [[ $arg_vlan ]]; then
	add_filter "vlan $arg_vlan"
fi
# ignore current SSH connection
add_filter "not (host \$1 and host \$3 and tcp port \$2 and tcp port \$4)"
# command-line 'host' shortcut
if [[ $arg_f_host == ??:??:??:??:??:?? ]]; then
	add_filter "ether host $arg_f_host"
elif [[ $arg_f_host ]]; then
	add_filter "host $arg_f_host"
fi
# command-line filter
if [[ $arg_filter ]]; then
	add_filter "$arg_filter"
fi
# no VLAN tag -- changes global filter state, must be last
#if ! [[ $arg_vlan ]]; then
#	add_filter "not vlan"
#fi
if (( arg_vlans )); then
	filter="($filter) or (vlan and ($filter))"
fi

echo "Capture filter: $filter" >&2

## craft the remote command

rcmd="if [ \"\$ZSH_VERSION\" ]; then setopt shwordsplit; fi; set -- \$SSH_CONNECTION; $tcpdump -n -i '$target_iface' -s 65535 -U -w - -G 3600 \"$filter\""
debug "remote command: $rcmd"

if [[ $arg_ui == tcpdump ]]; then
	lcmd="tcpdump -n -r - -U"
	if (( arg_ethers )); then
		lcmd+=" -e"
	fi
	# tshark -T fields -e _ws.col.Info
elif [[ $arg_ui == wireshark ]]; then
	lcmd="wireshark -k -i -"
else
	lcmd=$arg_ui
fi
debug "local command: $lcmd"

cap_name="$target_host.$target_iface"
if [[ $arg_name ]]; then
	cap_name+=".$arg_name"
fi

capf="$HOME/tmp/capture.$cap_name.pcap"
echo "Output file: $capf" >&2

trap '[[ -s "$capf" ]] || rm -f "$capf"; exit 0' INT QUIT EXIT

if [[ $arg_ui == wireshark ]]; then
	ssh -oTCPKeepAlive=yes "root@$target_host" "$rcmd" \
		| sh -c "$lcmd"
else
	ssh -oTCPKeepAlive=yes "root@$target_host" "$rcmd" \
		| stdbuf -i0 -o0 tee "$capf" \
		| sh -c "$lcmd"
fi
