#
#  Copyright Red Hat Inc., 2002
#  Copyright Mission Critical Linux, 2000
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version.
#
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to the
#  Free Software Foundation, Inc.,  675 Mass Ave, Cambridge, 
#  MA 02139, USA.
#

# $Revision: 1.11 $
#
# Author: Gregory P. Myrdal <Myrdal@MissionCriticalLinux.Com>
# Modified: Lon Hohberger <lhh at redhat.com>
#           - Added checking for ethernet channel-bonded slave interfaces.

#
# Shell library for IP functions.
#

SH_LIB=$(dirname $0)

LIBRARIES=

for library in $LIBRARIES
do
	if [ -f $library ]; then
	  . $library
	fi
done

#
# inSameSubnet IPaddr1 IPaddr2 mask
#
# Given two IP addresses and a subnet mask determine if these IP
# addresses are in the same subnet.  If they are, return $YES, if
# not return $NO.  In case of an error, return $FAIL.
#
#
inSameIPsubnet ()
{
	typeset -i n
	typeset -ia mask 
	typeset -ia ip1 ip2		# IP addresses given
	typeset -i quad1 quad2		# calculated quad words

	if [ $# -ne 3 ]; then
	  echo "Usage: ipSameSubnet IPaddr1 IPaddr2 mask"
	  return 1
	fi

	#
	# Remove '.' characters from dotted decimal notation and save
	# in arrays. i.e.
	#
	#	192.168.1.163 -> array[0] = 192
	#	                 array[1] = 168
	#	                 array[2] = 1
	#	                 array[3] = 163
	#
	let n=0
	for quad in $(echo $1 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  ip1[n]=$quad
	  let n=n+1
	done

	let n=0
	for quad in $(echo $2 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  ip2[n]=$quad
	  let n=n+1
	done

	let n=0
	for quad in $(echo $3 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  mask[n]=$quad
	  let n=n+1
	done

	#
	# For each quad word, logically AND the IP address with the subnet
	# mask to get the network/subnet quad word.  If the resulting
	# quad words for both IP addresses are the same they are in the 
	# same IP subnet.
	#
	for n in 0 1 2 3
	do
	  let $((quad1=${ip1[n]} & ${mask[n]}))
	  let $((quad2=${ip2[n]} & ${mask[n]}))

	  if [ $quad1 != $quad2 ]; then
	    return $NO	# in different subnets
	  fi
	done

	return $YES		# in the same subnet, all quad words matched
}


#
# isSlaveInterface <interface>
#
# Given an interface, return whether or not it has the "SLAVE" flag in its ifconfig.
# 
#
isSlaveInterface()
{
	if [ $# -ne 1 ]; then
	  echo "Usage: isSlaveInterface Interface"
	  return $NO
	fi

	# Inefficient, but it works.
	ifconfig $1 | grep -q "SLAVE"
	if [ $? -eq 0 ]; then
	   return $YES
	fi
	return $NO
}


#
# findInterface IPaddr
#
# Given a target IP address find the interface in which this address is
# configured.  If found return $SUCCESS, if not return $NOT_FOUND.  The
# interface name is returned to stdout.
#
findInterface()
{
	typeset line
	typeset intf
	typeset addr
	typeset state

	typeset target=$1

	{
	while read intf line
	do
	  while read line
	  do
	    if [ "$line" = "" ]; then	# go to next interface
	      continue 2
	    fi

	    set - $line

	    addr=
	    while [ $# -gt 0 ]; do
	      case $1 in
	        addr:*)
	          addr=${1##addr:}
	          if [ -n "$addr" -a $addr = $target ]; then
		    isSlaveInterface $intf
		    if [ $? -eq $NO ]; then
	              echo $intf
	              return $SUCCESS
		    fi
	          fi
	          ;;
	      esac
	      shift
	    done
	  done
	done
	} < <(ifconfig)

	return $NOT_FOUND
}


#
# findMacAddr IPaddress
#
# Given an interface find the MAC addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findMacAddr()
{
	typeset line
	typeset intf
	typeset addr
	typeset junk
	typeset state

	typeset target=$1

	{
	while read intf line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      HWaddr)
	        echo $2	 	# return MAC addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done
	} < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findNetmask IPaddress serviceName serviceID ipID
#
# Given an interface find the netmask addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findNetmask()
{
	typeset line
	typeset intf
	typeset addr
	typeset junk
	typeset state
	typeset addr

	typeset target=$1
	typeset svc_name=$2
	typeset svcID=$3
	typeset token=$4

	# 
	# If the user defined a network mask in the config file, use it.
	#
	addr=$(getSvcNetmask $DB $token)
	case $? in
	  0) echo $addr 	# found it
	     return $SUCCESS
	     ;;
	  2) addr= 		# not found
	     ;;
	  *) logAndPrint $LOG_ERR "\
Cannot find netmask $ipN for service $svc_name, err=$?"
	  return $FAIL ;;
	esac

	#
	# If no network mask is defined.  Find it from the interface
	# that this ip address is assigned to.
	#
	while read line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      Mask:*)
	        echo ${1##*:}	 	# return netmask addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findBroadcast IPaddress serviceName serviceID ipID
#
# Given an interface find the broadcast addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findBroadcast()
{
	typeset line
	typeset intf
	typeset addr
	typeset junk
	typeset state

	typeset target=$1
	typeset svc_name=$2
	typeset svcID=$3
	typeset ipN=$4

	# 
	# If the user defined a broadcast address in the config file, use it.
	#
	addr=$(getSvcBroadcast $DB $token)
	case $? in
	  0) echo $addr 	# found it
	     return $SUCCESS
	     ;;
	  2) addr= 		# not found
	     ;;
	  *) logAndPrint $LOG_ERR "\
Cannot find broadcast $ipN for service $svc_name, err=$?"
	  return $FAIL ;;
	esac

	#
	# If no broadcast address is defined. Find it from the interface
	# that this ip address is assigned to.
	#
	{
	while read line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      Bcast:*)
	        echo ${1##*:}	 	# return broadcast addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done
	} < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findInterfaceInSubnet IPaddr
#
# Given a target IP address find the interface which is configured
# in this subnet and find a free interface name.  When found return
# $SUCCESS and print the interface name to stdout.  If not found return
# $NOT_FOUND, on error return $FAIL.
#
findInterfaceInSubnet ()
{
	typeset line
	typeset intf
	typeset addr
	typeset mask

	typeset target=$1

	while read intf line
	do
	  while read line
	  do
	    if [ "$line" = "" ]; then	# go to next interface
	      continue 2
	    fi

	    set - $line

	    addr=
	    mask=
	    while [ $# -gt 0 ]; do
	      case $1 in
	        addr:*) addr=${1##addr:} ;;
	        Mask:*) mask=${1##Mask:} ;;
	      esac
	      shift
	    done

	    if [ -z "$addr" -o -z "$mask" ]; then
	      continue
	    fi
	  
	    inSameIPsubnet $target $addr $mask
	    if [ $? = $YES ]; then
	      isSlaveInterface $intf
	      if [ $? = $NO ]; then
	        echo $intf
	        return $SUCCESS
	      fi
	    fi
	  done
	done < <(ifconfig)

	return $NOT_FOUND
}

#
# reserveNextFreeInterface IPaddress
#
# Given an IP address find an interface for it to be configured on.
# First we must find an interface in the same IP subnet as this IP
# address.  Then we look for a free instance of that interface.
# On return, store the name of the interface that can be used in
# argument $2 and return $SUCCESS.  On error or not found return $FAIL.
#
# Note: the caller cannot use the same variable name of a 'typeset'
# variable name in this script.  If he does, scoping will not allow
# the setting to be seen when this function returns.
#
reserveNextFreeInterface()
{
	typeset used_interfaces used_intf
	typeset ipAddr
	typeset tmp_intf
	typeset -i n

	ipAddr=$1
	eval $2='""'		# initialize passed in arg

	tmp_intf=$(findInterfaceInSubnet $ipAddr)
	if [ $? -ne $SUCCESS ]; then
	  logAndPrint $LOG_ERR \
	      "Error: Cannot reserve alias interface for address $ipAddr"
	  return $FAIL
	fi

	used_interfaces=$(ifconfig | \
	                  grep "^${tmp_intf}:[0-9]" | sed 's/[ 	].*//')
	let n=0
	while :
	do
	  for used_intf in $used_interfaces
	  do
	    if [ $used_intf = ${tmp_intf}:$n ]; then
	      let n=n+1
	      continue 2		# its used, try next number
	    fi
	  done
	  break				# found an unused alias interface
	done
  
	#
	# Reserve this interface ($tmp_intf:$n).  
	#
	# Note: The caller should apply the correct netmask and broadcast
	# address and any other options.  This is just a place holder.
	# Also the caller needs to 'ifconfig $tmp_intf down' if something 
	# goes wrong.
	#
	ifconfig ${tmp_intf}:$n $ipAddr broadcast 0.0.0.0 \
	           netmask 255.255.255.255 > /dev/null 2>&1
	eval $2='${tmp_intf}:$n'	# pass back interface in given arg
	return $SUCCESS			# found a free interface
}


#
# Find slaves for a bonded interface
#
findSlaves()
{
	declare mastif=$1
	declare line
	declare intf
	declare interfaces

	if [ -z "$mastif" ]; then
		logAndPrint $LOG_ERR "usage: findSlaves <master I/F>"
		return $FAIL
	fi

	line=$(/sbin/ip link list dev $mastif | grep "<.*MASTER.*>")
	if [ $? -ne 0 ]; then
		logAndPrint $LOG_ERR "Error determining status of $mastif"
		return $FAIL
	fi

	if [ -z "`/sbin/ip link list dev $mastif | grep \"<.*MASTER.*>\"`" ]
	then
		logAndPrint $LOG_ERR "$mastif is not a master device"
		return $FAIL
	fi

	while read line; do
		set - $line
		while [ $# -gt 0 ]; do
			case $1 in
			eth*:)
				interfaces="${1/:/} $interfaces"
				continue 2
				;;
			esac
			shift
		done
	done < <( /sbin/ip link list | grep "master $mastif" )

	echo $interfaces
}


#
# ethernet_link_up <interface>
#
ethernet_link_up()
{
	declare linkstate=$(ethtool $1 | grep "Link detected:" |\
			    awk '{print $3}')
	
	if [ -z "$linkstate" ]; then
		logAndPrint $LOG_WARNING "Could not determine link state of $1"
		return $SUCCESS
	fi

	if [ "$linkstate" = "yes" ]; then
		# Ethtool says we've got a link
		return $SUCCESS
	fi
	return $FAIL
}


#
# Checks the physical link status of an ethernet or bonded interface.
#
network_link_up()
{
	declare slaves
	declare intf_arg=$1
	declare link_up=1		# Assume link down
	declare intf_test

	if [ -z "$intf_arg" ]; then
		logAndPrint $LOG_ERR "usage: network_link_up <intf>"
		return 1
	fi
	
	#
	# XXX assumes bond* interfaces are the bonding driver. (Fair
	# assumption on Linux, I think)
	#
	if [ "${intf_arg/bond/}" != "$intf_arg" ]; then
		
		#
		# Bonded driver.  Check link of all slaves for this interface.
		# If any link is up, the bonding driver is expected to route
		# traffic through that link.  Thus, the entire bonded link
		# is declared up.
		#
		slaves=$(findSlaves $intf_arg)
		if [ $? -ne 0 ]; then
			logAndPrint $LOG_ERR "Error finding slaves of $intf_arg"
			return 1
		fi
		for intf_test in $slaves; do
			ethernet_link_up $intf_test && link_up=0
		done
	else
		ethernet_link_up $intf_arg
		link_up=$?
	fi

	if [ $link_up -eq 0 ]; then
		logAndPrint $LOG_DEBUG "Link for $intf_arg: Detected"
	else
		logAndPrint $LOG_DEBUG "Link for $intf_arg: Not detected"
	fi

	return $link_up
}




#
#  startIPaddr svcID ipN IPaddress
#
# Given a service ID number configure all of its IP addresses.
#
startIPaddr()
{
	typeset mac_addr
	typeset intf
	typeset real_intf
	typeset ret_val

	if [ $# -ne 3 ]; then
	  logAndPrint $LOG_ERR "Usage: startIPaddr serviceID token IPaddress"
	  return $FAIL
	fi

	typeset svcID=$1
	typeset	token=$2
	typeset IPaddr=$3
	typeset bc_param=""
	typeset nm_param=""
	typeset monitor_links=$(getSvcIPmonitorlink $DB $token)
	
	logAndPrint $LOG_INFO "Starting IP address $IPaddr"

	intf=$(findInterface $IPaddr)
	if [ $? -ne $SUCCESS ]; then

	  reserveNextFreeInterface $IPaddr intf
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "Cannot find interface for $IPaddr"
	    return $FAIL
	  fi

	  real_intf=${intf%%:*}

	  netmask_addr=$(findNetmask $real_intf "$SVC_NAME" $svcID $token)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "\
Cannot find network mask for interface '$real_intf'"

	    # remove entry added by reserveNextFreeInterface()
	    ifconfig $intf down

	    return $FAIL
	  fi

	  broadcast_addr=$(findBroadcast $real_intf "$SVC_NAME" $svcID $token)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "\
Cannot find broadcast address for interface '$real_intf'"

	    # remove entry added by reserveNextFreeInterface()
	    ifconfig $intf down

	    return $FAIL
	  fi

	  if [ "$monitor_links" = "yes" ] || [ "$monitor_links" = "1" ]; then
	    if ! network_link_up $real_intf; then
	      logAndPrint $LOG_ERR "Network link not detected on $real_intf"
	      ifconfig $intf down
	      return $FAIL
	    fi
          fi

	  if [ -n "$broadcast_addr" ]; then
	    bc_param="broadcast $broadcast_addr"
	  fi

	  if [ -n "$netmask_addr" ]; then
	    nm_param="netmask $netmask_addr"
	  fi
	  	
	  ifconfig_cmd="ifconfig $intf $IPaddr $bc_param $nm_param"
	  logAndPrint $LOG_DEBUG $ifconfig_cmd

	  $ifconfig_cmd 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	    logAndPrint $LOG_ERR "\
Cannot ifconfig $IPaddr to $intf; err=$ret_val"
	    return $FAIL
	  fi
	else
	  real_intf=${intf%%:*}
	fi

	intf=$(findInterface $IPaddr)
	if [ $? -ne $SUCCESS ]; then
	  return $FAIL
	fi

	logAndPrint $LOG_DEBUG "Adding host based route for $IPaddr"

	route add -host $IPaddr dev $intf 2> /dev/null
	ret_val=$?
	if [ $ret_val -ne 0 -a $ret_val -ne 7 ]; then # 7 = already done
	  logAndPrint $LOG_ERR "\
Cannot route add $IPaddr to $intf; err=$ret_val"
	  return $FAIL
	fi

	mac_addr=$(findMacAddr $real_intf)
	if [ $? -ne $SUCCESS ]; then
	  logAndPrint $LOG_ERR "\
Cannot find harware address for interface '$real_intf'"
	  return $FAIL
	fi

	logAndPrint $LOG_DEBUG "Adding ARP entry for $IPaddr ($mac_addr)"

	arp -i $real_intf -s $IPaddr $mac_addr 2> /dev/null
	ret_val=$?
	if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	  logAndPrint $LOG_ERR "\
Cannot add ARP entry for $IPaddr ($mac_addr); err=$ret_val"
	  return $FAIL
	fi

	logAndPrint $LOG_INFO "Sending Gratuitous arp for $IPaddr ($mac_addr)"
	logAndPrint $LOG_DEBUG $GRATUITOUSARP $IPaddr $mac_addr $IPaddr ffffffffffff $real_intf
	for i in 1 2 3
	do
	  $GRATUITOUSARP $IPaddr $mac_addr $IPaddr ffffffffffff $real_intf
	  ret_val=$?
	  if [ $ret_val -ne 0 ]; then
	    logAndPrint $LOG_ERR "\
Cannot send gratuitous ARP for $IPaddr ($mac_addr); err=$ret_val"
	    return $FAIL
	  fi
	done

	return $SUCCESS
}

#
#  startIP serviceID
#
# Given a service ID number configure all of its IP addresses.
#
startIP()
{
	typeset IPaddr
	typeset -i try=0
	typeset -i max_tries=3	# max tries to start address
	typeset token tokenlist

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: startIP serviceID"
	  return 1
	fi

	typeset svcID=$1

	tokenlist=$(getSvcIPaddressTokenList $DB $svcID)
	for token in $tokenlist; do
	  IPaddr=$(getSvcIPaddress $DB $token)
	  case $? in
	    0) : ;;		# found it
	    2) break ;;		# no more IP addresses found
	    *) logAndPrint $LOG_ERR "\
Cannot find IP address $ipid for service $SVC_NAME, err=$?"
	    return $FAIL ;;
	  esac

	  let try=0
	  while :
	  do
	    let try=try+1

	    startIPaddr $svcID $token $IPaddr
	    if [ $? -ne $SUCCESS ]; then
	      if [ $try -gt $max_tries ]; then
	        logAndPrint $LOG_ERR \
	    	  "Cannot start IP address $IPaddr; max attempts reached"
	        return $FAIL
	      fi

	      logAndPrint $LOG_ERR \
	        "Cannot start IP address $IPaddr; retrying ..."

	      sleep 1
	      continue

	    fi
	    break
	  done
	done

	return $SUCCESS
}

#
#  stopIP serviceID
#
# Given a service ID number unconfigure all of its IP addresses.
#
stopIP()
{
	typeset IPaddr
	typeset intf
	typeset ret_val
	typeset token tokenlist

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: stopIP serviceID"
	  return 1
	fi

	typeset svcID=$1

	tokenlist=$(getSvcIPaddressTokenList $DB $svcID)
	for token in $tokenlist; do
	  IPaddr=$(getSvcIPaddress $DB $token)
	  case $? in
	    0) : ;;		# found it
	    2) break ;;		# no more IP addresses found
	    *) logAndPrint $LOG_ERR "\
Cannot find IP address $token for service $SVC_NAME, err=$?"
	    return $FAIL ;;
	  esac


	  intf=$(findInterface $IPaddr)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_DEBUG "IP address $IPaddr already removed"
	  else
	    logAndPrint $LOG_INFO "Stopping IP address $IPaddr"

	    logAndPrint $LOG_DEBUG ifconfig $intf down

	    ifconfig $intf down 2> /dev/null
	    ret_val=$?
	    if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	      logAndPrint $LOG_ERR "Cannot ifconfig $intf down"
	      return $FAIL
	    fi
	  fi

	  intf=$(findInterface $IPaddr)
	  if [ $? -ne $NOT_FOUND ]; then
	    logAndPrint $LOG_ERR "Cannot un-configure IP address $IPaddr"
	    return $FAIL
	  fi

	  logAndPrint $LOG_DEBUG "Removing host based route for $IPaddr"

	  route delete -host $IPaddr 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 7 ]; then	# 7 means already done
	    logAndPrint $LOG_ERR "Cannot route delete $IPaddr; err=$ret_val"
	    return $FAIL
	  fi

	  logAndPrint $LOG_DEBUG "Removing ARP entry for $IPaddr"

	  arp -d $IPaddr 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	    logAndPrint $LOG_ERR "\
Cannot delete ARP entry for $IPaddr; err=$ret_val"
	    return $FAIL
	  fi
	done

	return $SUCCESS
}

