diff options
Diffstat (limited to '')
-rwxr-xr-x | heartbeat/IPsrcaddr | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/heartbeat/IPsrcaddr b/heartbeat/IPsrcaddr new file mode 100755 index 0000000..c732ce8 --- /dev/null +++ b/heartbeat/IPsrcaddr @@ -0,0 +1,631 @@ +#!/bin/sh +# +# Description: IPsrcaddr - Preferred source(/dest) address modification +# +# Author: John Sutton <john@scl.co.uk> +# Support: users@clusterlabs.org +# License: GNU General Public License (GPL) +# Copyright: SCL Internet +# +# Based on the IPaddr script. +# +# This script manages the preferred source address associated with +# packets which originate on the localhost and are routed through the +# matching route. By default, i.e. without the use of this script or +# similar, these packets will carry the IP of the primary i.e. the +# non-aliased interface. This can be a nuisance if you need to ensure +# that such packets carry the same IP irrespective of which host in +# a redundant cluster they actually originate from. +# +# It can add a preferred source address, or remove one. +# +# usage: IPsrcaddr {start|stop|status|monitor|validate-all|meta-data} +# +# The "start" arg adds a preferred source address. +# +# Surprisingly, the "stop" arg removes it. :-) +# +# NOTES: +# +# 1) There must be one and not more than 1 matching route! Mainly because +# I can't see why you should have more than one. And if there is more +# than one, we would have to box clever to find out which one is to be +# modified, or we would have to pass its identity as an argument. +# +# 2) The script depends on Alexey Kuznetsov's ip utility from the +# iproute aka iproute2 package. +# +# 3) No checking is done to see if the passed in IP address can +# reasonably be associated with the interface on which the default +# route exists. So unless you want to deliberately spoof your source IP, +# check it! Normally, I would expect that your haresources looks +# something like: +# +# nodename ip1 ip2 ... ipN IPsrcaddr::ipX +# +# where ipX is one of the ip1 to ipN. +# +# OCF parameters are as below: +# OCF_RESKEY_ipaddress + +####################################################################### +# Initialization: +: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} +. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs +. ${OCF_FUNCTIONS_DIR}/findif.sh + +# Defaults +OCF_RESKEY_ipaddress_default="" +OCF_RESKEY_cidr_netmask_default="" +OCF_RESKEY_destination_default="0.0.0.0/0" +OCF_RESKEY_proto_default="" +OCF_RESKEY_metric_default="" +OCF_RESKEY_table_default="" + +: ${OCF_RESKEY_ipaddress=${OCF_RESKEY_ipaddress_default}} +: ${OCF_RESKEY_cidr_netmask=${OCF_RESKEY_cidr_netmask_default}} +: ${OCF_RESKEY_destination=${OCF_RESKEY_destination_default}} +: ${OCF_RESKEY_proto=${OCF_RESKEY_proto_default}} +: ${OCF_RESKEY_metric=${OCF_RESKEY_metric_default}} +: ${OCF_RESKEY_table=${OCF_RESKEY_table_default}} +####################################################################### + +[ -z "$OCF_RESKEY_proto" ] && PROTO="" || PROTO="proto $OCF_RESKEY_proto" +[ -z "$OCF_RESKEY_table" ] && TABLE="" || TABLE="table $OCF_RESKEY_table" + +USAGE="usage: $0 {start|stop|status|monitor|validate-all|meta-data}"; + + CMDSHOW="$IP2UTIL route show $TABLE to exact $OCF_RESKEY_destination" +CMDCHANGE="$IP2UTIL route change to " + +if [ "$OCF_RESKEY_destination" != "0.0.0.0/0" ]; then + CMDSHOW="$CMDSHOW src $OCF_RESKEY_ipaddress" +fi + +if [ "$OCF_RESKEY_table" = "local" ]; then + TABLE="$TABLE local" +fi + +SYSTYPE="`uname -s`" + +usage() { + echo $USAGE >&2 +} + +meta_data() { + cat <<END +<?xml version="1.0"?> +<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> +<resource-agent name="IPsrcaddr" version="1.0"> +<version>1.0</version> + +<longdesc lang="en"> +Resource script for IPsrcaddr. It manages the preferred source address +modification. + +Note: DHCP should not be enabled for the interface serving the preferred +source address. Enabling DHCP may result in unexpected behavior, such as +the automatic addition of duplicate or conflicting routes. This may +cause the IPsrcaddr resource to fail, or it may produce undesired +behavior while the resource continues to run. +</longdesc> +<shortdesc lang="en">Manages the preferred source address for outgoing IP packets</shortdesc> + +<parameters> +<parameter name="ipaddress" unique="0" required="1"> +<longdesc lang="en"> +The IP address. +</longdesc> +<shortdesc lang="en">IP address</shortdesc> +<content type="string" default="${OCF_RESKEY_ipaddress_default}" /> +</parameter> + +<parameter name="cidr_netmask"> +<longdesc lang="en"> +The netmask for the interface in CIDR format. (ie, 24), or in +dotted quad notation 255.255.255.0). +</longdesc> +<shortdesc lang="en">Netmask</shortdesc> +<content type="string" default="${OCF_RESKEY_cidr_netmask_default}"/> +</parameter> + +<parameter name="destination"> +<longdesc lang="en"> +The destination IP/subnet for the route (default: $OCF_RESKEY_destination_default) +</longdesc> +<shortdesc lang="en">Destination IP/subnet</shortdesc> +<content type="string" default="${OCF_RESKEY_destination_default}" /> +</parameter> + +<parameter name="proto"> +<longdesc lang="en"> +Proto to match when finding network. E.g. "kernel". +</longdesc> +<shortdesc lang="en">Proto</shortdesc> +<content type="string" default="${OCF_RESKEY_proto_default}" /> +</parameter> + +<parameter name="metric"> +<longdesc lang="en"> +Metric. Only needed if incorrect metric value is used. +</longdesc> +<shortdesc lang="en">Metric</shortdesc> +<content type="string" default="${OCF_RESKEY_metric_default}" /> +</parameter> + +<parameter name="table"> +<longdesc lang="en"> +Table to modify and use for interface lookup. E.g. "local". + +The table has to have a route matching the "destination" parameter. + +This can be used for policy based routing. See man ip-rule(8). +</longdesc> +<shortdesc lang="en">Table</shortdesc> +<content type="string" default="${OCF_RESKEY_table_default}" /> +</parameter> + +</parameters> + +<actions> +<action name="start" timeout="20s" /> +<action name="stop" timeout="20s" /> +<action name="monitor" depth="0" timeout="20s" interval="10s" /> +<action name="validate-all" timeout="5s" /> +<action name="meta-data" timeout="5s" /> +</actions> +</resource-agent> +END +} + +errorexit() { + ocf_exit_reason "$*" + exit $OCF_ERR_GENERIC +} + +# +# We can distinguish 3 cases: no preferred source address, a +# preferred source address exists which matches that specified, and one +# exists but doesn't match that specified. srca_read() returns 1,0,2 +# respectively. +# +# The output of route show is something along the lines of: +# +# default via X.X.X.X dev eth1 src Y.Y.Y.Y +# +# where the src clause "src Y.Y.Y.Y" may or may not be present + +WS="[[:blank:]]" +OCTET="[0-9]\{1,3\}" +IPADDR="\($OCTET\.\)\{3\}$OCTET" +SRCCLAUSE="src$WS$WS*\($IPADDR\)" +MATCHROUTE="\(.*${WS}\)\($SRCCLAUSE\)\($WS.*\|$\)" +METRICCLAUSE=".*\(metric$WS[^ ]\+\)" +PROTOCLAUSE=".*\(proto$WS[^ ]\+\).*" +FINDIF=findif + +# findif needs that to be set +export OCF_RESKEY_ip=$OCF_RESKEY_ipaddress + +srca_read() { + # Capture matching route - doublequotes prevent word splitting... + ROUTE="`$CMDSHOW dev $INTERFACE 2> /dev/null`" || errorexit "command '$CMDSHOW' failed" + + # ... so we can make sure there is only 1 matching route + [ 1 -eq `echo "$ROUTE" | wc -l` ] || \ + errorexit "more than 1 matching route exists" + + # But there might still be no matching route + [ "$OCF_RESKEY_destination" = "0.0.0.0/0" ] && [ -z "$ROUTE" ] && \ + ! ocf_is_probe && [ "$__OCF_ACTION" != stop ] && errorexit "no matching route exists" + + # Sed out the source ip address if it exists + SRCIP=`echo $ROUTE | sed -n "s/$MATCHROUTE/\3/p"` + + # and what remains after stripping out the source ip address clause + ROUTE_WO_SRC=`echo $ROUTE | sed "s/$MATCHROUTE/\1\5/"` + + # using "src <ip>" only returns output if there's a match + if [ "$OCF_RESKEY_destination" != "0.0.0.0/0" ]; then + [ -z "$ROUTE" ] && return 1 || return 0 + fi + + [ -z "$SRCIP" ] && return 1 + [ $SRCIP = $1 ] && return 0 + [ "$__OCF_ACTION" = "monitor" ] || [ "$__OCF_ACTION" = "status" ] && [ "${ROUTE%% *}" = "default" ] && return 1 + return 2 +} + +# +# Add (or change if it already exists) the preferred source address +# The exit code should conform to LSB exit codes. +# + +srca_start() { + srca_read $1 + + rc=$? + if [ $rc = 0 ]; then + rc=$OCF_SUCCESS + ocf_log info "The ip route has been already set.($NETWORK, $INTERFACE, $ROUTE_WO_SRC)" + else + $IP2UTIL route replace $TABLE $NETWORK dev $INTERFACE $PROTO src $1 $METRIC || \ + errorexit "command 'ip route replace $TABLE $NETWORK dev $INTERFACE $PROTO src $1 $METRIC' failed" + + if [ "$OCF_RESKEY_destination" = "0.0.0.0/0" ] ;then + $CMDCHANGE $ROUTE_WO_SRC src $1 || \ + errorexit "command '$CMDCHANGE $ROUTE_WO_SRC src $1' failed" + fi + rc=$? + fi + + return $rc +} + +# +# Remove (if it exists) the preferred source address. +# If one exists but it's not the same as the one specified, that's +# an error. Maybe that's the wrong behaviour because if this fails +# then when IPaddr releases the associated interface (if there is one) +# your matching route will also get dropped ;-( +# The exit code should conform to LSB exit codes. +# + +srca_stop() { + srca_read $1 + rc=$? + + if [ $rc = 1 ]; then + # We do not have a preferred source address for now + ocf_log info "No preferred source address defined, nothing to stop" + exit $OCF_SUCCESS + fi + + [ $rc = 2 ] && errorexit "The address you specified to stop does not match the preferred source address" + + if [ -z "$TABLE" ] || [ "${TABLE#table }" = "main" ]; then + SCOPE="link" + else + SCOPE="host" + fi + + PRIMARY_IP="$($IP2UTIL -4 -o addr show dev $INTERFACE primary | awk '{split($4,a,"/");print a[1]}')" + OPTS="proto kernel scope $SCOPE src $PRIMARY_IP" + + $IP2UTIL route replace $TABLE $NETWORK dev $INTERFACE $OPTS $METRIC || \ + errorexit "command 'ip route replace $TABLE $NETWORK dev $INTERFACE $OPTS $METRIC' failed" + + if [ "$OCF_RESKEY_destination" = "0.0.0.0/0" ] ;then + $CMDCHANGE $ROUTE_WO_SRC src $PRIMARY_IP || \ + errorexit "command '$CMDCHANGE $ROUTE_WO_SRC src $PRIMARY_IP' failed" + fi + + return $? +} + +srca_status() { + srca_read $1 + + case $? in + 0) echo "OK" + return $OCF_SUCCESS;; + + 1) echo "No preferred source address defined" + return $OCF_NOT_RUNNING;; + + 2) echo "Preferred source address has incorrect value" + return $OCF_ERR_GENERIC;; + esac +} + +# A not reliable IP address checking function, which only picks up those _obvious_ violations... +# +# It accepts IPv4 address in dotted quad notation, for example "192.168.1.1" +# +# 100% confidence whenever it reports "negative", +# but may get false "positive" answer. +# +CheckIP() { + ip="$1" + case $ip in + *[!0-9.]*) #got invalid char + false;; + .*|*.) #begin or end by ".", which is invalid + false;; + *..*) #consecutive ".", which is invalid + false;; + *.*.*.*.*) #four decimal dots, which is too many + false;; + *.*.*.*) #exactly three decimal dots, candidate, evaluate each field + local IFS=. + set -- $ip + if + ( [ $1 -le 254 ] && [ $2 -le 254 ] && [ $3 -le 254 ] && [ $4 -le 254 ] ) + then + if [ $1 -eq 127 ]; then + ocf_exit_reason "IP address [$ip] is a loopback address, thus can not be preferred source address" + exit $OCF_ERR_CONFIGURED + fi + else + true + fi + ;; + *) #less than three decimal dots + false;; + esac + return $? # This return is unnecessary, this comment too :) +} + +# +# Find out which interface or alias serves the given IP address +# The argument is an IP address, and its output +# is an (aliased) interface name (e.g., "eth0" and "eth0:0"). +# +find_interface_solaris() { + + + $IFCONFIG $IFCONFIG_A_OPT | $AWK '{if ($0 ~ /.*: / && NR > 1) {print "\n"$0} else {print}}' | + while read ifname linkstuff + do + : ifname = $ifname + read inet addr junk + : inet = $inet addr = $addr + while + read line && [ "X$line" != "X" ] + do + : Nothing + done + + # This doesn't look right for a box with multiple NICs. + # It looks like it always selects the first interface on + # a machine. Yet, we appear to use the results for this case too... + ifname=`echo "$ifname" | sed s'%:*$%%'` + + case $addr in + addr:$BASEIP) echo $ifname; return $OCF_SUCCESS;; + $BASEIP) echo $ifname; return $OCF_SUCCESS;; + esac + done + return $OCF_ERR_GENERIC +} + + +# +# Find out which interface or alias serves the given IP address +# The argument is an IP address, and its output +# is an (aliased) interface name (e.g., "eth0" and "eth0:0"). +# +find_interface_generic() { + + local iface=`$IP2UTIL -o -f inet addr show | grep "\ $BASEIP" \ + | cut -d ' ' -f2 | grep -v '^ipsec[0-9][0-9]*$'` + if [ -z "$iface" ]; then + return $OCF_ERR_GENERIC + else + echo $iface + return $OCF_SUCCESS + fi +} + + +# +# Find out which interface or alias serves the given IP address +# The argument is an IP address, and its output +# is an (aliased) interface name (e.g., "eth0" and "eth0:0"). +# +find_interface() { + case "$SYSTYPE" in + SunOS) + IF=`find_interface_solaris $BASEIP` + ;; + *) + IF=`find_interface_generic $BASEIP` + ;; + esac + + echo $IF + return $OCF_SUCCESS; +} + + +ip_status() { + + BASEIP="$1" + case "$SYSTYPE" in + Darwin) + # Treat Darwin the same as the other BSD variants (matched as *BSD) + SYSTYPE="${SYSTYPE}BSD" + ;; + *) + ;; + esac + + + case "$SYSTYPE" in + *BSD) + $IFCONFIG $IFCONFIG_A_OPT | grep "inet.*[: ]$BASEIP " >/dev/null 2>&1 + if [ $? = 0 ]; then + return $OCF_SUCCESS + else + return $OCF_NOT_RUNNING + fi;; + + Linux|SunOS) + IF=`find_interface "$BASEIP"` + if [ -z "$IF" ]; then + return $OCF_NOT_RUNNING + fi + + case $IF in + lo*) + ocf_exit_reason "IP address [$BASEIP] is served by loopback, thus can not be preferred source address" + exit $OCF_ERR_CONFIGURED + ;; + *)return $OCF_SUCCESS;; + esac + ;; + + *) + if [ -z "$IF" ]; then + return $OCF_NOT_RUNNING + else + return $OCF_SUCCESS + fi;; + esac +} + + +srca_validate_all() { + + if [ -z "$OCF_RESKEY_ipaddress" ]; then + # usage + ocf_exit_reason "Please set OCF_RESKEY_ipaddress to the preferred source IP address!" + return $OCF_ERR_CONFIGURED + fi + + if ! echo "$OCF_RESKEY_destination" | grep -q "/"; then + return $OCF_ERR_CONFIGURED + fi + + + if ! [ "x$SYSTYPE" = "xLinux" ]; then + # checks after this point are only relevant for linux. + return $OCF_SUCCESS + fi + + check_binary $AWK + case "$SYSTYPE" in + *BSD|SunOS) + check_binary $IFCONFIG + ;; + esac + +# The IP address should be in good shape + if CheckIP "$ipaddress"; then + : + else + ocf_exit_reason "Invalid IP address [$ipaddress]" + return $OCF_ERR_CONFIGURED + fi + + if ocf_is_probe; then + return $OCF_SUCCESS + fi + +# We should serve this IP address of course + if [ "$OCF_CHECK_LEVEL" -eq 10 ]; then + if ip_status "$ipaddress"; then + : + else + ocf_exit_reason "We are not serving [$ipaddress], hence can not make it a preferred source address" + return $OCF_ERR_INSTALLED + fi + fi + return $OCF_SUCCESS +} + +if + ( [ $# -ne 1 ] ) +then + usage + exit $OCF_ERR_ARGS +fi + +# These operations do not require the OCF instance parameters to be set +case $1 in + meta-data) meta_data + exit $OCF_SUCCESS + ;; + usage) usage + exit $OCF_SUCCESS + ;; + *) + ;; +esac + +ipaddress="$OCF_RESKEY_ipaddress" + +[ "$__OCF_ACTION" != "validate-all" ] && OCF_CHECK_LEVEL=10 +srca_validate_all +rc=$? +if [ $rc -ne $OCF_SUCCESS ]; then + case $1 in + # if we can't validate the configuration during a stop, that + # means the resources isn't configured correctly. There's no way + # to actually stop the resource in this situation because there's + # no way it could have even started. Return success here + # to indicate that the resource is not running, otherwise the + # stop action will fail causing the node to be fenced just because + # of a mis configuration. + stop) exit $OCF_SUCCESS;; + *) exit $rc;; + esac +fi + +findif_out=`$FINDIF` +rc=$? +[ $rc -ne 0 ] && { + ocf_exit_reason "[$FINDIF] failed" + exit $rc +} + +INTERFACE=`echo $findif_out | awk '{print $1}'` +LISTROUTE=`$IP2UTIL route list dev $INTERFACE scope link $PROTO match $ipaddress` +[ -z "$PROTO" ] && PROTO=`echo $LISTROUTE | sed -n "s/$PROTOCLAUSE/\1/p"` +if [ -n "$OCF_RESKEY_metric" ]; then + METRIC="metric $OCF_RESKEY_metric" +elif [ -z "$TABLE" ] || [ "${TABLE#table }" = "main" ]; then + METRIC=`echo $LISTROUTE | sed -n "s/$METRICCLAUSE/\1/p"` +else + METRIC="" +fi +if [ "$OCF_RESKEY_destination" = "0.0.0.0/0" ] ;then + NETWORK=`echo $LISTROUTE | grep -m 1 -o '^[^ ]*'` + + if [ -z "$NETWORK" ]; then + err_str="command '$IP2UTIL route list dev $INTERFACE scope link $PROTO" + err_str="$err_str match $ipaddress' failed to find a matching route" + + if [ "$__OCF_ACTION" = "start" ]; then + ocf_exit_reason "$err_str" + exit $OCF_ERR_ARGS + elif ! ocf_is_probe; then + ocf_log warn "$err_str" + else + ocf_log debug "$err_str" + fi + fi +else + NETWORK="$OCF_RESKEY_destination" +fi + +case $1 in + start) srca_start $ipaddress + ;; + stop) srca_stop $ipaddress + ;; + status) srca_status $ipaddress + ;; + monitor) srca_status $ipaddress + ;; + validate-all) srca_validate_all + ;; + *) usage + exit $OCF_ERR_UNIMPLEMENTED + ;; +esac + +exit $? + +# +# Version 0.3 2002/11/04 17:00:00 John Sutton <john@scl.co.uk> +# Name changed from IPsrcroute to IPsrcaddr and now reports errors +# using ha_log rather than on stderr. +# +# Version 0.2 2002/11/02 17:00:00 John Sutton <john@scl.co.uk> +# Changed status output to "OK" to satisfy ResourceManager's +# we_own_resource() function. +# +# Version 0.1 2002/11/01 17:00:00 John Sutton <john@scl.co.uk> +# First effort but does the job? +# |