ctdb-scripts: Fix regression in updateip code
[obnox/samba/samba-obnox.git] / ctdb / config / events.d / 10.interface
index a82e4295dd103a1c4717fd09a7cf5f2bf2ffe589..7445200b3287014db9034b9a64f7ad94344324c7 100755 (executable)
@@ -5,6 +5,9 @@
 # this adds/removes IPs from your 
 # public interface
 
+[ -n "$CTDB_BASE" ] || \
+    export CTDB_BASE=$(cd -P $(dirname "$0") ; dirname "$PWD")
+
 . $CTDB_BASE/functions
 loadconfig
 
@@ -13,113 +16,181 @@ loadconfig
 }
 
 [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
+       if [ "$1" = "init" ]; then
+               echo "No public addresses file found. Nothing to do for 10.interfaces"
+       fi
        exit 0
 }
 
 mark_up ()
 {
-    ok=1
+    up_interfaces_found=true
     ctdb setifacelink $1 up >/dev/null 2>&1
 }
 
 mark_down ()
 {
-    fail=1
+    fail=true
     ctdb setifacelink $1 down >/dev/null 2>&1
 }
 
-monitor_interfaces()
+# This sets $all_interfaces as a side-effect.
+get_all_interfaces ()
 {
-       INTERFACES=`cat $CTDB_PUBLIC_ADDRESSES |
-               sed -e "s/^[^\t ]*[\t ]*//" -e "s/,/ /g" -e "s/[\t ]*$//"`
+    # Get all the interfaces listed in the public_addresses file
+    all_interfaces=$(sed -e "s/^[^\t ]*[\t ]*//" -e "s/,/ /g" -e "s/[\t ]*$//" $CTDB_PUBLIC_ADDRESSES)
 
-       [ "$CTDB_PUBLIC_INTERFACE" ] && INTERFACES="$CTDB_PUBLIC_INTERFACE $INTERFACES"
-       [ "$CTDB_NATGW_PUBLIC_IFACE" ] && INTERFACES="$CTDB_NATGW_PUBLIC_IFACE $INTERFACES"
+    # Add some special interfaces if they're defined
+    [ "$CTDB_PUBLIC_INTERFACE" ] && all_interfaces="$CTDB_PUBLIC_INTERFACE $all_interfaces"
+    [ "$CTDB_NATGW_PUBLIC_IFACE" ] && all_interfaces="$CTDB_NATGW_PUBLIC_IFACE $all_interfaces"
+
+    # Get the interfaces for which CTDB has public IPs configured.
+    # That is, for all but the 1st line, get the 1st field.
+    ctdb_ifaces=$(ctdb -X ifaces | sed -e '1d' -e 's@^|@@' -e 's@|.*@@')
+
+    # Add $ctdb_interfaces and uniquify
+    all_interfaces=$(echo $all_interfaces $ctdb_ifaces | tr ' ' '\n' | sort -u)
+}
 
+get_real_iface ()
+{
+    # Output of "ip link show <iface>"
+    _iface_info="$1"
+
+    # Extract the full interface description to see if it is a VLAN
+    _t=$(echo "$_iface_info" |
+               awk 'NR == 1 { iface = $2; sub(":$", "", iface) ; \
+                              print iface }')
+    case "$_t" in
+       *@*)
+           # VLAN: use the underlying interface, after the '@'
+           echo "${_t##*@}"
+           ;;
+       *)
+           # Not a regular VLAN.  For backward compatibility, assume
+           # there is some other sort of VLAN that doesn't have the
+           # '@' in the output and only use what is before a '.'.  If
+           # there is no '.' then this will be the whole interface
+           # name.
+           echo "${_t%%.*}"
+    esac
+}
 
