Back-off exponentially on scan failures
[khome.git] / home / bin / arp-recon
CommitLineData
29e5f1be
SK
1#! /bin/bash
2
3set -e
4#set -u # Error on unset var
5set -o pipefail
6
7# commands:
8# - log (to stdout or file):
9# run arp-scan and convert output to our log format
10# - options
11# - interval
12# - file
13# - status (from stdin or file):
14# read log and report
15# - seen devices, sorted by last-seen
16# - ip changes?
17# - options
18# - file
19#
20# TODO
21# - [ ] Gather more info on each device. How? nmap?
22# ...
23#
24
25_debug=''
26
27_log() {
28 local -r level="$1"; shift
29 local -r fmt="$1\n"; shift
30 local -r args="$*"
31
32 printf '%s [%s] ' "$(date '+%Y-%m-%d %H:%M:%S')" "$level" >&2
33 printf "$fmt" $args >&2
34}
35
36error() {
37 _log 'error' "$@"
38}
39
40debug() {
41 if [[ -n "$_debug" ]]; then
42 _log 'debug' "$@"
43 fi
44}
45
46log() {
ae3941ec 47 local -r interval_init="$1"
29e5f1be 48 local -r log_file="$2"
ae3941ec 49 local interval_curr="$interval_init"
29e5f1be
SK
50
51 while :; do
52 debug '(>) scan'
ae3941ec
SK
53 if sudo arp-scan --localnet; then
54 debug '(.) scan ok'
55 interval_curr="$interval_init"
56 else
57 error '(.) scan failure'
58 interval_curr=$(( interval_curr * 2 ))
44be784b 59 fi
ae3941ec
SK
60 debug '(>) sleep for %d seconds' "$interval_curr"
61 sleep "$interval_curr";
62 debug '(.) sleep'
29e5f1be
SK
63 done \
64 | stdbuf -o L awk '
65 /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {
66 ip = $1
67 mac = $2
68 print mac, ip
69 }' \
70 | ts '%.s' \
71 >> "$log_file"
72}
73
74status() {
75 local -r log_file="$1"
76
77 (
83b73f79
SK
78 echo 'mac ip staleness_cur staleness_avg age freq dist'
79 echo '--- -- ------------- ------------- --- ---- ----'
29e5f1be
SK
80 sort -n -k 1 "$log_file" \
81 | awk -v now="$(date '+%s')" \
82 '
83 {
84 ts = $1
85 mac = $2
86 ip = $3
87
83b73f79 88 interval[mac, ip, intervals[mac, ip]++] = ts - seen_last[mac, ip]
29e5f1be
SK
89 freq[mac, ip]++
90 if (!seen_last[mac, ip] || ts > seen_last[mac, ip] ) seen_last[mac, ip] = ts
91 if (!seen_first[mac, ip] || ts < seen_first[mac, ip]) seen_first[mac, ip] = ts
92 }
93
94 END {
95 for (key in freq) {
96 split(key, macip, SUBSEP)
97 mac = macip[1]
98 ip = macip[2]
83b73f79
SK
99 staleness_cur = now - seen_last[mac, ip]
100 age = now - seen_first[mac, ip]
29e5f1be 101 dist = 100 * (freq[mac, ip] / NR)
83b73f79
SK
102 intervals_sum = 0
103 for (i=1; i<=intervals[mac, ip]; i++)
104 intervals_sum += interval[mac, ip, i]
105 staleness_avg = intervals_sum / intervals[mac, ip]
29e5f1be
SK
106 print \
107 mac, \
108 ip, \
83b73f79
SK
109 sprintf("%d", staleness_cur), \
110 sprintf("%d", staleness_avg), \
111 sprintf("%d", age), \
29e5f1be
SK
112 freq[mac, ip], \
113 sprintf("%d", dist)
114 }
115 }
116 ' \
117 | sort -n -k 3 \
118 ) \
119 | column -t
120}
121
122main() {
123 local cmd
124 local interval
125 local log_file
126
127 case "$1" in
128 '-d')
129 _debug='yes'
130 shift
131 ;;
132 esac
133 cmd="$1"
134 case "$cmd" in
135 'log')
136 interval=60
137 log_file='/dev/stdout'
138
139 if [[ -n "$2" ]]; then
140 interval="$2"
141 if [[ -n "$3" ]]; then
142 log_file="$3"
143 fi
144 fi
145 debug '(>) log | interval:"%s" log_file:"%s"' "$interval" "$log_file"
146 log "$interval" "$log_file"
147 debug '(.) log | interval:"%s" log_file:"%s"' "$interval" "$log_file"
148 ;;
149 'status')
150 log_file='/dev/stdin'
151 if [[ -n "$2" ]]; then
152 log_file="$2"
153 fi
154 debug '(>) status | log_file:"%s"' "$log_file"
155 status "$log_file"
156 debug '(.) status | log_file:"%s"' "$log_file"
157 ;;
158 *)
159 error 'Unknown command: "%s"' "$cmd"
160 exit 1
161 ;;
162 esac
163}
164
165main "$@"
This page took 0.053131 seconds and 4 git commands to generate.