#! /bin/bash set -e #set -u # Error on unset var set -o pipefail # commands: # - log (to stdout or file): # run arp-scan and convert output to our log format # - options # - interval # - file # - status (from stdin or file): # read log and report # - seen devices, sorted by last-seen # - ip changes? # - options # - file # # TODO # - [ ] Gather more info on each device. How? nmap? # ... # _debug='' _log() { local -r level="$1"; shift local -r fmt="$1\n"; shift local -r args="$*" printf '%s [%s] ' "$(date '+%Y-%m-%d %H:%M:%S')" "$level" >&2 printf "$fmt" $args >&2 } error() { _log 'error' "$@" } debug() { if [[ -n "$_debug" ]]; then _log 'debug' "$@" fi } log() { local -r interval_init="$1" local -r log_file="$2" local interval_curr="$interval_init" while :; do debug '(>) scan' if sudo arp-scan --localnet; then debug '(.) scan ok' interval_curr="$interval_init" else error '(.) scan failure' interval_curr=$(( interval_curr * 2 )) fi debug '(>) sleep for %d seconds' "$interval_curr" sleep "$interval_curr"; debug '(.) sleep' done \ | stdbuf -o L awk ' /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { ip = $1 mac = $2 print mac, ip }' \ | ts '%.s' \ >> "$log_file" } status() { local -r log_file="$1" ( echo 'mac ip staleness_cur staleness_avg age freq dist' echo '--- -- ------------- ------------- --- ---- ----' sort -n -k 1 "$log_file" \ | awk -v now="$(date '+%s')" \ ' { ts = $1 mac = $2 ip = $3 interval[mac, ip, intervals[mac, ip]++] = ts - seen_last[mac, ip] freq[mac, ip]++ if (!seen_last[mac, ip] || ts > seen_last[mac, ip] ) seen_last[mac, ip] = ts if (!seen_first[mac, ip] || ts < seen_first[mac, ip]) seen_first[mac, ip] = ts } END { for (key in freq) { split(key, macip, SUBSEP) mac = macip[1] ip = macip[2] staleness_cur = now - seen_last[mac, ip] age = now - seen_first[mac, ip] dist = 100 * (freq[mac, ip] / NR) intervals_sum = 0 for (i=1; i<=intervals[mac, ip]; i++) intervals_sum += interval[mac, ip, i] staleness_avg = intervals_sum / intervals[mac, ip] print \ mac, \ ip, \ sprintf("%d", staleness_cur), \ sprintf("%d", staleness_avg), \ sprintf("%d", age), \ freq[mac, ip], \ sprintf("%d", dist) } } ' \ | sort -n -k 3 \ ) \ | column -t } main() { local cmd local interval local log_file case "$1" in '-d') _debug='yes' shift ;; esac cmd="$1" case "$cmd" in 'log') interval=60 log_file='/dev/stdout' if [[ -n "$2" ]]; then interval="$2" if [[ -n "$3" ]]; then log_file="$3" fi fi debug '(>) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" log "$interval" "$log_file" debug '(.) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" ;; 'status') log_file='/dev/stdin' if [[ -n "$2" ]]; then log_file="$2" fi debug '(>) status | log_file:"%s"' "$log_file" status "$log_file" debug '(.) status | log_file:"%s"' "$log_file" ;; *) error 'Unknown command: "%s"' "$cmd" exit 1 ;; esac } main "$@"