-       # For all but the 1st line, get the 2nd last field with commas
-       # changes to spaces.
-       IFACES=`ctdb -Y ip -v | sed -e '1d' -e 's/:[^:]*:$//' -e 's/^.*://' -e 's/,/ /g'`
+monitor_interfaces()
+{
+       get_all_interfaces
 
-       INTERFACES=`for IFACE in $INTERFACES $IFACES ; do echo $IFACE ; done | sort | uniq`
+       fail=false
+       up_interfaces_found=false
 
-       fail=0
-       ok=0
-       for IFACE in $INTERFACES ; do
+       # Note that this loop must not exit early.  It must process
+       # all interfaces so that the correct state for each interface
+       # is set in CTDB using mark_up/mark_down.  If there is a
+       # problem with an interface then set fail=true and continue.
+       for iface in $all_interfaces ; do
 
-           ip addr show $IFACE 2>/dev/null >/dev/null || {
-               echo Interface $IFACE does not exist but it is used by public addresses.
+           _iface_info=$(ip link show $iface 2>&1) || {
+               echo "ERROR: Interface $iface does not exist but it is used by public addresses."
+               mark_down $iface
                continue
            }
 
            # These interfaces are sometimes bond devices
            # When we use VLANs for bond interfaces, there will only
            # be an entry in /proc for the underlying real interface
-           REALIFACE=`echo $IFACE |sed -e 's/\..*$//'`
-           bi=$(get_proc "net/bonding/$REALIFACE" 2>/dev/null) && {
+           realiface=$(get_real_iface "$_iface_info")
+           bi=$(get_proc "net/bonding/$realiface" 2>/dev/null) && {
                echo "$bi" | grep -q 'Currently Active Slave: None' && {
-                       echo "ERROR: No active slaves for bond device $REALIFACE"
-                       mark_down $IFACE
-                       continue;
+                       echo "ERROR: No active slaves for bond device $realiface"
+                       mark_down $iface
+                       continue
                }
                echo "$bi" | grep -q '^MII Status: up' || {
-                       echo "ERROR: public network interface $REALIFACE is down"
-                       mark_down $IFACE
-                       continue;
+                       echo "ERROR: public network interface $realiface is down"
+                       mark_down $iface
+                       continue
                }
                echo "$bi" | grep -q '^Bonding Mode: IEEE 802.3ad Dynamic link aggregation' && {
+                       # This works around a bug in the driver where the
+                       # overall bond status can be up but none of the actual
+                       # physical interfaces have a link.
                        echo "$bi" | grep 'MII Status:' | tail -n +2 | grep -q '^MII Status: up' || {
-                               echo No active slaves for 802.ad bond device $REALIFACE
-                               mark_down $IFACE
+                               echo "ERROR: No active slaves for 802.ad bond device $realiface"
+                               mark_down $iface
                                continue
                        }
                }
-               mark_up $IFACE
-               continue;
+               mark_up $iface
+               continue
            }
 
-           case $IFACE in
+           case $iface in
            lo*)
                # loopback is always working
-               mark_up $IFACE
+               mark_up $iface
                ;;
            ib*)
-               # we dont know how to test ib links
-                mark_up $IFACE
+               # we don't know how to test ib links
+               mark_up $iface
                ;;
            *)
-               [ -z "$IFACE" ] || {
-                   [ "$(basename $(readlink /sys/class/net/$IFACE/device/driver) 2>/dev/null)" = virtio_net ] ||
-                   ethtool $IFACE | grep -q 'Link detected: yes' || {
-                       # On some systems, this is not successful when a
-                       # cable is plugged but the interface has not been
-                       # brought up previously. Bring the interface up and
-                       # try again...
-                       ip link set $IFACE up
-                       ethtool $IFACE | grep -q 'Link detected: yes' || {
-                           echo "ERROR: No link on the public network interface $IFACE"
-                           mark_down $IFACE
-                           continue
-                       }
+               ethtool $iface | grep -q 'Link detected: yes' || {
+                   # On some systems, this is not successful when a
+                   # cable is plugged but the interface has not been
+                   # brought up previously. Bring the interface up
+                   # and try again...
+                   ip link set $iface up
+                   ethtool $iface | grep -q 'Link detected: yes' || {
+                       echo "ERROR: No link on the public network interface $iface"
+                       mark_down $iface
+                       continue
                    }
-                   mark_up $IFACE
                }
+               mark_up $iface
                ;;
            esac
 
        done
 
-       test x"$fail" = x"0" && {
-               return 0;
-       }
+       $fail || return 0
 
-       test x"$ok" = x"1" && {
-               return 2;
-       }
+       $up_interfaces_found && \
+           [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" = "yes" ] && \
+           return 0
+
+       return 1
+}
 
-       return 1;
+# Sets: iface, ip, maskbits, family
+get_iface_ip_maskbits_family ()
+{
+    _iface_in="$1"
+    ip="$2"
+    _maskbits_in="$3"
+
+    set -- $(ip_maskbits_iface "$ip")
+    if [ -n "$1" ] ; then
+       maskbits="$1"
+       iface="$2"
+       family="$3"
+
+       if [ "$iface" != "$_iface_in" ] ; then
+           printf \
+               'WARNING: Public IP %s hosted on interface %s but VNN says %s\n' \
+               "$ip" "$iface" "$_iface_in"
+       fi
+       if [ "$maskbits" != "$_maskbits_in" ] ; then
+           printf \
+               'WARNING: Public IP %s has %s bit netmask but VNN says %s\n' \
+                   "$ip" "$maskbits" "$_maskbits_in"
+       fi
+    else
+       die "ERROR: Unable to determine interface for IP ${ip}"
+    fi
 }
 
+ctdb_check_args "$@"
+
 case "$1" in 
      #############################
      # called when ctdbd starts up
@@ -129,31 +200,27 @@ case "$1" in
        get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
            set_proc sys/net/ipv4/conf/all/arp_filter 1
        }
+
+       _promote="sys/net/ipv4/conf/all/promote_secondaries"
+       get_proc "$_promote" >/dev/null 2>&1 || \
+           die "Public IPs only supported if promote_secondaries is available"
+
+       # make sure we drop any ips that might still be held if
+       # previous instance of ctdb got killed with -9 or similar
+       drop_all_public_ips
        ;;
 
      #############################
      # called after ctdbd has done its initial recovery
      # and we start the services to become healthy
      startup)
