Detecting Wireless Network Changes In OS X 10.5
In versions prior to Leopard, OS X had a utility known as kicker, which allowed us the ability to specify shell scripts to run whenever the AirPort configuration changed. With it we could do clever things like automatically reconnect to VPNs, file shares and restart ssh tunnels whenever we changed locations and connected to a different access point. In 10.5 this utility was removed; this article explores one method of providing the same capabilities in Leopard via shell scripting.AirPort Info On The Command-Line
Kicker may be gone, but Apple has provided us with a comand-line utility to interrogate our Mac's wireless connection. This means that we can monitor the connection ourselves, without relying on the system to notify us of a change. How does it work? Using a little shell scripting, we can find out our current access point's SSID is. Open up a terminal window on your Mac, and paste this command into the command-line:/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -IWhat you'll see is some information about your current airport connection. Here's an example:
agrCtlRSSI: -55
agrExtRSSI: 0
agrCtlNoise: -96
agrExtNoise: 0
state: running
op mode: station
lastTxRate: 130
maxRate: 130
lastAssocStatus: 0
802.11 auth: open
link auth: wpa-psk
BSSID: 0:1d:72:ae:12:63
SSID: example
MCS: 15
channel: 4
Pretty good so far, but way more information than we require. Since all we really need to know is whether or not we've connected to a different wireless access point, we only care about the SSID. Our friend sed can help us pick out the relevant bit:
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | /usr/bin/sed -ne 's/^.*[[:space:]]SSID:[[:space:]]*\(.*\)/\1/p'`Running this command will yield only the name of the SSID, "example." Perfect! Now all we need is a way of detecting when this value changes, so we can do the things we need to do.
Detecting Changes
Let's do a little scripting:#!/bin/sh # Remember the airport command's location, for easier reading AIRPORT="/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport" LAST_SSID="" while true; do SSID=`$AIRPORT -I| /usr/bin/sed -ne 's/^.*[[:space:]]SSID:[[:space:]]*\(.*\)/\1/p'` if [ "$LAST_SSID" != "$SSID" ]; then echo "SSID Change detected: $LAST_SSID => $SSID" fi LAST_SSID="$SSID" sleep 10 doneSo what do we have here? A simple shell script that runs the airport/sed combo from the previous section to get the SSID of the access point we're currently connected to. The script then compares that value to the last SSID it remembers seeing; if they differ, we know we've changed access points. The script waits ten seconds (that's the sleep command) and checks again. Easy! Let's put it to use:
A Real-World Example: iTunes Everywhere
My iTunes library is much larger than the hard drive of my MacBook, but my file server has lots and lots of space. To take advantage of this, I host my music on a network share, which I mount before opening iTunes. This keeps iTunes happy, and my meager laptop's hard drive from being overrun.But what about when I'm not at home you say? Since I use sshfs to mount the network share, it's easy to access it from anywhere -- all I need to know is whether I'm at home or away, so I can use the fileserver's local name inside my network, and the fully-qualified domain name when I'm somewhere else. To take care of that, I wrote a script called sshfs_automount, and posted a little article about it a while back. By combining that automounter with our little SSID change detector, we can automatically remount the sshfs shares whenever we change locations (or whenever the local cafe's wireless router gets rebooted):
#!/bin/sh # Remember the airport command's location, for easier reading AIRPORT="/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport" LAST_SSID="" while true; do SSID=`$AIRPORT -I| /usr/bin/sed -ne 's/^.*[[:space:]]SSID:[[:space:]]*\(.*\)/\1/p'` if [ "$LAST_SSID" != "$SSID" ]; then echo "SSID Change detected: $LAST_SSID => $SSID; remounting network shares" # Call the sshfs_automount script, which will sort out how to remount the shares for us /Users/greg/bin/sshfs_automount fi LAST_SSID="$SSID" sleep 10 doneWith this script running in the background, wherever I am in the world I can access my iTunes library without having to fuss about with fixing the mounts whenever my connection changes.
The Complete Script: WiFi Kicker
Taking this technique and fleshing it out yields WiFi Kicker, a script you can modify to perform any task you please whenever a change in access point is detected. Here's the whole script, complete with growl support (courtesy of growlnotify):
#!/bin/sh
#
# wifi_kicker.sh
# Copyright (c) 2008, Greg Boyington <greg@automagick.us>
# All rights reserved.
#
# Source Article:
# http://mac.automagick.us/wifi_kicker.mhtml
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Greg Boyington nor automagick.us may be used to
# endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY GREG BOYINGTON ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL GREG BOYINGTON BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# version string
VERSION="WiFi Kicker v1.0"
# airport utility location
AIRPORT="/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport"
# growlnotify utility location
GROWL_CMD="growlnotify -n $0 -d $0 -t $VERSION -m "
# Your home network's SSID, signalling that internal hostnames should resolve
HOMESSID="vroomchili"
# function vmsg()
#
# send a message only if we're in VERBOSE MODE
function vmsg() {
if [ ! $VERBOSE ]; then
return
fi
msg ${*}
}
# function msg()
#
# send a message using either echo or growl
#
function msg() {
MSG_CMD="echo";
if [ $GROWL ] ; then
MSG_CMD=$GROWL_CMD;
fi
$MSG_CMD "${*}"
}
# function usage()
#
# echo program usage instructions
#
function usage() {
echo "WiFi Kicker v1.0 -- detect SSID changes and respond"
echo "Usage: $0 [-vg]"
echo " -p n poll airport status every n seconds (default: 5)"
echo " -v verbose"
echo " -g use growl for messages"
}
# function doKicks()
#
# Do something when the SSID has changed
#
function doKicks() {
# ...
}
# process the command-line args
ARGS=`getopt hvgp: $*`
set -- $ARGS
for I do
case "$I" in
-v) VERBOSE=1;
shift ;;
-g) GROWL=1;
shift ;;
-h) usage;
exit ;;
-p) POLL_INTERVAL=$2;
shift ;;
--) shift; break;
esac
done
# how often to poll airport for SSID changes
# XXX: Need to do some sanity checking here to ensure a numeric value
if [ ! $POLL_INTERVAL ]; then
POLL_INTERVAL=5
fi
vmsg "Polling airport every $POLL_INTERVAL seconds"
# main loop -- check the SSID every 5 seconds and dispatch kicks as necessary
msg "$VERSION Starting up..."
LAST_SSID=""
while true; do
SSID=`$AIRPORT --getinfo| /usr/bin/sed -ne 's/^.*[[:space:]]SSID:[[:space:]]*\(.*\)/\1/p'`
if [ "$LAST_SSID" != "$SSID" ]; then
msg "SSID Change detected: $LAST_SSID => $SSID"
doKicks
else
vmsg "SSID still $SSID"
fi
LAST_SSID="$SSID"
sleep $POLL_INTERVAL
done
WiFi Kicker supports three command-line arguments: -v to turn on verbose messages (useful for debugging), -g for routing status messages to growl instead of STDOUT, and -p, for specifying the polling interval (how long to wait before rechecking the SSID). To use it, modify the doKicks() function to take whatever actions you require, then start the script from your terminal command-line:
wifi_kicker.sh -gp 10 &
Note the trailing ampersand (&); this tells the terminal to start the script in the background -- leaving you free to close the terminal and go about your business.
You can download wifi_kicker.sh here.
License
All source code, tools and scripts on http://automagick.us is Copyright © 2007 - 2010 Greg Boyington, and licensed under aCreative Commons Attribution-Share Alike 3.0 United States License, except where otherwise noted.
