Commit | Line | Data |
---|---|---|
29e5f1be SK |
1 | #! /bin/bash |
2 | ||
3 | set -e | |
4 | #set -u # Error on unset var | |
5 | set -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 | ||
36 | error() { | |
37 | _log 'error' "$@" | |
38 | } | |
39 | ||
40 | debug() { | |
41 | if [[ -n "$_debug" ]]; then | |
42 | _log 'debug' "$@" | |
43 | fi | |
44 | } | |
45 | ||
46 | log() { | |
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 | ||
74 | status() { | |
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 | ||
122 | main() { | |
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 | ||
165 | main "$@" |