|
#!/bin/zsh |
|
set -euo pipefail |
|
set -x |
|
|
|
|
|
if [ $UID != 0 ]; then |
|
exec /usr/bin/sudo "$0" "$@" |
|
fi |
|
|
|
network_service_to_bsd_device() { |
|
/usr/sbin/networksetup -listnetworkserviceorder | grep -A1 ") $1$" | awk '/, Device/{gsub("[()]","");print $NF}' |
|
} |
|
|
|
#### Configuration |
|
WIFI=$(network_service_to_bsd_device Wi-Fi) |
|
DEFAULT_SSID=$(cat /usr/local/.home.wifi.ssid || { echo >&2 'Missing /usr/local/.home.wifi.ssid'; exit 1; }) |
|
#### Configuration |
|
|
|
|
|
airport_getnetwork() { |
|
/usr/sbin/networksetup -getairportnetwork "$WIFI" \ |
|
| /usr/bin/sed 's/^Current Wi-Fi Network: //' |
|
} |
|
|
|
find_ip() { |
|
local DEV=$(network_service_to_bsd_device "$1") |
|
echo >&2 "find_ip $DEV -> $1" |
|
{ /sbin/ifconfig "$DEV" inet 2>/dev/null || true ; } \ |
|
| /usr/bin/sed -E '/^[[:space:]]+inet /!d;s/[[:space:]]+inet ([0-9.]+).*/\1/;/^169\.254\./d;/^0\.0\.0\.0/d' |
|
} |
|
|
|
default_gateway_for_interface() { |
|
/usr/sbin/netstat -rn | /usr/bin/awk "/default/ && /${1//\//\/}$/{print\$2}" |
|
} |
|
|
|
disassociate() { |
|
if ! is_associated; then # wifi is already disconnected |
|
return |
|
fi |
|
echo "On ethernet" |
|
ORIG_SSID=$(airport_getnetwork) |
|
echo "Disabling wifi network $WIFI" |
|
# TODO FIXME currently broken! |
|
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -z |
|
} |
|
|
|
airport_power() { |
|
/usr/sbin/networksetup -getairportpower "$WIFI" | grep -q ': On' |
|
} |
|
|
|
toggle_airpower() { |
|
if airport_power; then |
|
echo "Connecting to default wifi network" |
|
/usr/sbin/networksetup -setairportpower "$WIFI" off |
|
sleep 1 |
|
/usr/sbin/networksetup -setairportpower "$WIFI" on |
|
sleep 10 |
|
else |
|
echo "Not connecting to wifi because it was off previously" |
|
fi |
|
} |
|
|
|
connect_to_network() { |
|
echo "Connecting to previous wifi network $1" |
|
local PASSWD=$(/usr/bin/security find-generic-password -D "AirPort network password" -wa "$1") |
|
|
|
for ((TRIES = 0; TRIES < 3; TRIES++)); do |
|
/usr/sbin/networksetup -setairportnetwork "$WIFI" "$1" "$PASSWD" |
|
sleep 1 |
|
if is_associated; then |
|
echo >&2 'Connected OK' |
|
return 0 |
|
fi |
|
done |
|
|
|
echo >&2 'Unable to connect to wifi network' |
|
return 1 |
|
} |
|
|
|
associate() { |
|
if is_associated; then |
|
return |
|
fi |
|
echo "On Wi-Fi" |
|
if [ -n "${ORIG_SSID-}" ]; then |
|
connect_to_network "$ORIG_SSID" && return |
|
fi |
|
if [ -n "${DEFAULT_SSID-}" ]; then |
|
connect_to_network "$DEFAULT_SSID" && return |
|
fi |
|
toggle_airpower |
|
} |
|
|
|
|
|
is_associated() { |
|
! /usr/sbin/networksetup -getairportnetwork "$WIFI" | grep -q 'You are not associated with an AirPort network.' |
|
} |
|
|
|
is_network_reachable() { |
|
if [ -z "$1" ]; then |
|
echo >&2 'is_network_reachable parameter error: BSD network device missing' |
|
return 1 |
|
fi |
|
local gateway=$(default_gateway_for_interface "$1") |
|
[ -n "$gateway" ] || return 1 |
|
nc -z "$gateway" 443 -b "$1" -G 2 |
|
} |
|
|
|
is_internet_reachable() { |
|
if [ -z "$1" ]; then |
|
echo >&2 'is_internet_reachable parameter error: BSD network device missing' |
|
return 1 |
|
fi |
|
echo >&2 "Checking network reachability for internet '$1'" |
|
/usr/bin/nc -z 8.8.8.8 443 -b "$1" -G 2 |
|
} |
|
|
|
wifi_device() { |
|
network_service_to_bsd_device 'Wi-Fi' |
|
} |
|
|
|
is_internet_reachable_from_wifi() { |
|
is_internet_reachable $(wifi_device) |
|
} |
|
|
|
has_ethernet() { |
|
sleep 1 |
|
local ETHS=() |
|
readarray -t ETHS < <(/usr/sbin/networksetup -listallnetworkservices | sed '/^\*/d;/^An asterisk/d;/^Wi-Fi/d') |
|
local HAS_ETH= |
|
local E |
|
for E in "${ETHS[@]}"; do |
|
HAS_ETH=$(find_ip "$E") |
|
if [ -n "$HAS_ETH" ]; then |
|
echo "Active ethernet device found '$E'" |
|
ETH="$E" |
|
return 0 |
|
fi |
|
done |
|
return 1 |
|
} |
|
|
|
while :; do |
|
if has_ethernet; then |
|
DEV=$(network_service_to_bsd_device "$ETH") |
|
if is_internet_reachable "$DEV"; then |
|
disassociate |
|
continue |
|
fi |
|
# TODO: handle cases where internet isn't reachable from ethernet |
|
else |
|
# TODO: handle precondition cases where association isn't desired and to prevent flapping |
|
associate |
|
fi |
|
sleep 2 |
|
done |