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