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() { | |
47 | local -r interval="$1" | |
48 | local -r log_file="$2" | |
49 | ||
50 | while :; do | |
51 | debug '(>) scan' | |
44be784b SK |
52 | if ! sudo arp-scan --localnet; then |
53 | error 'scan failed' | |
54 | fi | |
29e5f1be SK |
55 | debug '(.) scan' |
56 | sleep "$interval"; | |
57 | done \ | |
58 | | stdbuf -o L awk ' | |
59 | /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { | |
60 | ip = $1 | |
61 | mac = $2 | |
62 | print mac, ip | |
63 | }' \ | |
64 | | ts '%.s' \ | |
65 | >> "$log_file" | |
66 | } | |
67 | ||
68 | status() { | |
69 | local -r log_file="$1" | |
70 | ||
71 | ( | |
83b73f79 SK |
72 | echo 'mac ip staleness_cur staleness_avg age freq dist' |
73 | echo '--- -- ------------- ------------- --- ---- ----' | |
29e5f1be SK |
74 | sort -n -k 1 "$log_file" \ |
75 | | awk -v now="$(date '+%s')" \ | |
76 | ' | |
77 | { | |
78 | ts = $1 | |
79 | mac = $2 | |
80 | ip = $3 | |
81 | ||
83b73f79 | 82 | interval[mac, ip, intervals[mac, ip]++] = ts - seen_last[mac, ip] |
29e5f1be SK |
83 | freq[mac, ip]++ |
84 | if (!seen_last[mac, ip] || ts > seen_last[mac, ip] ) seen_last[mac, ip] = ts | |
85 | if (!seen_first[mac, ip] || ts < seen_first[mac, ip]) seen_first[mac, ip] = ts | |
86 | } | |
87 | ||
88 | END { | |
89 | for (key in freq) { | |
90 | split(key, macip, SUBSEP) | |
91 | mac = macip[1] | |
92 | ip = macip[2] | |
83b73f79 SK |
93 | staleness_cur = now - seen_last[mac, ip] |
94 | age = now - seen_first[mac, ip] | |
29e5f1be | 95 | dist = 100 * (freq[mac, ip] / NR) |
83b73f79 SK |
96 | intervals_sum = 0 |
97 | for (i=1; i<=intervals[mac, ip]; i++) | |
98 | intervals_sum += interval[mac, ip, i] | |
99 | staleness_avg = intervals_sum / intervals[mac, ip] | |
29e5f1be SK |
100 | print \ |
101 | mac, \ | |
102 | ip, \ | |
83b73f79 SK |
103 | sprintf("%d", staleness_cur), \ |
104 | sprintf("%d", staleness_avg), \ | |
105 | sprintf("%d", age), \ | |
29e5f1be SK |
106 | freq[mac, ip], \ |
107 | sprintf("%d", dist) | |
108 | } | |
109 | } | |
110 | ' \ | |
111 | | sort -n -k 3 \ | |
112 | ) \ | |
113 | | column -t | |
114 | } | |
115 | ||
116 | main() { | |
117 | local cmd | |
118 | local interval | |
119 | local log_file | |
120 | ||
121 | case "$1" in | |
122 | '-d') | |
123 | _debug='yes' | |
124 | shift | |
125 | ;; | |
126 | esac | |
127 | cmd="$1" | |
128 | case "$cmd" in | |
129 | 'log') | |
130 | interval=60 | |
131 | log_file='/dev/stdout' | |
132 | ||
133 | if [[ -n "$2" ]]; then | |
134 | interval="$2" | |
135 | if [[ -n "$3" ]]; then | |
136 | log_file="$3" | |
137 | fi | |
138 | fi | |
139 | debug '(>) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" | |
140 | log "$interval" "$log_file" | |
141 | debug '(.) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" | |
142 | ;; | |
143 | 'status') | |
144 | log_file='/dev/stdin' | |
145 | if [[ -n "$2" ]]; then | |
146 | log_file="$2" | |
147 | fi | |
148 | debug '(>) status | log_file:"%s"' "$log_file" | |
149 | status "$log_file" | |
150 | debug '(.) status | log_file:"%s"' "$log_file" | |
151 | ;; | |
152 | *) | |
153 | error 'Unknown command: "%s"' "$cmd" | |
154 | exit 1 | |
155 | ;; | |
156 | esac | |
157 | } | |
158 | ||
159 | main "$@" |