#!/bin/bash

#
#  Copyright Red Hat, Inc. 2004
#  Copyright Mission Critical Linux, Inc. 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.
#

#
# IPv4/IPv6 address management using new /sbin/ifcfg instead of 
# ifconfig utility.
#


ipv4_same_subnet()
{
	declare addrl=$1
	declare addrr=$2
	declare m=$3 
	declare r x llsb rlsb

	if [ $# -lt 2 ]; then
		logAndPrint $LOG_ERR "usage: ipv4_same_subnet current_addr new_addr [maskbits]"
		return 255
	fi


	#
	# Chop the netmask off of the ipaddr:
	# e.g. 1.2.3.4/22 -> 22
	#
	if [ -z "$m" ]; then
		m=${addrl/*\//}
		[ -n "$m" ] || return 1
	fi

	#
	# Check to see if there was a subnet mask provided on the
	# new IP address.  If there was one and it does not match
	# our expected subnet mask, we are done.
	#
	if [ "${addrr}" != "${addrr/\/*/}" ] &&
	   [ "$m" != "${addrr/*\//}" ]; then
		return 1
	fi

	#
	# Chop off subnet bits for good.
	#
	addrl=${addrl/\/*/}
	addrr=${addrr/\/*/}

	#
	# 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 x=0
	for quad in ${addrl//./\ }; do
		ip1[((x++))]=$quad
	done

	x=0
	for quad in ${addrr//./\ }; do
		ip2[((x++))]=$quad
	done

	x=0

	while [ $m -ge 8 ]; do
		((m-=8))
		if [ ${ip1[x]} -ne ${ip2[x]} ]; then
			return 1
		fi
		((x++))
	done

	case $m in
	0)
		return 0
		;;
	1)	
		[ $((${ip1[x]} & 128)) -eq $((${ip2[x]} & 128)) ]
		return $?
		;;
	2)
		[ $((${ip1[x]} & 192)) -eq $((${ip2[x]} & 192)) ]
		return $?
		;;
	3)
		[ $((${ip1[x]} & 224)) -eq $((${ip2[x]} & 224)) ]
		return $?
		;;
	4)
		[ $((${ip1[x]} & 240)) -eq $((${ip2[x]} & 240)) ]
		return $?
		;;
	5)
		[ $((${ip1[x]} & 248)) -eq $((${ip2[x]} & 248)) ]
		return $?
		;;
	6)
		[ $((${ip1[x]} & 252)) -eq $((${ip2[x]} & 252)) ]
		return $?
		;;
	7)
		[ $((${ip1[x]} & 254)) -eq $((${ip2[x]} & 254)) ]
		return $?
		;;
	esac

	return 1
}


#
# Find slaves for a bonded interface
#
# XXX Provided by svclib_ip
# findSlaves
#


isSlave()
{
	declare intf=$1
	declare line

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

	line=$(/sbin/ip link list dev $intf)
	if [ $? -ne 0 ]; then
		logAndPrint $LOG_ERR "$intf not found"
		return $FAIL
	fi

	if [ "$line" = "${line/<*SLAVE*>/}" ]; then
		return $NO
	fi

	# Yes, it's a slave device.  Ignore.
	return $YES
}


#
# ethernet_link_up <interface>
#
# XXX provided by svclib_ip
#


#
# Checks the physical link status of an ethernet or bonded interface.
#
# network_link_up
# XXX provided by svclib_ip
#


