diff --git a/rgmanager/src/resources/ip.sh b/rgmanager/src/resources/ip.sh index 7e192053d..6391fabc5 100755 --- a/rgmanager/src/resources/ip.sh +++ b/rgmanager/src/resources/ip.sh @@ -1,1027 +1,1052 @@ #!/bin/bash # # IPv4/IPv6 address management using iproute2 (formerly: ifcfg, ifconfig). # # # Copyright (C) 1997-2003 Sistina Software, Inc. All rights reserved. # Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. # # 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 # of the License, 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; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # LC_ALL=C LANG=C PATH=/bin:/sbin:/usr/bin:/usr/sbin export LC_ALL LANG PATH +SENDUA=/usr/libexec/heartbeat/send_ua + # Grab nfs lock tricks if available export NFS_TRICKS=1 if [ -f "$(dirname $0)/svclib_nfslock" ]; then . $(dirname $0)/svclib_nfslock NFS_TRICKS=0 fi . $(dirname $0)/ocf-shellfuncs meta_data() { cat < 1.0 This is an IP address. Both IPv4 and IPv6 addresses are supported, as well as NIC link monitoring for each IP address. This is an IP address. IPv4 or IPv6 address to use as a virtual IP resource. It may be followed by a slash and a decimal number that encodes the network prefix length. IP Address IPv4 or IPv6 address protocol family. Family Enabling this causes the status check to fail if the link on the NIC to which this IP address is bound is not present. Monitor NIC Link If set and unmounting the file system fails, the node will try to kill lockd and issue reclaims across all remaining network interface cards. Enable NFS lock workarounds Amount of time to sleep after removing an IP address. Value is specified in seconds. Default value is 10. Amount of time (seconds) to sleep. Disable updating of routing using RDISC protocol and preserve static routes. Disable updating of routing using RDISC protocol The network interface to which the IP address should be added. The interface must already be configured and active. This parameter should be used only when at least two active interfaces have IP addresses on the same subnet and it is desired to have the IP address added to a particular interface. Network interface EOT } verify_address() { # XXX TBD return 0 } verify_all() { # XXX TBD return 0 } # # Expand an IPv6 address. # ipv6_expand() { typeset addr=$1 typeset maskbits typeset -i x typeset tempaddr maskbits=${addr/*\//} if [ "$maskbits" = "$addr" ]; then maskbits="" else # chop off mask bits addr=${addr/\/*/} fi # grab each hex quad and expand it to 4 digits if it isn't already # leave doublecolon in place for expansion out to the proper number of zeros later tempaddr="" for count in `seq 1 8`; do quad=`echo $addr|awk -v count=$count -F : '{print $count}'` quadlen=${#quad} if [ $quadlen -eq 0 ]; then quad=:: elif [ $quadlen -eq 1 ]; then quad=000$quad elif [ $quadlen -eq 2 ]; then quad=00$quad elif [ $quadlen -eq 3 ]; then quad=0$quad fi tempaddr=$tempaddr$quad done addr=$tempaddr # use space as placeholder addr=${addr/::/\ } # get rid of colons addr=${addr//:/} # add in zeroes where the doublecolon was len=$((${#addr}-1)) zeroes= while [ $len -lt 32 ]; do zeroes="0$zeroes" ((len++)) done addr=${addr/\ /$zeroes} # probably a better way to do this for (( x=0; x < ${#addr} ; x++)); do naddr=$naddr${addr:x:1} if (( x < (${#addr} - 1) && x%4 == 3)); then naddr=$naddr: fi done if [ -n "$maskbits" ]; then echo "$naddr/$maskbits" return 0 fi echo "$naddr" return 0 } # # see if two ipv6 addrs are in the same subnet # ipv6_same_subnet() { declare addrl=$1 declare addrr=$2 declare m=$3 declare r x llsb rlsb if [ $# -lt 2 ]; then ocf_log err "usage: ipv6_same_subnet addr1 addr2 [mask]" return 255 fi if [ -z "$m" ]; then m=${addrl/*\//} [ -n "$m" ] || return 1 fi if [ "${addrr}" != "${addrr/*\//}" ] && [ "$m" != "${addrr/*\//}" ]; then return 1 fi addrl=${addrl/\/*/} if [ ${#addrl} -lt 39 ]; then addrl=$(ipv6_expand $addrl) fi addrr=${addrr/\/*/} if [ ${#addrr} -lt 39 ]; then addrr=$(ipv6_expand $addrr) fi # Calculate the amount to compare directly x=$(($m/4+$m/16-(($m%4)==0))) # and the remaining number of bits r=$(($m%4)) if [ $r -ne 0 ]; then # If we have any remaining bits, we will need to compare # them later. Get them now. llsb=`printf "%d" 0x${addrl:$x:1}` rlsb=`printf "%d" 0x${addrr:$x:1}` # One less byte to compare directly, please ((--x)) fi # direct (string comparison) to see if they are equal if [ "${addrl:0:$x}" != "${addrr:0:$x}" ]; then return 1 fi case $r in 0) return 0 ;; 1) [ $(($llsb & 8)) -eq $(($rlsb & 8)) ] return $? ;; 2) [ $(($llsb & 12)) -eq $(($rlsb & 12)) ] return $? ;; 3) [ $(($llsb & 14)) -eq $(($rlsb & 14)) ] return $? ;; esac return 1 } ipv4_same_subnet() { declare addrl=$1 declare addrr=$2 declare m=$3 declare r x llsb rlsb if [ $# -lt 2 ]; then ocf_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 } ipv6_list_interfaces() { declare idx dev ifaddr declare ifaddr_exp while read idx dev ifaddr; do isSlave $dev if [ $? -ne 2 ]; then continue fi idx=${idx/:/} ifaddr_exp=$(ipv6_expand $ifaddr) echo $dev ${ifaddr_exp/\/*/} ${ifaddr_exp/*\//} done < <(/sbin/ip -o -f inet6 addr | awk '{print $1,$2,$4}') return 0 } isSlave() { declare intf=$1 declare line if [ -z "$intf" ]; then ocf_log err "usage: isSlave " return $OCF_ERR_ARGS fi line=$(/sbin/ip link list dev $intf) if [ $? -ne 0 ]; then ocf_log err "$intf not found" return $OCF_ERR_GENERIC fi if [ "$line" = "${line/<*SLAVE*>/}" ]; then return 2 fi # Yes, it is a slave device. Ignore. return 0 } # # Check if interface is in UP state # interface_up() { declare intf=$1 if [ -z "$intf" ]; then ocf_log err "usage: interface_up " return 1 fi line=$(/sbin/ip -o link show up dev $intf 2> /dev/null) [ -z "$line" ] && return 2 return 0 } ethernet_link_up() { declare linkstate=$(ethtool $1 | grep "Link detected:" |\ awk '{print $3}') [ -n "$linkstate" ] || return 0 case $linkstate in yes) return 0 ;; *) return 1 ;; esac return 1 } # # 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 ocf_log err "usage: network_link_up " return 1 fi ethernet_link_up $intf_arg link_up=$? if [ $link_up -eq 0 ]; then ocf_log debug "Link for $intf_arg: Detected" else ocf_log warn "Link for $intf_arg: Not detected" fi return $link_up } ipv4_list_interfaces() { declare idx dev ifaddr while read idx dev ifaddr; do isSlave $dev if [ $? -ne 2 ]; then continue fi idx=${idx/:/} echo $dev ${ifaddr/\/*/} ${ifaddr/*\//} done < <(/sbin/ip -o -f inet addr | awk '{print $1,$2,$4}') return 0 } # # Add an IP address to our interface or remove it. # ipv6() { declare dev maskbits declare addr=$2 declare addr_exp=$(ipv6_expand $addr) while read dev ifaddr_exp maskbits; do if [ -z "$dev" ]; then continue fi if [ "$1" = "add" ]; then if [ -n "$OCF_RESKEY_prefer_interface" ] && \ [ "$OCF_RESKEY_prefer_interface" != $dev ]; then continue fi ipv6_same_subnet $ifaddr_exp/$maskbits $addr_exp if [ $? -ne 0 ]; then continue fi interface_up $dev if [ $? -ne 0 ]; then continue fi if [ "$OCF_RESKEY_monitor_link" = "yes" ]; then network_link_up $dev if [ $? -ne 0 ]; then continue fi fi if [ "${addr/\/*/}" = "${addr}" ]; then addr="$addr/$maskbits" fi ocf_log info "Adding IPv6 address $addr to $dev" fi if [ "$1" = "del" ]; then if [ "${addr_exp/\/*/}" != "$ifaddr_exp" ]; then continue fi addr=`/sbin/ip addr list | grep "$addr" | head -n 1 | awk '{print $2}'` ocf_log info "Removing IPv6 address $addr from $dev" fi - if [ "$1" = "add" ]; then - ocf_log debug "Pinging addr ${addr%%/*} from dev $dev" - if ping_check inet6 ${addr%%/*} $dev; then - ocf_log err "IPv6 address collision ${addr%%/*}" - return 1 - fi - fi /sbin/ip -f inet6 addr $1 dev $dev $addr [ $? -ne 0 ] && return 1 + + # Duplicate Address Detection [DAD] + # Kernel will flag the IP as 'tentative' until it ensured that + # there is no duplicates. + # if there is, it will flag it as 'dadfailed' + if [ "$1" = "add" ]; then + for i in {1..10}; do + ipstatus=$(/sbin/ip -o -f inet6 addr show dev $dev to $addr) + if [[ $ipstatus == *dadfailed* ]]; then + ocf_log err "IPv6 address collision ${addr%%/*} [DAD]" + ip -f inet6 addr del dev $dev $addr + if [[ $? -ne 0 ]]; then + ocf_log err "Could not delete IPv6 address" + fi + return 1 + elif [[ $ipstatus != *tentative* ]]; then + break + elif [[ $i -eq 10 ]]; then + ofc_log warn "IPv6 address : DAD is still in tentative" + fi + sleep 0.5 + done + # Now the address should be useable + # Try to send Unsolicited Neighbor Advertisements if send_ua is available + if [ -x $SENDUA ]; then + ARGS="-i 200 -c 5 ${addr%%/*} $maskbits $dev" + ocf_log info "$SENDUA $ARGS" + $SENDUA $ARGS || ocf_log err "Could not send ICMPv6 Unsolicited Neighbor Advertisements." + fi + fi # # NDP should take of figuring out our new address. Plus, # we do not have something (like arping) to do this for ipv6 # anyway. # # RFC 2461, section 7.2.6 states thusly: # # Note that because unsolicited Neighbor Advertisements do not # reliably update caches in all nodes (the advertisements might # not be received by all nodes), they should only be viewed as # a performance optimization to quickly update the caches in # most neighbors. # # Not sure if this is necessary for ipv6 either. file=$(which rdisc 2>/dev/null) if [ -f "$file" ]; then if [ "$OCF_RESKEY_disable_rdisc" != "yes" ] && \ [ "$OCF_RESKEY_disable_rdisc" != "1" ]; then killall -HUP rdisc || rdisc -fs fi fi return 0 done < <(ipv6_list_interfaces) return 1 } # # Add an IP address to our interface or remove it. # ipv4() { declare dev ifaddr maskbits declare addr=$2 while read dev ifaddr maskbits; do if [ -z "$dev" ]; then continue fi if [ "$1" = "add" ]; then if [ -n "$OCF_RESKEY_prefer_interface" ] && \ [ "$OCF_RESKEY_prefer_interface" != $dev ]; then continue fi ipv4_same_subnet $ifaddr/$maskbits $addr if [ $? -ne 0 ]; then continue fi interface_up $dev if [ $? -ne 0 ]; then continue fi if [ "$OCF_RESKEY_monitor_link" = "yes" ]; then network_link_up $dev if [ $? -ne 0 ]; then continue fi fi if [ "${addr/\/*/}" = "${addr}" ]; then addr="$addr/$maskbits" fi ocf_log info "Adding IPv4 address $addr to $dev" fi if [ "$1" = "del" ]; then if [ "${addr/\/*/}" != "$ifaddr" ]; then continue fi addr=`/sbin/ip addr list | grep "$ifaddr/" | head -n 1 | awk '{print $2}'` ocf_log info "Removing IPv4 address $addr from $dev" fi if [ "$1" = "add" ]; then ocf_log debug "Pinging addr ${addr%%/*} from dev $dev" if ping_check inet ${addr%%/*} $dev; then ocf_log err "IPv4 address collision ${addr%%/*}" return 1 fi fi /sbin/ip -f inet addr $1 dev $dev $addr [ $? -ne 0 ] && return 1 # # XXX: Following needed? ifconfig:YES, ifcfg:NO, iproute2:??? # if [ "$1" = "add" ]; then # do that freak arp thing hwaddr=$(/sbin/ip -o link show $dev) hwaddr=${hwaddr/*link\/ether\ /} hwaddr=${hwaddr/\ \*/} addr=${addr/\/*/} ocf_log debug "Sending gratuitous ARP: $addr $hwaddr" arping -q -c 2 -U -I $dev $addr fi file=$(which rdisc 2>/dev/null) if [ -f "$file" ]; then if [ "$OCF_RESKEY_disable_rdisc" != "yes" ] && \ [ "$OCF_RESKEY_disable_rdisc" != "1" ]; then killall -HUP rdisc || rdisc -fs fi fi return 0 done < <(ipv4_list_interfaces) return 1 } # # Usage: # ping_check
[interface] # ping_check() { declare ops="-c 1 -w 2" declare pingcmd="" if [ "$1" = "inet6" ]; then pingcmd="ping6" else pingcmd="ping" fi if [ -n "$3" ]; then ops="$ops -I $3" fi return $($pingcmd $ops $2 &> /dev/null) } # # Usage: # check_interface_up
# check_interface_up() { declare dev declare addr=${2/\/*/} declare currentAddr caExpanded if [ "$1" == "inet6" ]; then addrExpanded=$(ipv6_expand $addr) for currentAddr in `/sbin/ip -f $1 -o addr|awk '{print $4}'`; do caExpanded=$(ipv6_expand $currentAddr) caExpanded=${caExpanded/\/*/} if [ "$addrExpanded" == "$caExpanded" ]; then dev=$(/sbin/ip -f $1 -o addr | grep " ${currentAddr/\/*/}" | awk '{print $2}') break fi done else dev=$(/sbin/ip -f $1 -o addr | grep " $addr/" | awk '{print $2}') fi if [ -z "$dev" ]; then return 1 fi interface_up $dev return $? } # # Usage: # address_configured
# address_configured() { declare line declare addr declare currentAddr caExpanded # Chop off mask bits addr=${2/\/*/} if [ "$1" == "inet6" ]; then addrExpanded=$(ipv6_expand $addr) for currentAddr in `/sbin/ip -f $1 -o addr|awk '{print $4}'`; do caExpanded=$(ipv6_expand $currentAddr) caExpanded=${caExpanded/\/*/} if [ "$addrExpanded" == "$caExpanded" ]; then line=$(/sbin/ip -f $1 -o addr | grep " ${currentAddr/\/*/}"); break fi done else line=$(/sbin/ip -f $1 -o addr | grep " $addr/") fi if [ -z "$line" ]; then return 1 fi return 0 } # # Usage: # ip_op
[quiet] # ip_op() { declare dev declare rtr declare addr=${3/\/*/} declare caExpanded currentAddr if [ "$2" = "status" ]; then ocf_log debug "Checking $3, Level $OCF_CHECK_LEVEL" if [ "$1" == "inet6" ]; then addrExpanded=$(ipv6_expand $addr) for currentAddr in `/sbin/ip -f $1 -o addr|awk '{print $4}'`; do caExpanded=$(ipv6_expand $currentAddr) caExpanded=${caExpanded/\/*/} if [ "$addrExpanded" == "$caExpanded" ]; then dev=$(/sbin/ip -f $1 -o addr | grep " ${currentAddr/\/*/}" | awk '{print $2}') break fi done else dev=$(/sbin/ip -f $1 -o addr | grep " $addr/" | awk '{print $2}') fi if [ -z "$dev" ]; then ocf_log warn "$3 is not configured" return 1 fi ocf_log debug "$3 present on $dev" if [ "$OCF_RESKEY_monitor_link" = "yes" ]; then if ! network_link_up $dev; then ocf_log warn "No link on $dev..." return 1 fi ocf_log debug "Link detected on $dev" fi [ $OCF_CHECK_LEVEL -lt 10 ] && return 0 if ! ping_check $1 $addr $dev; then ocf_log warn "Failed to ping $addr" return 1 fi ocf_log debug "Local ping to $addr succeeded" return 0 fi case $1 in inet) ipv4 $2 $3 return $? ;; inet6) if [ "$2" = "del" ]; then addrExpanded=$(ipv6_expand $addr) for currentAddr in `/sbin/ip -f $1 -o addr|awk '{print $4}'`; do caExpanded=$(ipv6_expand $currentAddr) caExpanded=${caExpanded/\/*/} if [ "$addrExpanded" == "$caExpanded" ]; then addr6=$(/sbin/ip -f $1 -o addr | grep " ${currentAddr/\/*/}" | awk '{print $4}') ipv6 $2 $addr6 return $? fi done fi ipv6 $2 $3 return $? ;; esac return 1 } case ${OCF_RESKEY_family} in inet) ;; inet6) ;; *) if [ "${OCF_RESKEY_address//:/}" != "${OCF_RESKEY_address}" ]; then export OCF_RESKEY_family=inet6 else export OCF_RESKEY_family=inet fi ;; esac # Force ipv6 addresses to lower case if [ "$OCF_RESKEY_family" = "inet6" ]; then OCF_RESKEY_address=$(echo $OCF_RESKEY_address | tr '[:upper:]' '[:lower:]') fi if [ -z "$OCF_CHECK_LEVEL" ]; then OCF_CHECK_LEVEL=0 fi if [ "${OCF_RESKEY_monitor_link}" = "no" ] || [ "${OCF_RESKEY_monitor_link}" = "0" ]; then OCF_RESKEY_monitor_link="no" else OCF_RESKEY_monitor_link="yes" fi case $1 in start) if address_configured ${OCF_RESKEY_family} ${OCF_RESKEY_address}; then ocf_log debug "${OCF_RESKEY_address} already configured" exit 0 fi ip_op ${OCF_RESKEY_family} add ${OCF_RESKEY_address} if [ $? -ne 0 ]; then exit $OCF_ERR_GENERIC fi if [ $NFS_TRICKS -eq 0 ]; then if [ "$OCF_RESKEY_nfslock" = "yes" ] || \ [ "$OCF_RESKEY_nfslock" = "1" ]; then notify_list_broadcast /var/lib/nfs/statd fi fi exit $? ;; stop) if address_configured ${OCF_RESKEY_family} ${OCF_RESKEY_address}; then ip_op ${OCF_RESKEY_family} del ${OCF_RESKEY_address} # Make sure it's down if address_configured ${OCF_RESKEY_family} ${OCF_RESKEY_address}; then ocf_log err "Failed to remove ${OCF_RESKEY_address}" exit 1 fi # XXX Let nfsd/lockd clear their queues; we hope to have a # way to enforce this in the future if [ -z "$OCF_RESKEY_sleeptime" ]; then sleep 10 else if [ "$OCF_RESKEY_sleeptime" -gt "0" ]; then sleep $OCF_RESKEY_sleeptime fi fi else ocf_log debug "${OCF_RESKEY_address} is not configured" fi exit 0 ;; status|monitor) ip_op ${OCF_RESKEY_family} status ${OCF_RESKEY_address} [ $? -ne 0 ] && exit $OCF_NOT_RUNNING check_interface_up ${OCF_RESKEY_family} ${OCF_RESKEY_address} exit $? ;; restart) $0 stop || exit $OCF_ERR_GENERIC $0 start || exit $OCF_ERR_GENERIC exit 0 ;; meta-data) meta_data exit 0 ;; validate-all|verify_all) verify_all exit $? ;; *) echo "usage: $0 {start|stop|status|monitor|restart|meta-data|validate-all}" exit $OCF_ERR_UNIMPLEMENTED ;; esac