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 | ( | |
70 | echo 'mac ip last first freq dist' | |
71 | echo '--- -- ---- ----- ---- ----' | |
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 | ||
80 | freq[mac, ip]++ | |
81 | if (!seen_last[mac, ip] || ts > seen_last[mac, ip] ) seen_last[mac, ip] = ts | |
82 | if (!seen_first[mac, ip] || ts < seen_first[mac, ip]) seen_first[mac, ip] = ts | |
83 | } | |
84 | ||
85 | END { | |
86 | for (key in freq) { | |
87 | split(key, macip, SUBSEP) | |
88 | mac = macip[1] | |
89 | ip = macip[2] | |
90 | last = now - seen_last[mac, ip] | |
91 | first = now - seen_first[mac, ip] | |
92 | dist = 100 * (freq[mac, ip] / NR) | |
93 | print \ | |
94 | mac, \ | |
95 | ip, \ | |
96 | sprintf("%d", last), \ | |
97 | sprintf("%d", first), \ | |
98 | freq[mac, ip], \ | |
99 | sprintf("%d", dist) | |
100 | } | |
101 | } | |
102 | ' \ | |
103 | | sort -n -k 3 \ | |
104 | ) \ | |
105 | | column -t | |
106 | } | |
107 | ||
108 | main() { | |
109 | local cmd | |
110 | local interval | |
111 | local log_file | |
112 | ||
113 | case "$1" in | |
114 | '-d') | |
115 | _debug='yes' | |
116 | shift | |
117 | ;; | |
118 | esac | |
119 | cmd="$1" | |
120 | case "$cmd" in | |
121 | 'log') | |
122 | interval=60 | |
123 | log_file='/dev/stdout' | |
124 | ||
125 | if [[ -n "$2" ]]; then | |
126 | interval="$2" | |
127 | if [[ -n "$3" ]]; then | |
128 | log_file="$3" | |
129 | fi | |
130 | fi | |
131 | debug '(>) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" | |
132 | log "$interval" "$log_file" | |
133 | debug '(.) log | interval:"%s" log_file:"%s"' "$interval" "$log_file" | |
134 | ;; | |
135 | 'status') | |
136 | log_file='/dev/stdin' | |
137 | if [[ -n "$2" ]]; then | |
138 | log_file="$2" | |
139 | fi | |
140 | debug '(>) status | log_file:"%s"' "$log_file" | |
141 | status "$log_file" | |
142 | debug '(.) status | log_file:"%s"' "$log_file" | |
143 | ;; | |
144 | *) | |
145 | error 'Unknown command: "%s"' "$cmd" | |
146 | exit 1 | |
147 | ;; | |
148 | esac | |
149 | } | |
150 | ||
151 | main "$@" |