#!/bin/bash
# switchhgate 0.0.5
# (c) 2005-2010 under GPL by Adrian Zaugg
# switchgate pings a set of hosts to determine the state of the internet connectivity. If
# the connection is down, it can change the default gateway to an alternative gateway.
## Settings
#
# Where the config files reside
CONFIG_PATH="/etc/switchgate"
# Path where to find dhcp information file from switchgate-helper,
# the dhclient-exit-hook helper program
DHCP_TMP_DIR=/tmp/switchgate
# eMail address to send alert messages, if not operating on the
# standard default gateway
ALERT_EMAIL="adi@ente.limmat.ch"
# Host set to ping. A file containing IP addresses, each on a single line.
# Do not include any local hosts, list only hosts located behind your gateways.
HOSTSET_FILE="$CONFIG_PATH/hostset"
# The file defining fixed and dynamic gateway preference
GATEWAYS_FILE="$CONFIG_PATH/gateways"
# path to fping
PING=/usr/bin/fping
# answer of fping to reachable hosts
ALIVE_ANSWER="is alive"
# set to an empty string to avoid debug output
# to "low" for a few output and to anything else
# for verbose output
DEBUG=
# -------- Do not edit below this line --------
PINGARGS="-A -p25 -t100"
## Function declarations
#
# Read gateways from file
function readgwlist {
if [ -f "$GATEWAYS_FILE" ]; then
# Ordered list of all gateways in file. Highest preference has entry with index 0, which
# is your standard default gateway.
# dhcp[.ethX] means dhcp supplied gateways in file $CONFIG_PATH/dhcp.ethX
number_of_gws=0
if [ -n "$DEBUG" -a "$DEBUG" != "low" ]; then
echo "Reading config file $GATEWAYS_FILE..."
fi
allgates=`grep -ve "^[ ]*[\#]\+.*$" "$GATEWAYS_FILE" | xargs`
alldhcpgates=""
for gate in $allgates; do
if [[ $(echo "$gate" | grep -c -e "^[[:space:]]*dhcp\..*$") -eq 1 && \
-f "$DHCP_TMP_DIR/$gate" ]]; then
# Read dhcp supplied gate for a specific interface
alldhcpgates="$alldhcpgates `grep -e "^[^\#].*$" "$DHCP_TMP_DIR/$gate" | xargs`"
elif [[ $(echo "$gate" | grep -c -e "^[[:space:]]*dhcp[[:space:]]*$") -eq 1 ]]; then
# Read all dhcp files
for dhcpfile in $(echo -n $(ls -1 $DHCP_TMP_DIR/dhcp* 2>/dev/null)); do
alldhcpgates="$alldhcpgates `grep -e "^[^\#].*$" "$dhcpfile" | xargs`"
done
else
# it's a fixed gateway
addgw="$gate"
debug_msg="fixed"
addgateway
fi
for dhcpgate in $alldhcpgates; do
addgw="$dhcpgate"
debug_msg="$gate"
addgateway
done
unset alldhcpgates
done
fi
if [ "$number_of_gws" -eq 0 ]; then
echo "No gateways configured. Please edit $GATEWAYS_FILE."
exit 1
elif [[ "$number_of_gws" -eq 1 && ! -z $DEBUG ]]; then
echo "Only 1 gateway configured. No fallback switching possible."
else
if [[ -n "$DEBUG" && "$DEBUG" != "low" ]]; then
echo "... $number_of_gws gateway(s) found."
fi
fi
}
# add a gateway to the array of all gateways to possibly switch to
function addgateway {
addgw=$(echo "$addgw" | tr -d ' ')
# check no duplicate addition is performed
if [ $(echo "${GW[*]}" | grep -c "$addgw") -eq 0 ]; then
GW[$number_of_gws]=$addgw
if [[ -n "$DEBUG" && "$DEBUG" != "low" ]]; then
echo -e "\tGateway $number_of_gws: $addgw ($debug_msg)"
fi
let "number_of_gws += 1"
fi
unset addgw
}
# If one host of the HOSTSET_FILE responds, the conncetion is considered up.
function checkconnection {
UP=""
if [ `$PING $PINGARGS < "$HOSTSET_FILE" 2> /dev/null | grep -c "$ALIVE_ANSWER"` -gt 0 ]; then
UP=true
fi
if [ -n "$DEBUG" ]; then
if [ -n "$UP" ]; then
echo "The connection is up."
else
echo "The connection is down."
fi
fi
}
# Is a certain gateway reachable?
function checkgw {
if [ `$PING $PINGARGS "$NEWGW" 2> /dev/null | grep -c "$ALIVE_ANSWER"` -gt 0 ]; then
if [ -n "$DEBUG" ]; then
echo "$NEWGW is reachable."
fi
else
if [ -n "$DEBUG" ]; then
echo "$NEWGW is not reachable."
fi
NEWGW=""
fi
}
function getcurrentgw {
iproute_default_gw_txt="$(ip route show scope global)"
if [ `echo $iproute_default_gw_txt | grep -c default` -gt 1 ]; then
echo "No support for multiple default gateways. Exiting."
exit 1
else
iproute_default_gw_txt="$(echo "$iproute_default_gw_txt" | grep "default via")"
fi
if [ -z "$iproute_default_gw_txt" ]; then
# no default gateway currently set, set to the highest index, to land on GW0
CURRENTGW_ID=0
CURRENTGW=${GW[$CURRENTGW_ID]}
CURRENTGW_DEV="unknown"
echo "No default gateway currently set. Setting it now to $CURRENTGW."
ip route add default via $CURRENTGW
else
CURRENTGW=`echo $iproute_default_gw_txt | sed "s/^default via \(\([0-9]\+\.\?\)\{4\}\).*\$/\1/"`
CURRENTGW_DEV=`echo $iproute_default_gw_txt | sed "s/^default via .* dev \(eth[0-9]\+\).*\$/\1/"`
fi
# get index of current gateway
index=-1
for NEWGW in ${GW[*]}; do
let "index += 1"
if [ "$CURRENTGW" = "$NEWGW" ]; then
CURRENTGW_ID=$index
break
fi
done
if [ -z "$CURRENTGW_ID" ]; then
echo "The current gateway ($CURRENTGW) is not in the list of available gateways."
echo "Please edit $GATEWAYS_FILE to correct."
exit 1
fi
if [ "$DEBUG" = "low" ]; then
echo "Default gateway detected: $CURRENTGW ($CURRENTGW_ID) on dev $CURRENTGW_DEV"
fi
}
# Set NEWGW and NEWGW_ID to the next gateway from GW[]
function getnextgw {
if [ -z "$NEWGW_ID" ]; then
if [ -z "$CURRENTGW_ID" ]; then
getcurrentgw
NEWGW_ID=$CURRENTGW_ID
else
NEWGW_ID=-1
fi
fi
NEWGW_ID=$[ $NEWGW_ID + 1 ]
if [ "$NEWGW_ID" -ge "${#GW[*]}" ]; then
NEWGW_ID=0
fi
NEWGW=${GW[$NEWGW_ID]}
}
# Set $NEWGW to the next working gateway
function getnewgw {
getnextgw
# Check wheter all gateways are already tested and failed
if [ "$NEWGW_ID" -eq "$CURRENTGW_ID" ]; then
if [ -n "$DEBUG" ]; then
echo "All gateways tried."
fi
NEWGW=""
NEWGW_ID=""
else
if [ -n "$DEBUG" ]; then
echo -n "Trying $NEWGW..."
fi
savegw=$NEWGW
checkgw
if [ -z "$NEWGW" ]; then
if [ -n "$DEBUG" ]; then
echo "No luck. Trying next..."
fi
NEWGW=$savegw
getnewgw
else
if [ -n "$DEBUG" ]; then
echo "Successfully found a new gateway, it's $NEWGW."
fi
fi
fi
}
function switchgw {
getnewgw
if [ -n "$NEWGW" ]; then
if [ "$DEBUG" = "low" ]; then
echo -n "Switching default gateway now to $NEWGW ($NEWGW_ID)..."
fi
iproute_msg="$(ip route change default via $NEWGW 2>&1 )"
iproute_exit=$?
# generate some traffic to take effect
sleep 1
GARBAGE=`$PING -q -c 5 -t100 -p50 -A -f $HOSTSET_FILE 2> /dev/null`
sleep 1
if [ "$DEBUG" = "low" ]; then
echo "done."
fi
checkconnection
if [ -z "$UP" ]; then
if [ "$DEBUG" = "low" ]; then
echo -n "$NEWGW ($NEWGW_ID) is not working, switching back to $CURRENTGW ($CURRENTGW_ID)..."
fi
ip route change default via $CURRENTGW
if [ "$DEBUG" = "low" ]; then
echo "done."
fi
switchgw
elif [ "$iproute_exit" -eq 0 ]; then
CURRENTGW=$NEWGW
CURRENTGW_ID=$NEWGW_ID
else
echo "Failure from iproute: $iproute_msg"
echo "However, the connection is working. Continue as if nothing."
fi
else
if [ "$DEBUG" = "low" ]; then
echo "Not switching."
fi
fi
}
## MAIN
#
# Parse writeable dir request (hidden feature)
if [ "$1" = "-C" ]; then
echo "$DHCP_TMP_DIR"
exit 0
fi
# Parse debug switch (another hidden feature)
if [ "$1" = "-d" ]; then
if [ "$2" != "low" ]; then
echo "Debug mode on."
DEBUG="choke"
else
#echo "Info mode on."
DEBUG="low"
fi
fi
# Check fping existence
if [ ! -x "$PING" ]; then
echo -e "Error: Ping program not executable or not found at $PING.\nPlease configure the full path in $0."
exit 1
fi
# Get all gateways, we can forward traffic
readgwlist
# Parse query for current gw (yet another hidden feature)
if [ "$1" = "-q" ]; then
DEBUG=
getcurrentgw
echo "$CURRENTGW"
exit 0
fi
# get current gw
getcurrentgw
# check wether host set file is around
if [ ! -e $HOSTSET_FILE ]; then
echo "No host list found! File $HOSTSET_FILE not found."
exit 1
fi
# check connection
checkconnection
# If the connection is down, change to the nextgw
if [ -z "$UP" ]; then
# Some more information of down reason: Diagnose wheter the current gateway is alive
NEWGW=$CURRENTGW
checkgw
if [ -z "$NEWGW" ]; then
echo "Gateway $CURRENTGW is down."
else
echo "No connection through gateway $CURRENTGW."
fi
NEWGW_ID=$CURRENTGW_ID
switchgw
else
# If the current gateway is not the standard default gateway, try if the connection
# through the standard default gateway is back
if [ "$CURRENTGW_ID" != 0 ]; then
if [ "$DEBUG" = "low" ]; then
echo "Not operating on standard default gateway (${GW[0]}). Trying to change."
fi
NEWGW_ID=""
switchgw
else
# normal operation
if [[ -n "$DEBUG" && "$DEBUG" != "low" ]]; then
echo "Normal operation on standard default gateway $CURRENTGW ($CURRENTGW_ID)."
fi
fi
fi
# send an eMail if the connection is down or limited
if [ -z "$UP" ]; then
if [ -z "$NEWGW" ]; then
EMAIL_BODY="Switchgate on $(hostname -f) reports: No connection!\n\nTime:\t\t$(date)\nGateway:\t$CURRENTGW ($CURRENTGW_ID)"
EMAIL_SUBJECT="Attention: $(hostname -f) has no connection!"
echo -e "$EMAIL_BODY" | mail -s "$EMAIL_SUBJECT" "$ALERT_EMAIL"
if [[ -n "$DEBUG" && "$DEBUG" != "low" ]]; then
echo "Alert message sent to $ALERT_EMAIL."
fi
fi
elif [ "$CURRENTGW_ID" != 0 ]; then
EMAIL_BODY="Switchgate on $(hostname -f) reports: Not operating on standard default gateway!\n\nTime:\t\t$(date)\nGateway:\t$CURRENTGW ($CURRENTGW_ID)"
EMAIL_SUBJECT="Attention: $(hostname -f) not operating on standard default gateway!"
echo -e "$EMAIL_BODY" | mail -s "$EMAIL_SUBJECT" "$ALERT_EMAIL"
if [[ -n "$DEBUG" && "$DEBUG" != "low" ]]; then
echo "Alert message sent to $ALERT_EMAIL."
fi
fi
if [ "$DEBUG" = "low" -o "$DEBUG" = "choke" ]; then
if [ -n "$UP" ]; then
echo
echo "Current Default Gateway: $CURRENTGW ($CURRENTGW_ID)"
echo
else
echo
echo "No connection."
echo
fi
fi
exit 0
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>