ipv4_find_interface()
{
	declare idx dev ifaddr
	declare newaddr=$1

	while read idx dev ifaddr; do

		isSlave $dev
		if [ $? -ne $NO ]; then
			continue
		fi

		idx=${idx/:/}

		if [ "$ifaddr" = "$newaddr" ]; then
			# for most things, 
			echo $dev ${ifaddr/*\//}
			return 0
		fi

		if ipv4_same_subnet $ifaddr $newaddr; then
			echo $dev ${ifaddr/*\//}
			return 0
		fi
	done < <(/sbin/ip -o -f inet addr | awk '{print $1,$2,$4}')

	return 1
}


#
# Add an IP address to our interface.
#
ipv4()
{
	declare dev maskbits hwaddr
	declare addr=$2
		
	read dev maskbits < <(ipv4_find_interface $addr)

	if [ -z "$dev" ]; then
		return 1
	fi

	if [ "${addr}" = "${addr/\*\//}" ]; then
		addr="$addr/$maskbits"
	fi

	if [ "$1" = "add" ]; then
		if [ "$3" = "1" ] || [ "$3" = "yes" ]; then
			network_link_up $dev
		fi
		if [ $? -ne 0 ]; then
			echo "Cannot add $addr to $dev; no link"
			return 1
		fi
	fi

	logAndPrint $LOG_INFO "Attempting to $1 IPv4 address $addr ($dev)"

	#/sbin/ip $dev $1 $addr
	/sbin/ip -f inet addr $1 dev $dev $addr
	[ $? -ne 0 ] && return 1

	#
	# The following is needed only with ifconfig; ifcfg does it for us
	#
	if [ "$1" = "add" ]; then
		# do that freak arp thing

		hwaddr=$(/sbin/ip -o link show $dev)
		hwaddr=${hwaddr/*link\/ether\ /}
		hwaddr=${hwaddr/\ \*/}

		addr=${addr/\/*/}
		logAndPrint $LOG_INFO "Sending gratuitous ARP: $addr $hwaddr"
 		arping -q -c 2 -U -I $dev $addr
	fi

	killall -HUP rdisc || rdisc -fs
	return 0
}


#
# Usage:
# ip_op <family> <operation> <address>
#
ip_op()
{
	declare dev
	declare rtr

	# Parameters
	declare k_family=$1
	declare op=$2
	declare k_address=$3
	declare k_monitor_link=$4

	[ -z "$k_monitor_link" ] && k_monitor_link=0

	if [ "$2" = "status" ]; then

		logAndPrint $LOG_DEBUG "Checking $3"
	
		dev=$(/sbin/ip -f $1 -o addr | grep " $3/" | awk '{print $2}')
		if [ -z "$dev" ]; then
			[ -n "$4" ] || logAndPrint $LOG_DEBUG "$3 is not configured"
			return 1
		fi

		if [ "${k_monitor_link}" != "yes" ] &&
		   [ "${k_monitor_link}" != "1" ]; then
			return 0
		fi

		if ! network_link_up $dev; then
			logAndPrint $LOG_ERR \
				"Link not present on $dev"
			return 1
		fi

		return 0
	fi

	case $1 in
	inet)
		ipv4 $2 $3 $k_monitor_link
		return $?
		;;
	esac
	return 1
}
	


ipaddr()
{
	# Parameters
	declare k_family=$1
	declare op=$2
	declare k_address=$3
	declare k_monitor_link=$4

	[ -z "$k_monitor_link" ] && k_monitor_link=0

	case $op in
	start)
		if ip_op ${k_family} status ${k_address} ${k_monitor_link}; then
			logAndPrint $LOG_DEBUG "${k_address} already configured"
			return 0
		fi
		ip_op ${k_family} add ${k_address} ${k_monitor_link}
		return $?
		;;
	stop)
		if ip_op ${k_family} status ${k_address} no; then
			ip_op ${k_family} del ${k_address} no
	
			# Make sure it's down
			if ip_op ${k_family} status ${k_address} no; then
				return 1
			fi
		else
			logAndPrint $LOG_INFO "${k_address} is not configured"
		fi
		return 0
		;;
	status|monitor)
		ip_op ${k_family} status ${k_address} ${k_monitor_link}
		return $?
		;;
	esac

	return 1
}


ip_netlink()
{
        declare -i try=0
        declare -i max_tries=3  # max tries to start address
        declare token tokenlist
	declare command=$1
	typeset svcID=$2

        if [ $# -ne 2 ]; then
		logAndPrint $LOG_ERR "Usage: ip_netlink <start|stop|status> serviceID"
		return 1
	fi

	tokenlist=$(getSvcIPaddressTokenList $DB $svcID)
	for token in $tokenlist; do
		declare IPaddr=$(getSvcIPaddress $DB $token)
		declare linkmon
		
		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

		linkmon=$(getSvcIPmonitorlink $DB $token)

		let try=0
		while :
		do
			let try=try+1
			ipaddr inet $command $IPaddr $linkmon
			if [ $? -ne $SUCCESS ]; then
				if [ $try -gt $max_tries ]; then
					logAndPrint $LOG_ERR \
                    "Cannot $command IP address $IPaddr; max attempts reached"
					return $FAIL
				fi
				logAndPrint $LOG_ERR \
		    "Cannot $command IP address $IPaddr; retrying ..."

				sleep 1
				continue
			fi
			break
		done
        done

        return $SUCCESS
}
