#!/bin/sh # # Description: IPsrcaddr - Preferred source(/dest) address modification # # Author: John Sutton # 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 < 1.0 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. Manages the preferred source address for outgoing IP packets The IP address. IP address The netmask for the interface in CIDR format. (ie, 24), or in dotted quad notation 255.255.255.0). Netmask The destination IP/subnet for the route (default: $OCF_RESKEY_destination_default) Destination IP/subnet Proto to match when finding network. E.g. "kernel". Proto Metric. Only needed if incorrect metric value is used. Metric 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). Table 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 " 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 # 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 # Changed status output to "OK" to satisfy ResourceManager's # we_own_resource() function. # # Version 0.1 2002/11/01 17:00:00 John Sutton # First effort but does the job? #