ctdb-scripts: Quote some variable expansions
[nivanova/samba-autobuild/.git] / ctdb / config / events.d / 13.per_ip_routing
index a0df11989541bcc80c196edd92cb5fa869551356..20f9085383e74c13231b983cbac3c4ca25c09d6e 100755 (executable)
 #!/bin/sh
 
-. $CTDB_BASE/functions
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
 loadconfig
 
-[ -z "$CTDB_PER_IP_ROUTING_STATE" ] && {
-       CTDB_PER_IP_ROUTING_STATE="$CTDB_VARDIR/state/per_ip_routing"
-}
+service_name=per_ip_routing
 
-AUTO_LINK_LOCAL="no"
-
-case "$CTDB_PER_IP_ROUTING_CONF" in
-       __auto_link_local__)
-               AUTO_LINK_LOCAL="yes"
-               CTDB_PER_IP_ROUTING_CONF="$CTDB_PER_IP_ROUTING_STATE/auto_link_local.conf"
-               ;;
-       *)
-               [ -z "$CTDB_PER_IP_ROUTING_CONF" ] && {
-                       #echo "No config file found. Nothing to do for 13.per_ip_routing"
-                       exit 0;
-               }
-               ;;
-esac
+# Do nothing if unconfigured 
+[ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
 
-_low=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
-_high=$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH
+table_id_prefix="ctdb."
 
-test -z "$_low" && {
-       echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW not configured";
-       exit 1;
-}
-test -z "$_high" && {
-       echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_HIGH not configured";
-       exit 1;
-}
-test "$_low" -ge "$_high" && {
-       echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$_low] needs to be below CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$_high]";
-       exit 1;
-}
+[ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
+    die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
 
-test -z "$CTDB_PER_IP_ROUTING_RULE_PREF" && {
-       echo "$0: CTDB_PER_IP_ROUTING_RULE_PREF not configured";
-       exit 1;
-}
+[ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
+    die "error: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW] and/or CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] improperly configured"
 
-locknesting=0
-lock_root="$CTDB_PER_IP_ROUTING_STATE"
-host=`hostname`
+if [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -le 253 -a \
+    255 -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
+    die "error: range CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW]..CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] must not include 253-255"
+fi
 
-lock_debug()
+have_link_local_config ()
 {
-       echo -n ""
+    [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ]
 }
 
-############################
-# grab a lock file. Not atomic, but close :)
-# tries to cope with NFS
-lock_file() {
-       if [ -z "$lock_root" ]; then
-               lock_root=`pwd`;
-       fi
-       lckf="$lock_root/$1"
-       machine=`cat "$lckf" 2> /dev/null | cut -d: -f1`
-       pid=`cat "$lckf" 2> /dev/null | cut -d: -f2`
+if ! have_link_local_config && [ ! -r "$CTDB_PER_IP_ROUTING_CONF" ] ; then
+    die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
+fi
 
-       if [ "$pid" = "$$" ]; then
-               locknesting=`expr $locknesting + 1`
-               lock_debug "lock nesting now $locknesting"
-               return 0
-       fi
+######################################################################
 
-       if test -f "$lckf"; then
-               test $machine = $host || {
-                       lock_debug "lock file $lckf is valid for other machine $machine"
-                       stat -c%y "$lckf"
-                       return 1
-               }
-               kill -0 $pid && {
-                       lock_debug "lock file $lckf is valid for process $pid"
-                       stat -c%y "$lckf"
-                       return 1
-               }
-               lock_debug "stale lock file $lckf for $machine:$pid"
-               cat "$lckf"
-               rm -f "$lckf"
+ipv4_is_valid_addr()
+{
+    _ip="$1"
+
+    _count=0
+    # Get the shell to break up the address into 1 word per octet 
+    for _o in $(export IFS="." ; echo $_ip) ; do
+       # The 2>/dev/null stops output from failures where an "octet"
+       # is not numeric.  The test will still fail.
+       if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
+           return 1
        fi
-       echo "$host:$$" > "$lckf"
-       return 0
-}
+       _count=$(($_count + 1))
+    done
 
-############################
-# unlock a lock file
-unlock_file() {
-       if [ -z "$lock_root" ]; then
-               lock_root=`pwd`;
-       fi
-       if [ "$locknesting" != "0" ]; then
-               locknesting=`expr $locknesting - 1`
-               lock_debug "lock nesting now $locknesting"
-       else
-               lckf="$lock_root/$1"
-               rm -f "$lckf"
-       fi
+    # A valid IPv4 address has 4 octets
+    [ $_count -eq 4 ]
 }
 
-generate_table_id () {
-       local _ip=$1
-       local _ipsdir="$CTDB_PER_IP_ROUTING_STATE/ips"
-       local _ipdir="$_ipsdir/$_ip"
-
-       mkdir -p $_ipdir
-
-       #echo "generate_table_id $_ip"
+ensure_ipv4_is_valid_addr ()
+{
+    _event="$1"
+    _ip="$2"
 
-       local _id=`cat $_ipdir/table_id 2>/dev/null| xargs`
-       test -n "$_id" && {
-               #echo "IP: $_ip => OLD TABLE: $_id"
-               table_id=$_id
-               return 0;
-       }
+    ipv4_is_valid_addr "$_ip" || {
+       echo "$0: $_event not an ipv4 address skipping IP:$_ip"
+       exit 0
+    }
+}
 
-       local _low="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
-       local _high="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
-
-       local _newid=""
-       for _id in `seq $_low $_high | xargs`; do
-               local _table_lck="table_id_$_id.lock"
-               lock_file $_table_lck 2>/dev/null || {
-                       continue;
-               }
-               local _taken=`grep "^$_id$" $_ipsdir/*/table_id 2>/dev/null| wc -l | xargs`
-               test x"$_taken" != x"0" && {
-                       unlock_file $_table_lck
-                       #echo "tableid: $_id taken"
-                       continue
-               }
-               _newid=$_id;
-               echo "$_newid" > $_ipdir/table_id
-               unlock_file $_table_lck
-               break;
-       done
+ipv4_host_addr_to_net ()
+{
+    _host="$1"
+    _maskbits="$2"
+
+    # Convert the host address to an unsigned long by splitting out
+    # the octets and doing the math.
+    _host_ul=0
+    for _o in $(export IFS="." ; echo $_host) ; do
+       _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
+    done
+
+    # Calculate the mask and apply it.
+    _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
+    _net_ul=$(( $_host_ul & $_mask_ul ))
+    # Now convert to a network address one byte at a time.
+    _net=""
+    for _o in $(seq 1 4) ; do
+       _net="$(($_net_ul & 255))${_net:+.}${_net}"
+       _net_ul=$(($_net_ul >> 8))
+    done
+
+    echo "${_net}/${_maskbits}"
+}
 
-       test -z "$_newid" && {
-               echo "generate_table_id: out of table ids: $_low - $_high"
-               exit 1;
-       }
+######################################################################
 
-       #echo "IP: $_ip => NEW TABLE: $_newid"
-       table_id=$_newid
-       return 0;
+ensure_rt_tables ()
+{
+    rt_tables="$CTDB_SYS_ETCDIR/iproute2/rt_tables"
+
+    # This file should always exist.  Even if this didn't exist on the
+    # system, adding a route will have created it.  What if we startup
+    # and immediately shutdown?  Let's be sure.
+    if [ ! -f "$rt_tables" ] ; then
+       mkdir -p "${rt_tables%/*}" # dirname
+       touch "$rt_tables"
+    fi
 }
 
-run_release_script_once()
+# Setup a table id to use for the given IP.  We don't need to know it,
+# it just needs to exist in /etc/iproute2/rt_tables.  Fail if no free
+# table id could be found in the configured range.
+ensure_table_id_for_ip ()
 {
-       local _script=$1
-
-       #echo "run_release_script_once[$_script]"
+    _ip=$1
+
+    ensure_rt_tables
+
+    # Maintain a table id for each IP address we've ever seen in
+    # rt_tables.  We use a "ctdb." prefix on the label.
+    _label="${table_id_prefix}${_ip}"
+
+    # This finds either the table id corresponding to the label or a
+    # new unused one (that is greater than all the used ones in the
+    # range).
+    (
+       # Note that die() just gets us out of the subshell...
+       flock --timeout 30 0 || \
+           die "ensure_table_id_for_ip: failed to lock file $rt_tables"
+
+       _new=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
+       while read _t _l ; do
+           # Skip comments
+           case "$_t" in
+               \#*) continue ;;
+           esac
+           # Found existing: done
+           if [ "$_l" = "$_label" ] ; then
+               return 0
+           fi
+           # Potentially update the new table id to be used.  The
+           # redirect stops error spam for a non-numeric value.
+           if [ $_new -le $_t -a \
+               $_t -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] 2>/dev/null ; then
+               _new=$(($_t + 1))
+           fi
+       done
 
-       test -x "$_script" && {
-               #echo "run it: start"
-               $_script || {
-                       echo "release_script: $_script - failed $?"
-                       return $?;
-               }
-               #echo "run it: end"
-       }
+       # If the new table id is legal then add it to the file and
+       # print it.
+       if [ $_new -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] ; then
+           printf "%d\t%s\n" "$_new" "$_label" >>"$rt_tables"
+           return 0
+       else
+           return 1
+       fi
+    ) <"$rt_tables"
+}
 
-       echo '#!/bin/sh' > $_script
-       echo '#' >> $_script
-       echo >> $_script
+# Clean up all the table ids that we might own.
+clean_up_table_ids ()
+{
+    ensure_rt_tables
+
+    (
+       # Note that die() just gets us out of the subshell...
+       flock --timeout 30 0 || \
+           die "clean_up_table_ids: failed to lock file $rt_tables"
+
+       # Delete any items from the file that have a table id in our
+       # range or a label matching our label.  Preserve comments.
+       _tmp="${rt_tables}.$$.ctdb"
+       awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
+           -v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
+           -v pre="$table_id_prefix" \
+           '/^#/ || \
+            !(min <= $1 && $1 <= max) && \
+            !(index($2, pre) == 1) \
+            { print $0 }' "$rt_tables" >"$_tmp"
+
+       mv "$_tmp" "$rt_tables"
+       # The lock is gone - don't do anything else here
+    ) <"$rt_tables"
+}
 
-       chmod +x $_script
+######################################################################
 
-       return 0;
+# This prints the config for an IP, which is either relevant entries
+# from the config file or, if set to the magic link local value, some
+# link local routing config for the IP.
+get_config_for_ip ()
+{
+    _ip="$1"
+
+    if have_link_local_config ; then
+       # When parsing public_addresses also split on '/'.  This means
+       # that we get the maskbits as item #2 without further parsing.
+       while IFS="/$IFS" read _i _maskbits _x ; do
+           if [ "$_ip" = "$_i" ] ; then
+               echo -n "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
+           fi
+       done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
+    else
+       while read _i _rest ; do
+           if [ "$_ip" = "$_i" ] ; then
+               printf "%s\t%s\n" "$_ip" "$_rest"
+           fi
+       done <"$CTDB_PER_IP_ROUTING_CONF"
+    fi
 }
 
-generate_auto_link_local()
+ip_has_configuration ()
 {
-       local _ip=$1
-       local _maskbits=$2
-
-       #echo "generate_auto_link_local $_ip $_maskbits"
-
-       local _netip=`ipv4_host_addr_to_net_addr $_ip $_maskbits`
-
-       local _line="$_ip $_netip/$_maskbits"
-
-       local _lockfile="$CTDB_PER_IP_ROUTING_CONF.lock"
-       local _script="$CTDB_PER_IP_ROUTING_CONF.$$.sh"
-
-       echo "#!/bin/sh" > $_script
-       echo "#" >> $_script
-       echo "" >> $_script
-       echo "_config=\`cat $CTDB_PER_IP_ROUTING_CONF 2>/dev/null\`" >> $_script
-       echo "_exact=\`echo -n \"\$_config\" | grep \"^$_line\$\" | wc -l | xargs\`" >> $_script
-       echo "" >> $_script
-
-       echo "test x\"\$_exact\" = x\"1\" && {" >> $_script
-       echo "    exit 0;" >> $_script
-       echo "}" >> $_script
-       echo "" >> $_script
-
-       echo "_tmp=\"$CTDB_PER_IP_ROUTING_CONF.$$.tmp\"" >> $_script
-       echo "echo -n \"\$_config\" | grep -v \"^$_ip \" | cat > \$_tmp || {" >> $_script
-       echo "    echo \"echo -n \\\"\$_config\\\" | grep -v \\\"^$_ip \\\" > \$_tmp - failed\"" >> $_script
-       echo "    exit 1;" >> $_script
-       echo "}" >> $_script
-       echo "echo \"$_line\" >> \$_tmp || {" >> $_script
-       echo "    echo \"echo \\\"$_line\\\" >> \$_tmp - failed\"" >> $_script
-       echo "    exit 1;" >> $_script
-       echo "}" >> $_script
-       echo "" >> $_script
-
-       echo "mv \$_tmp $CTDB_PER_IP_ROUTING_CONF || {" >> $_script
-       echo "    echo \"mv \$_tmp $CTDB_PER_IP_ROUTING_CONF - failed\"" >> $_script
-       echo "    exit 1;" >> $_script
-       echo "}" >> $_script
-       echo "" >> $_script
-
-       echo "echo \"Added '$_line' to $CTDB_PER_IP_ROUTING_CONF\"">> $_script
-       echo "exit 0" >> $_script
-
-       chmod +x $_script
-
-       test -f $_lockfile || {
-               touch $_lockfile
-       }
+    _ip="$1"
 
-       flock --timeout 30 $_lockfile $_script
-       ret=$?
-       rm $_script
-       return $ret
+    [ -n "$(get_config_for_ip $_ip)" ]
 }
 
-generate_per_ip_routing()
+add_routing_for_ip ()
 {
-       local _ip=$1
-       local _maskbits=$2
-       local _iface=$3
-       local _readonly=$4
-       local _ipdir="$CTDB_PER_IP_ROUTING_STATE/ips/$_ip"
-
-       table_id=""
-       release_script="$_ipdir/per_ip_routing_release.sh"
-       setup_script="$_ipdir/per_ip_routing_setup.sh"
-
-       test x"$_readonly" = x"yes" && {
-               test -d $_ipdir || {
-                       return 1;
-               }
-               return 0;
-       }
+    _iface="$1"
+    _ip="$2"
 
-       mkdir -p $_ipdir || {
-               echo "mkdir -p $_ipdir failed"
-               return 1;
-       }
-       echo "$_ip" > $_ipdir/ip
+    # Do nothing if no config for this IP.
+    ip_has_configuration "$_ip" || return 0
 
-       generate_table_id $_ip
+    ensure_table_id_for_ip "$_ip" || \
+       die "add_routing_for_ip: out of table ids in range $CTDB_PER_IP_ROUTING_TABLE_ID_LOW - $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
 
-       test x"$AUTO_LINK_LOCAL" = x"yes" && {
-               generate_auto_link_local $_ip $_maskbits
-       }
+    _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+    _table_id="${table_id_prefix}${_ip}"
 
-       run_release_script_once $release_script
+    del_routing_for_ip "$_ip" >/dev/null 2>&1
 
-       echo '#!/bin/sh' > $setup_script
-       echo '#' >> $setup_script
-       echo >> $setup_script
-       chmod +x $setup_script
+    ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
+       die "add_routing_for_ip: failed to add rule for $_ip"
 
-       return 0;
+    # Add routes to table for any lines matching the IP.
+    get_config_for_ip "$_ip" |
+    while read _i _dest _gw ; do
+       _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
+       ip route add $_r || \
+           die "add_routing_for_ip: failed to add route: $_r"
+    done
 }
 
-setup_per_ip_routing()
+del_routing_for_ip ()
 {
-       local _ip=$1
-       local _iface=$2
-       local _table_id=$3
-       local _release_script=$4
-       local _setup_script=$5
+    _ip="$1"
+
+    _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+    _table_id="${table_id_prefix}${_ip}"
+
+    # Do this unconditionally since we own any matching table ids.
+    # However, print a meaningful message if something goes wrong.
+    _cmd="ip rule del from $_ip pref $_pref table $_table_id"
+    _out=$($_cmd 2>&1) || \
+       cat <<EOF
+WARNING: Failed to delete policy routing rule
+  Command "$_cmd" failed:
+  $_out
+EOF
+    # This should never usually fail, so don't redirect output.
+    # However, it can fail when deleting a rogue IP, since there will
+    # be no routes for that IP.  In this case it should only fail when
+    # the rule deletion above has already failed because the table id
+    # is invalid.  Therefore, go to a little bit of trouble to indent
+    # the failure message so that it is associated with the above
+    # warning message and doesn't look too nasty.
+    ip route flush table "$_table_id" 2>&1 | sed -e 's@^.@  &@'
+}
 
-       local _config=`cat $CTDB_PER_IP_ROUTING_CONF`
-       local _lines=`echo -n "$_config" | grep -n "^$_ip " | cut -d ':' -f1 | xargs`
+######################################################################
 
-       local _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+flush_rules_and_routes ()
+{
+       ip rule show |
+       while read _p _x _i _x _t ; do
+           # Remove trailing colon after priority/preference.
+           _p="${_p%:}"
+           # Only remove rules that match our priority/preference.
+           [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+
+           echo "Removing ip rule for public address $_i for routing table $_t"
+           ip rule del from "$_i" table "$_t" pref "$_p"
+           ip route flush table "$_t" 2>/dev/null
+       done
+}
 
-       test -n "$_lines" && {
-               echo "ip rule del from $_ip pref $_pref table $_table_id" >> $_release_script
-               echo "ip route flush table $_table_id 2>/dev/null" >> $_release_script
+# Add any missing routes.  Some might have gone missing if, for
+# example, all IPs on the network were removed (possibly if the
+# primary was removed).  If $1 is "force" then (re-)add all the
+# routes.
+add_missing_routes ()
+{
+    $CTDB ip -v -X | {
+       read _x # skip header line
+
+       # Read the rest of the lines.  We're only interested in the
+       # "IP" and "ActiveInterface" columns.  The latter is only set
+       # for addresses local to this node, making it easy to skip
+       # non-local addresses.  For each IP local address we check if
+       # the relevant routing table is populated and populate it if
+       # not.
+       while IFS="|" read _x _ip _x _iface _x ; do
+           [ -n "$_iface" ] || continue
+           
+           _table_id="${table_id_prefix}${_ip}"
+           if [ -z "$(ip route show table $_table_id 2>/dev/null)" -o \
+               "$1" = "force" ]  ; then
+               add_routing_for_ip "$_iface" "$_ip"
+           fi
+       done
+    } || exit $?
+}
 
-               cmd="ip rule del from $_ip pref $_pref 2>/dev/null"
-               echo "$cmd" >> $_setup_script
+# Remove rules/routes for addresses that we're not hosting.  If a
+# releaseip event failed in an earlier script then we might not have
+# had a chance to remove the corresponding rules/routes.
+remove_bogus_routes ()
+{
+    # Get a IPs current hosted by this node, each anchored with '@'.
+    _ips=$($CTDB ip -v -X | awk -F'|' 'NR > 1 && $4 != "" {printf "@%s@\n", $2}')
+
+    ip rule show |
+    while read _p _x _i _x _t ; do
+       # Remove trailing colon after priority/preference.
+       _p="${_p%:}"
+       # Only remove rules that match our priority/preference.
+       [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+       # Only remove rules for which we don't have an IP.  This could
+       # be done with grep, but let's do it with shell prefix removal
+       # to avoid unnecessary processes.  This falls through if
+       # "@${_i}@" isn't present in $_ips.
+       [ "$_ips" = "${_ips#*@${_i}@}" ] || continue
+
+       echo "Removing ip rule/routes for unhosted public address $_i"
+       del_routing_for_ip "$_i"
+    done
+}
 
-               cmd="ip route flush table $_table_id 2>/dev/null"
-               echo "$cmd" >> $_setup_script
+######################################################################
 
-               cmd="ip rule add from $_ip pref $_pref table $_table_id"
-               echo "$cmd || {" >> $_setup_script
-               echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
-               echo "    exit \$ret" >> $_setup_script
-               echo "}" >> $_setup_script
-       }
-       local _l
-       for _l in $_lines; do
-               local _line=`echo -n "$_config" | head -n $_l | tail -n 1`
-               local _dest=`echo -n "$_line" | cut -d ' ' -f 2`
-               local _gw=`echo -n "$_line" | cut -d ' ' -f 3`
-
-               local _via=""
-               test -n "$_gw" && {
-                       _via="via $_gw"
-               }
-
-               cmd="ip route add $_dest $_via dev $_iface table $_table_id"
-               echo "$cmd || {" >> $_setup_script
-               echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
-               echo "    exit \$ret" >> $_setup_script
-               echo "}" >> $_setup_script
-       done
+service_reconfigure ()
+{
+    add_missing_routes "force"
+    remove_bogus_routes
 
-       $_setup_script
-       return $?;
+    # flush our route cache
+    set_proc sys/net/ipv4/route/flush 1
 }
 
-case "$1" in
-     #############################
-     # called when ctdbd starts up
-     startup)
-       # cleanup old rules
-       pref=$CTDB_PER_IP_ROUTING_RULE_PREF
-       rules=`ip rule show | grep "^$pref:" | sed -e 's/.*from \([^ ][^ ]*\) lookup \([^ ][^ ]*\)/\2;\1/' | xargs`
-       for r in $rules; do
-               table_id=`echo -n "$r" | cut -d ';' -f1`
-               ip=`echo -n "$r" | cut -d ';' -f2-`
-
-               echo "Removing ip rule for public address $ip for routing table $table_id"
-               cmd="ip rule del from $ip table $table_id pref $pref"
-               #echo $cmd
-               eval $cmd
-               cmd="ip route flush table $table_id"
-               #echo $cmd
-               eval $cmd 2>/dev/null
-       done
-
-       # make sure that we only respond to ARP messages from the NIC where
-       # a particular ip address is associated.
-       [ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
-           echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
-       }
+######################################################################
 
-       mkdir -p $CTDB_PER_IP_ROUTING_STATE
+ctdb_check_args "$@"
 
-       ;;
+ctdb_service_check_reconfigure
 
-     shutdown)
+case "$1" in
+    startup)
+       flush_rules_and_routes
 
-       for s in $CTDB_PER_IP_ROUTING_STATE/ips/*/per_ip_routing_release.sh; do
-               run_release_script_once "$s"
-       done
-       rm -rf $CTDB_PER_IP_ROUTING_STATE
+       # make sure that we only respond to ARP messages from the NIC
+       # where a particular ip address is associated.
+       get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
+           set_proc sys/net/ipv4/conf/all/arp_filter 1
+       }
+       ;;
 
+    shutdown)
+       flush_rules_and_routes
+       clean_up_table_ids
        ;;
 
-     ################################################
-     # called when ctdbd wants to claim an IP address
-     takeip)
-       if [ $# != 4 ]; then
-          echo "must supply interface, IP and maskbits"
-          exit 1
-       fi
+    takeip)
        iface=$2
        ip=$3
        maskbits=$4
 
-       ipv4_is_valid_addr $ip || {
-               echo "$0: $1 not an ipv4 address skipping IP:$ip"
-               exit 0;
-       }
-
-       [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
-               echo "$0: $1 No state directory found, waiting for startup."
-               exit 0;
-       }
-
-       generate_per_ip_routing $ip $maskbits $iface "no" || {
-               echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface no - failed"
-               exit 1;
-       }
-
-       setup_per_ip_routing $ip $iface $table_id $release_script $setup_script || {
-               echo "$0: $1: setup_per_ip_routing $ip $iface $table_id $release_script $setup_script - failed"
-               exit 1;
-       }
-
-       setup_iface_ip_readd_script $iface $ip $maskbits $setup_script || {
-               echo "$0: $1: setup_iface_ip_readd_script $iface $ip $maskbits $setup_script - failed"
-               exit 1;
-       }
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       add_routing_for_ip "$iface" "$ip"
 
        # flush our route cache
-       echo 1 > /proc/sys/net/ipv4/route/flush
-       ctdb gratiousarp $ip $iface
+       set_proc sys/net/ipv4/route/flush 1
 
+       $CTDB gratiousarp "$ip" "$iface"
        ;;
 
-     ################################################
-     # called when ctdbd wants to claim an IP address
-     updateip)
-       if [ $# != 5 ]; then
-          echo "must supply old interface, new interface, IP and maskbits"
-          exit 1
-       fi
+    updateip)
        oiface=$2
        niface=$3
        ip=$4
        maskbits=$5
 
-       ipv4_is_valid_addr $ip || {
-               echo "$0: $1 not an ipv4 address skipping IP:$ip"
-               exit 0;
-       }
-
-       [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
-               echo "$0: $1 No state directory found, waiting for startup."
-               exit 0;
-       }
-
-       generate_per_ip_routing $ip $maskbits $niface "no" || {
-               echo "$0: $1: generate_per_ip_routing $ip $maskbits $niface no - failed"
-               exit 1;
-       }
-
-       setup_per_ip_routing $ip $niface $table_id $release_script $setup_script || {
-               echo "$0: $1: setup_per_ip_routing $ip $niface $table_id $release_script $setup_script - failed"
-               exit 1;
-       }
-
-       setup_iface_ip_readd_script $niface $ip $maskbits $setup_script || {
-               echo "$0: $1: setup_iface_ip_readd_script $niface $ip $maskbits $setup_script - failed"
-               exit 1;
-       }
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       add_routing_for_ip "$niface" "$ip"
 
        # flush our route cache
-       echo 1 > /proc/sys/net/ipv4/route/flush
-
-       ctdb gratiousarp $ip $niface
-       tickle_tcp_connections $ip
+       set_proc sys/net/ipv4/route/flush 1
 
+       $CTDB gratiousarp "$ip" "$niface"
+       tickle_tcp_connections "$ip"
        ;;
 
-     ##################################################
-     # called when ctdbd wants to release an IP address
-     releaseip)
-       if [ $# != 4 ]; then
-          echo "must supply interface, IP and maskbits"
-          exit 1
-       fi
-
+    releaseip)
        iface=$2
        ip=$3
        maskbits=$4
 
-       ipv4_is_valid_addr $ip || {
-               echo "$0: $1 not an ipv4 address skipping IP:$ip"
-               exit 0;
-       }
-
-       [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
-               echo "$0: $1 No state directory found, waiting for startup."
-               exit 0;
-       }
-
-       generate_per_ip_routing $ip $maskbits $iface "yes" || {
-               echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface yes - failed"
-               exit 1;
-       }
-
-       run_release_script_once "$release_script"
-
-       ;;
-
-
-     ###########################################
-     # called when ctdbd has finished a recovery
-     recovered)
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       del_routing_for_ip "$ip"
        ;;
 
-     ####################################
-     # called when ctdbd is shutting down
-     shutdown)
+    ipreallocated)
+       add_missing_routes
+       remove_bogus_routes
        ;;
 
-     monitor)
-       ;;
     *)
        ctdb_standard_event_handler "$@"
        ;;
 esac
 
 exit 0
-