-
Notifications
You must be signed in to change notification settings - Fork 1
/
channel-watch
268 lines (240 loc) · 11.2 KB
/
channel-watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/bin/bash
# shellcheck source=/dev/null
CACHE_DIR="$HOME/.config/wifi-channel-watcher/"
# pull in user config (or create it, if it doesn't exist)
if [ ! "$1" == "uninstallservice" ] && [ ! -d "$CACHE_DIR" ];
then
mkdir -p "$CACHE_DIR" && cp icon.svg "$CACHE_DIR" && cp "$PWD/config.conf" "$CACHE_DIR"config.conf && echo "Created $CACHE_DIR (first run)"
sed -i -- "s|PROCESSED_DURING_SETUP|$CACHE_DIR|g" ~/.config/wifi-channel-watcher/config.conf && echo "Set cache directory"
source "$CACHE_DIR"config.conf
elif [ -e "$CACHE_DIR"config.conf ];
then
source "$CACHE_DIR"config.conf
fi
function debug() {
if [ "$1" == "1" ];
then
STATE="INFO"
elif [ "$1" == "2" ];
then
STATE="TEST"
else
STATE="WARN"
fi
echo "$STATE: $(date) $2" >> "$DEBUG_PATH"
}
# get the channel in use
[ "$DEBUG" == "1" ] && debug "1" "-- Start of debug for this run --"
INTERFACE="$(cat < /proc/net/wireless | awk '{print $1}' | tail -n1 | tr -d .:)"
[ "$DEBUG" == "1" ] && debug "0" "Interface check: $INTERFACE triggered."
# wifi active?
if [ "$(nmcli con show --active | awk '$3 ~ "wifi" || "*wireless"')" == "" ];
then
echo "Wifi interface appears to be inactive or is not detected. Using a Pi?"
echo "For troubleshooting: https://github.com/angela-d/wifi-channel-watcher/wiki/Troubleshooting"
exit 0
fi
# check if internal resources are accessible, general local cache
[[ ! $INTERNAL_CHECK == "" && "$(ping -c 1 "$INTERNAL_CHECK")" ]] && INTERNAL_CONN="yes" || INTERNAL_CONN=""
[ "$DEBUG" == "1" ] && debug "1" "Internal connection value (empty = unmet): $INTERNAL_CONN"
# MAC address lookup vendor
function macsearch() {
# look up macs without api keys from IEEE
# cache this mac address, since it won't change (if the script runs multiple times, no need to make new calls)
if [ ! -f "$CACHE_DIR""$1" ];
then
LOOKUP=$(echo "$1" | tr -d ':' | head -c 6)
# cache the mac lookups, this will almost never change so don't litter network requests
if [ -f "$CACHE_DIR"mac.cache ];
then
cat < "$CACHE_DIR"mac.cache | grep -i "$LOOKUP" | cut -d')' -f2 | tr -d '\t' > "$CACHE_DIR""$LOOKUP"
else
if [[ $INTERNAL_CONN == 'yes' ]];
then
# no cache exists, pull a copy for a local cache
curl -L -sS -C - "$MAC_LIST" > "$CACHE_DIR"mac.cache && cat < "$CACHE_DIR"mac.cache | grep -i "$LOOKUP" | cut -d')' -f2 | tr -d '\t' > "$CACHE_DIR""$LOOKUP"
sleep 4
else
# not on the internal network, obtain from the web and cache it
curl -L -sS -C - "http://standards-oui.ieee.org/oui.txt" > "$CACHE_DIR"mac.cache && cat < "$CACHE_DIR"mac.cache | grep -i "$LOOKUP" | cut -d')' -f2 | tr -d '\t' > "$CACHE_DIR""$LOOKUP"
sleep 4
fi
fi
# if the mac doesn't match a manufacturer, it's probably spoofed
[ -s "$CACHE_DIR""$LOOKUP" ] && cat "$CACHE_DIR""$LOOKUP" || echo "(spoofed)"
fi
}
# for enterprise lookups, ideal for environments with multiple access points using the same ssid
# and you want to know specifically which ap you're connected to
function apsearch() {
if [ "$INTERNAL_CONN" == "yes" ];
then
[ "$DEBUG" == "1" ] && debug "1" "AP List: $AP_LIST"
AP=$(echo "$1" | tr -d ':')
[ "$DEBUG" == "1" ] && debug "1" "Raw BSSID: $AP"
AP=$(curl -sS "$AP_LIST" | grep -i "$AP" | head -n1 | awk '{ print $1 }')
echo "$AP"
[ "$DEBUG" == "1" ] && debug "1" "apsearch function reached. AP: $AP"
fi
}
# get nearby channel usage
ME="$(/sbin/iwgetid -r)"
CHANNEL="$(/sbin/iw dev "$INTERFACE" info | grep channel | awk '{print $2}')"
# 2g or 5g? use for channel suggestions, if enabled
RADIO="$(echo $CHANNEL | awk '$1 ~ /^1$|^6$|^11$/' | sort | uniq -c | sort -n)"
[ "$DEBUG" == "1" ] && debug "1" "My SSID: $ME - Channel: $CHANNEL"
# iw scan needs elevation to scan, so nmcli is better to use, otherwise you have an equal amount of work to do to
# pass info to the userspace from root and/or specify sudo privs in visudo
#
# get a channel list into an array (needs work)
IFS=$'\n'
# shellcheck disable=SC2207
declare -a CHAN=($(nmcli -g chan dev wi list | sort | uniq -c))
[ "$DEBUG" == "1" ] && debug "2" "Unprocessed query: $(nmcli -g chan dev wi list | sort | uniq -c)"
# isolate the active usage
for ACTIVE_CHANNEL in "${CHAN[@]}";
do
if [[ $ACTIVE_CHANNEL =~ $CHANNEL ]];
then
# isolate the total on the active channel
TOTAL_ACTIVE=$(echo "$ACTIVE_CHANNEL" | awk '{print $1}')
# it will always be +1 because of me, so subtract
TOTAL_ACTIVE=$((TOTAL_ACTIVE-1))
[ "$DEBUG" == "1" ] && debug "1" "On my channel (incl me) extract: $ACTIVE_CHANNEL"
break
fi
done
[ "$DEBUG" == "1" ] && debug "1" "Total on my channel (excl me): $TOTAL_ACTIVE"
# check for notify-send and send a notification to the user
if [ "$TOTAL_ACTIVE" -ge "$THRESHOLD" ] && [ -f /usr/bin/notify-send ];
then
[ "$DEBUG" == "1" ] && debug "1" "/usr/bin/notify-send exists"
# display the ssid, if enabled
if [ "$DISPLAY_CHANNEL_BSSID" == "1" ];
then
[ "$DEBUG" == "1" ] && debug "1" "DISPLAY_CHANNEL_BSSID is true"
# redundant loop; merge with CHAN at some point
# shellcheck disable=SC2207
declare -a DETAILED=($(nmcli -f ssid,bssid,chan,bars dev wi list | awk -v CHANNEL="$CHANNEL" -v ME="$ME" '$3 == CHANNEL' | tail -n 1))
for NEIGHBOR in "${DETAILED[@]}"
do
SSID="$(printf "%s" "$NEIGHBOR" | awk '{ print $1 }')"
# ssid is sanitized since it returns values to the screen created by untrusted individuals, because, you never know :)
[ "$SSID" == "--" ] && SSID="[hidden]" || SSID="${SSID//[^a-zA-Z0-9\_-]/}"
BSSID="$(printf "%s" "$NEIGHBOR" | awk '{ print $2 }')"
[ "$DEBUG" == "1" ] && debug "1" "BSSID in neighbor loop: $BSSID"
# get the router manufacturer
BSSID_HUMAN_READABLE=$(macsearch "$BSSID")
BARS="$(printf "%s" "$NEIGHBOR" | awk '{ print $4 }')"
[[ "$INTERNAL_CONN" == "yes" ]] && AP_NAME=$(apsearch "$BSSID") || AP_NAME=""
BSSID_DETAIL+="\\n$BARS $SSID - $BSSID_HUMAN_READABLE\\n$AP_NAME"
[ "$DEBUG" == "1" ] && debug "1" "BSSID DETAIL: $BSSID_DETAIL"
done
else
[ "$DEBUG" == "1" ] && debug "1" "WARN: DISPLAY_CHANNEL_BSSID is false, or notify-send not seen"
BSSID_DETAIL=""
fi
# assess a lesser congested channel (vacant only)
if [ "$SUGGEST_CHANNEL" == "1" ];
then
function arraydiff() {
# thx fabian lee
# https://fabianlee.org/2020/09/06/bash-difference-between-two-arrays/
awk 'BEGIN{RS=ORS=" "}
{NR==FNR?a[$0]++:a[$0]--}
END{for(k in a)if(a[k])print k}' <(echo -n "${!1}") <(echo -n "${!2}")
}
IFS='|'
# if radio is unset, user is on a 5g radio; channel selections in user config
if [ ! "$RADIO" ];
then
# look for vacant 5GHz channels; ignoring dfs/radar + wider channels incompatible w/ some devices
read -a VACANTS <<< "$CHANNELS_5G"
read -a IGNORE <<< "$IGNORE_5G"
else
# 2GHz user
read -a VACANTS <<< "$CHANNELS_2G"
read -a IGNORE <<< "$IGNORE_2G"
fi
# splitter for the arraydiff
IFS=' '
# compare vacants arr w/ chan array to spot any unused channels
# shellcheck disable=SC2207
SUGGESTING=($(arraydiff CHAN[@] VACANTS[@]))
[ "$DEBUG" == "1" ] && debug "1" "Channels that appear vacant: ${SUGGESTING[*]}"
for ANALYZE in "${SUGGESTING[@]}";
do
[ "$DEBUG" == "1" ] && debug "1" "Channel suggestion (unfiltered/active channel): $ANALYZE"
# 40 gets regex clipped due to the presence of dfs 140 in ignore, despite shellcheck claiming this is a literal match
# shellcheck disable=SC2076
if [[ ! ${IGNORE[*]} =~ "$ANALYZE" ]] || [[ "$ANALYZE" -eq 40 && ! "$RADIO" ]]
then
SUGGEST="$ANALYZE"
fi
done
[ "$DEBUG" == "1" ] && debug "1" "Channel to suggest: $SUGGEST"
IFS=$'\n'
if [ -z "$SUGGEST" ] && [ "$RADIO" ];
then
# make a 2g suggestion based off the least-trafficked channel
CONGESTED="$(nmcli -f chan dev wi list | awk '$1 ~ /^1$|^6$|^11$/' | sort | uniq -c | sort -n | head -n1)"
SUGGEST="$(printf "%s" $CONGESTED | awk '{ print $2 }')"
[ "$DEBUG" == "1" ] && debug "1" "INFO: Congested 2G chanels: $CONGESTED"
elif [ -z "$SUGGEST" ] && [ ! "$RADIO" ];
then
# make a 5g suggestion based off the least-trafficked channel
CONGESTED="$(nmcli -f chan dev wi list | awk '$1 ~ /^36$|^40$|^44$|^48$|^149$|^153$|^157$|^161$|^165$/' | sort | uniq -c | sort -n | head -n1)"
SUGGEST="$(printf "%s" $CONGESTED | awk '{ print $2 }')"
fi
# if the suggested channel is the active channel, don't notify
[ "$SUGGESTED" == "$CHANNEL" ] && HIDE_PROMPT="true" && [ "$DEBUG" == "1" ] && debug "1" "You are on the best channel: $SUGGESTED"
SUGGESTED="\\nBetter channel: $SUGGEST"
fi
if [ -z "$HIDE_PROMPT" ];
then
# format the message to prevent notify-send from throwing errors
PROMPT="$TOTAL_ACTIVE neighboring APs also on channel $CHANNEL\\n$BSSID_DETAIL $SUGGESTED"
[ "$DEBUG" == "1" ] && debug "1" "Prompt output: $PROMPT"
# send the notification
notify-send -i "$HOME/.config/wifi-channel-watcher/icon.svg" "Wifi Channel Not Optimal" \
-u critical \ "$PROMPT"
fi
fi
[ "$DEBUG" == "1" ] && debug "1" "INFO: -- End of debug for this run --"
if [ "$1" == "installservice" ];
then
# checking for systemd
echo "Checking for systemd..."
if [ -d /usr/lib/systemd ];
then
echo "Found. Creating systemd service directory..."
[ ! -d ~/.config/systemd/user/ ] && mkdir -p ~/.config/systemd/user/
[ -f "$PWD/service/systemd/wifiwatcher.service" ] && cp "$PWD/service/systemd/wifiwatcher.service" ~/.config/systemd/user/wifiwatcher.service && echo "Unit file added."
[ -f "$PWD/service/systemd/wifiwatcher.timer" ] && cp "$PWD/service/systemd/wifiwatcher.timer" ~/.config/systemd/user/wifiwatcher.timer && echo "Timer file added."
echo "Modifying the path to this script (you'll need to modify the service file if you move the repo on your system)..."
echo -e "\\t$PWD/channel-watch"
sleep 3
sed -i -- "s|SERVICE_PATH|$PWD/channel-watch|g" ~/.config/systemd/user/wifiwatcher.service
echo "Enabling service..."
systemctl --user enable wifiwatcher && echo "Enabling scheduler (default every 10min, see wiki for adjusting)"
systemctl --user enable wifiwatcher.timer && echo "Starting services..."
systemctl --user start wifiwatcher --now && systemctl --user start wifiwatcher.timer --now && echo "Service setup complete!"
fi
elif [ "$1" == "uninstallservice" ];
then
# checking for systemd
echo "Checking for systemd..."
if [ -d /usr/lib/systemd ];
then
echo "Found. Stopping services..."
systemctl --user stop wifiwatcher.timer
systemctl --user stop wifiwatcher
systemctl --user disable wifiwatcher.timer && systemctl --user disable wifiwatcher && echo "Removed wifiwatcher from startup"
echo "Removing service files for wifiwatcher..."
[ -e ~/.config/systemd/user/wifiwatcher.service ] && rm ~/.config/systemd/user/wifiwatcher.service && echo "wifiwatcher service removed"
[ -e ~/.config/systemd/user/wifiwatcher.timer ] && rm ~/.config/systemd/user/wifiwatcher.timer && echo "wifiwatcher timer removed"
echo "Checking for cache directory..."
[ -d ~/.config/wifi-channel-watcher ] && rm -rf ~/.config/wifi-channel-watcher && echo "Cache directory removed"
echo "Uninstall complete!"
fi
fi