#
#  statusIP serviceID
#
statusIP()
{
	typeset IPaddr
	typeset intf real_intf
	typeset ret_val token tokenlist
	typeset monitor_links

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: statusIP serviceID"
	  return 1
	fi

	typeset svcID=$1

	tokenlist=$(getSvcIPaddressTokenList $DB $svcID)
	for token in $tokenlist; do
	  IPaddr=$(getSvcIPaddress $DB $token)
	  case $? in
	    0) : ;;		# found it
	    2) break ;;		# no more IP addresses found
	    *) logAndPrint $LOG_ERR "\
Cannot find IP address $token for service $SVC_NAME, err=$?"
	    return $FAIL ;;
	  esac

	  monitor_links=$(getSvcIPmonitorlink $DB $token)

	  intf=$(findInterface $IPaddr)

	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "IP address $IPaddr missing"
	    return $FAIL
	  fi

	  if [ "$monitor_links" = "yes" ] || [ "$monitor_links" = "1" ]; then
	    real_intf=${intf%%:*}
	    logAndPrint $LOG_INFO "Checking link on $real_intf"

	    if ! network_link_up $real_intf; then
	      logAndPrint $LOG_ERR "Network link not detected on $real_intf"
	      return $FAIL
	    fi
          fi

	done

	return $SUCCESS
}

#
# ip
#
# Given an action and service ID number run that action for that service.
#
ip()
{

	if [ $# -ne 2 ]; then
	  logAndPrint $LOG_ERR "Usage: ip [start, stop, status] serviceID"
	  return $FAIL
	fi

	typeset action=$1
	typeset svcID=$2

	case "$action" in
	'start')
	  startIP $svcID
	  return $?
	  ;;

	'stop')
	  stopIP $svcID
	  return $?
	  ;;

	'status')
	  statusIP $svcID
	  return $?
	  ;;
	esac
}
