Skip to content

Instantly share code, notes, and snippets.

@skull-squadron
Created March 14, 2024 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skull-squadron/165a288f7092fd23a04d6f4d3defcf27 to your computer and use it in GitHub Desktop.
Save skull-squadron/165a288f7092fd23a04d6f4d3defcf27 to your computer and use it in GitHub Desktop.
macOS Ventura/Sonoma auto Ethernet/Wi-Fi switching

Auto Ethernet/Wi-Fi switching script for macOS

! CRITICAL PROBLEM ! Currently broken because ...../airport -z no longer functions and the replacement is uncertain and nonexistent for now

Installation

  1. Add local.auto-switch-wifi-ethernet.zsh to /usr/local/sbin
  2. Add local.auto-switch-wifi-ethernet.plist to /Library/LaunchDaemons
  3. Create /usr/local/.home.wifi.ssid containing the Wi-Fi network name (SSID) to try to connect to when Ethernet is no longer internet reachable
  4. Start it permanently sudo launchctl unload -w /Library/LaunchDaemons/local.auto-switch-wifi-ethernet.plist

Uninstallation

sudo launchctl unload -w /Library/LaunchDaemons/local.auto-switch-wifi-ethernet.plist
sudo rm -f /Library/LaunchDaemons/local.auto-switch-wifi-ethernet.plist \
           /usr/local/sbin/local.auto-switch-wifi-ethernet.zsh \
           /usr/local/.home.wifi.ssid
           
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.auto-switch-wifi-ethernet</string>
<key>Program</key>
<string>/usr/local/sbin/local.auto-switch-wifi-ethernet.zsh</string>
<key>Nice</key>
<integer>20</integer>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/dev/null</string>
<key>StandardOutPath</key>
<string>/dev/null</string>
</dict>
</plist>
#!/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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment