I often find myself changing routes on my OS X system. Usually, however, I’m systematically adding the same routes over and over again, because they were automatically removed after a reboot, network change or whatever. Enter LocationChanger.
It’s basically just a LaunchAgent that gets called every time something network-related changes. Just add this content to ~/Library/LaunchAgents/LocationChanger.plist (or another name, if you prefer).
Label tech.inhelsinki.nl.locationchanger ProgramArguments /Users/USERNAME/bin/locationchanger WatchPaths /Library/Preferences/SystemConfiguration
And either reboot or run launchctl load ~/Library/LaunchAgents/LocationChanger.plist.
This will execute the named script (~/bin/locationchanger) every time the network setup changes. In this script you can do whatever you want. Here are some snippets of code that I use:
#!/bin/bash
GROWL_TITLE="Network location detected"
PATH="$PATH:/usr/local/bin/" # for growlnotify
sleep 2 # Wait for things to settle
SSID="$( /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -I |
grep ' SSID:' | cut -d ':' -f 2 | tr -d ' ' )"
EN0IP=`ifconfig en0 | grep 'inet ' | cut -d' ' -f 2`
EN1IP=`ifconfig en1 | grep 'inet ' | cut -d' ' -f 2`
SSIDS_AROUND="$( /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s |
( read HEADER; cat ) )"
logger "Location changer: SSID=$SSID; en0=$EN0IP; en1=$EN1IP;"
function bits_to_mask() {
BITS="$1"
if [ $BITS -le 0 ]; then
echo "0"
return
fi
case $BITS in
1) echo "128";;
2) echo "192";;
3) echo "224";;
4) echo "240";;
5) echo "248";;
6) echo "252";;
7) echo "254";;
*) echo "255";;
esac
}
function bits_to_subnet() {
BITS="${1:-32}"
SN="$( bits_to_mask $BITS )"
BITS="$(( $BITS - 8 ))"
SN="$SN.$( bits_to_mask $BITS )"
BITS="$(( $BITS - 8 ))"
SN="$SN.$( bits_to_mask $BITS )"
BITS="$(( $BITS - 8 ))"
SN="$SN.$( bits_to_mask $BITS )"
echo $SN
}
function add_routes() {
GW="$1"
while read r; do
r="${r/\#*}" # remove comments
if [ -z "$r" ]; then
: # Do nothing on empty lines
else
# Does it look like "IP subnetmask" ?
if echo "$r" |
grep "^[[:space:]]*[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+[[:space:]]\+[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+[[:space:]]*$"; then
ADDR="$( echo "$r" | cut -d' ' -f1 )"
MASK="$( echo "$r" | cut -d' ' -f2 )"
sudo route add -net -proto1 $ADDR $GW $MASK # proto1 is to recognize them when cleaning up
# Does it look like "IP/mask"?
elif echo "$r" |
grep "^[[:space:]]*[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/[[:digit:]]\+[[:space:]]*$"; then
ADDR="$( echo "$r" | cut -d/ -f1 )"
MASK="$( bits_to_subnet $( echo "$r" | cut -sd/ -f2 ) )"
sudo route add -net -proto1 $ADDR $GW $MASK # proto1 is to recognize them when cleaning up
# Does it look like "IP"?
elif echo "$r" |
grep "^[[:space:]]*[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+[[:space:]]*$"; then
ADDR="$r"
sudo route add -host -proto1 $ADDR $GW # proto1 is to recognize them when cleaning up
else
# probably a hostname
host "$r" | grep "has address" | sed 's/.*has address //' | while read ip; do
sudo route add -host -proto1 $ip $GW
done
fi
fi
done
}
function cleanup_routes() {
netstat -nrf inet | perl -ne '@f=split/ +/; print $_ if $f[2] =~ m/1/;' |
while read r; do
GW="$( echo "$r" | awk '{print $2}' )"
if echo "$r" | awk '{print $1}' | grep '/' >/dev/null; then
# IP/mask style
ADDR="$( echo "$r" | cut -d' ' -f1 | cut -d/ -f1 )"
ADDR="$( echo "$ADDR.0.0.0" | perl -pe 's/(\d+\.\d+\.\d+\.\d+)[\d.]*/$1/' )"
BITS="$( echo "$r" | cut -d' ' -f1 | cut -d/ -f2 )"
MASK="$( bits_to_subnet $BITS )"
sudo route delete $ADDR $GW $MASK
else
# figure out mask from class
ADDR="$( echo "$r" | cut -d' ' -f1 )"
ADDR="$( echo "$ADDR.0.0.0" | perl -pe 's/(\d+\.\d+\.\d+\.\d+)[\d.]*/$1/' )"
FIRST_OCTET="$( echo "$ADDR" | cut -d. -f1 )"
if [ $FIRST_OCTET -lt 128 ]; then
MASK="255.0.0.0" # Class A
elif [ $FIRST_OCTET -lt 192 ]; then
MASK="255.255.0.0" # Class B
elif [ $FIRST_OCTET -lt 224 ]; then
MASK="255.255.255.0" # Class C
elif [ $FIRST_OCTET -lt 240 ]; then
MASK="" # Class D
else
MASK="" # Class E
fi
sudo route delete $ADDR $GW $MASK
fi
done
}
cleanup_routes
# Possible location scores
LOCATIONS=(other HOME WORK)
i=0; while [ $i -lt ${#LOCATIONS[@]} ]; do
SCORE[$i]=0
i=$(($i+1))
done
function location_to_index() {
i=0; while [ $i -lt ${#LOCATIONS[@]} ]; do
if [ "${LOCATIONS[$i]}" == "$1" ]; then
echo $i
return
fi
i=$(($i+1))
done
echo "Invalid location $1" >&2
}
function add_score() {
LOC="$1"
D="$2"
I="$( location_to_index $LOC )"
S="${SCORE[$I]}"
S="$(( $S $D ))"
SCORE[$I]=$S
}
# Am I connected to the WORK network?
REPLY="$( dig +timeout=1 +tries=1 @10.1.1.1 intranet.work.org )"
if [ $? -eq 0 ]; then
# Got reply, is in NXDOMAIN or NOERROR?
if echo "$REPLY" | grep "status: NOERROR" > /dev/null; then
add_score WORK "+1"
else
add_score WORK "-1"
fi
fi
# Is the WORK wifi around?
if echo "$SSIDS_AROUND" | grep "WORK" > /dev/null; then
add_score WORK "+1"
fi
# Am I associated to home
if [ "$SSID" == "home" ]; then
add_score HOME "+1"
else
add_score HOME "-1"
fi
# Is neighbour around
if echo "$SSIDS_AROUND" | grep "neighbor" > /dev/null; then
add_score HOME "+1"
fi
SCORES=""
MAX_SCORE=0
MAX_SCORE_INDEX=
i=0; while [ $i -lt ${#LOCATIONS[@]} ]; do
SCORES="$SCORES ${LOCATIONS[$i]}=${SCORE[$i]}"
if [ ${SCORE[$i]} -gt $MAX_SCORE ]; then
MAX_SCORE=${SCORE[$i]}
MAX_SCORE_INDEX=$i
fi
i=$(($i+1))
done
LOCATION=${LOCATIONS[$MAX_SCORE_INDEX]}
logger "Location scores:$SCORES"
logger "Conclusion: $LOCATION"
growlnotify -t "$GROWL_TITLE" -m - <<-EOT
Scores:$SCORES
Conclusion: $LOCATION
EOT
case $LOCATION in
WORK)
# Is the VPN tunnel up?
if ps ax | grep 'openvpn.*OUTSIDE' | grep -v grep >/dev/null; then
# Route around firewall
add_routes 192.168.102.5 <<-EOR
# Amazon Web Services
# Source: https://forums.aws.amazon.com/ann.jspa?annID=1408
# US East (Northern Virginia):
72.44.32.0/19
67.202.0.0/18
# ...
EOR
fi
# Do I have the guest wifi to evade the firewall?
if [ "$SSID" == "guest" ]; then
# Route around firewall
add_routes 192.168.0.1 <<-EOR
irc.freenode.org
news.gmane.org
EOR
fi
;;
esac
exit 0