-       # Assume all links are good initially
-       INTERFACES=`for IFACE in $INTERFACES ; do echo $IFACE ; done | sort | uniq`
-
-       for IFACE in $INTERFACES ; do
-               ctdb setifacelink $IFACE down >/dev/null 2>/dev/null
-       done
-       
        monitor_interfaces
-
        ;;
 
 
      ################################################
      # called when ctdbd wants to claim an IP address
      takeip)
-       if [ $# != 4 ]; then
-          echo "must supply interface, IP and maskbits"
-          exit 1
-       fi
        iface=$2
        ip=$3
        maskbits=$4
@@ -163,21 +230,19 @@ case "$1" in
        }
 
        # cope with the script being killed while we have the interface blocked
-       iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
+       case "$ip" in
+           *:*) family="inet6" ;;
+           *)   family="inet"  ;;
+       esac
+       iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
 
-       # flush our route cache
-       set_proc sys/net/ipv4/route/flush 1
+       flush_route_cache
        ;;
 
 
      ##################################################
      # called when ctdbd wants to release an IP address
      releaseip)
-       if [ $# != 4 ]; then
-          echo "must supply interface, IP and maskbits"
-          exit 1
-       fi
-
        # releasing an IP is a bit more complex than it seems. Once the IP
        # is released, any open tcp connections to that IP on this host will end
        # up being stuck. Some of them (such as NFS connections) will be unkillable
@@ -188,69 +253,64 @@ case "$1" in
        # 2) use netstat -tn to find existing connections, and kill them 
        # 3) remove the IP from the interface
        # 4) remove the firewall rule
-       iface=$2
-       ip=$3
-       maskbits=$4
+       shift
+       get_iface_ip_maskbits_family "$@"
 
-       failed=0
        # we do an extra delete to cope with the script being killed
-       iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
-       iptables -I INPUT -i $iface -d $ip -j DROP
+       iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
+       iptables_wrapper $family -I INPUT -i $iface -d $ip -j DROP
        kill_tcp_connections $ip
 
        delete_ip_from_iface $iface $ip $maskbits || {
-               iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
-               exit 1;
+           iptables_wrapper $family \
+                            -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
+               exit 1
        }
 
-       iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
+       iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
 
-       # flush our route cache
-       set_proc sys/net/ipv4/route/flush 1
+       flush_route_cache
        ;;
 
      ##################################################
      # called when ctdbd wants to update an IP address
      updateip)
-       if [ $# != 5 ]; then
-          echo "must supply old interface, new interface, IP and maskbits"
-          exit 1
-       fi
-
        # moving an IP is a bit more complex than it seems.
        # First we drop all traffic on the old interface.
        # Then we try to add the ip to the new interface and before
        # we finally remove it from the old interface.
        #
        # 1) firewall this IP, so no new external packets arrive for it
-       # 2) add the IP to the new interface
-       # 3) remove the IP from the old interface
+       # 2) remove the IP from the old interface (and new interface, to be sure)
+       # 3) add the IP to the new interface
        # 4) remove the firewall rule
        # 5) use ctdb gratiousarp to propagate the new mac address
        # 6) use netstat -tn to find existing connections, and tickle them
-       oiface=$2
+       _oiface=$2
        niface=$3
-       ip=$4
-       maskbits=$5
+       _ip=$4
+       _maskbits=$5
+
+       get_iface_ip_maskbits_family "$_oiface" "$_ip" "$_maskbits"
+       oiface="$iface"
 
-       failed=0
        # we do an extra delete to cope with the script being killed
-       iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
-       iptables -I INPUT -i $oiface -d $ip -j DROP
+       iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
+       iptables_wrapper $family -I INPUT -i $oiface -d $ip -j DROP
 
        delete_ip_from_iface $oiface $ip $maskbits 2>/dev/null
        delete_ip_from_iface $niface $ip $maskbits 2>/dev/null
 
        add_ip_to_iface $niface $ip $maskbits || {
-               iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
-               exit 1;
+           iptables_wrapper $family \
+                            -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
+           exit 1
        }
 
        # cope with the script being killed while we have the interface blocked
-       iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
+       iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
 
-       # flush our route cache
-       set_proc sys/net/ipv4/route/flush 1
+       flush_route_cache
 
        # propagate the new mac address
        ctdb gratiousarp $ip $niface
@@ -262,33 +322,8 @@ case "$1" in
 
        ;;
 
-
-     ###########################################
-     # called when ctdbd has finished a recovery
-     recovered)
-       ;;
-
-     ####################################
-     # called when ctdbd is shutting down
-     shutdown)
-       ;;
-
      monitor)
-       monitor_interfaces
-       ret=$?
-
-       test x"$ret" = x"2" && {
-               test x"$CTDB_PARTIALLY_ONLINE_INTERFACES" != x"yes" && {
-                       exit 1;
-               }
-               # as long as we have one interface available don't become
-               # unhealthy
-               ret=0
-       }
-
-       test x"$ret" != x"0" && {
-               exit 1;
-       }
+       monitor_interfaces || exit 1
        ;;
     *)
        ctdb_standard_event_handler "$@"