ctdb-scripts: Fix regression in updateip code
[obnox/samba/samba-obnox.git] / ctdb / config / events.d / 10.interface
index dd54f1e395250d006513cceb97e4a2da3718d558..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
 
@@ -41,14 +44,38 @@ get_all_interfaces ()
     [ "$CTDB_PUBLIC_INTERFACE" ] && all_interfaces="$CTDB_PUBLIC_INTERFACE $all_interfaces"
     [ "$CTDB_NATGW_PUBLIC_IFACE" ] && all_interfaces="$CTDB_NATGW_PUBLIC_IFACE $all_interfaces"
 
-    # For all but the 1st line, get the 2nd last field with commas
-    # changes to spaces.
-    ctdb_ifaces=$(ctdb -Y ip -v | sed -e '1d' -e 's/:[^:]*:$//' -e 's/^.*://' -e 's/,/ /g')
+    # 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
+}
+
 monitor_interfaces()
 {
        get_all_interfaces
@@ -56,17 +83,22 @@ monitor_interfaces()
        fail=false
        up_interfaces_found=false
 
+       # 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 "WARNING: 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/\..*$//'`
+           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"
@@ -98,26 +130,23 @@ monitor_interfaces()
                mark_up $iface
                ;;
            ib*)
-               # we dont know how to test ib links
+               # 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' || {
-                       # 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
-                       }
+                       echo "ERROR: No link on the public network interface $iface"
+                       mark_down $iface
+                       continue
                    }
-                   mark_up $iface
                }
+               mark_up $iface
                ;;
            esac
 
@@ -132,6 +161,34 @@ monitor_interfaces()
        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 
@@ -143,20 +200,21 @@ 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
-       get_all_interfaces
-       for iface in $all_interfaces ; do
-               ctdb setifacelink $iface up >/dev/null 2>/dev/null
-       done
-       
        monitor_interfaces
-
        ;;
 
 
@@ -172,10 +230,13 @@ 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
        ;;
 
 
@@ -192,25 +253,23 @@ 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
        ;;
 
      ##################################################
@@ -222,34 +281,36 @@ case "$1" in
        # 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
@@ -261,17 +322,6 @@ case "$1" in
 
        ;;
 
-
-     ###########################################
-     # called when ctdbd has finished a recovery
-     recovered)
-       ;;
-
-     ####################################
-     # called when ctdbd is shutting down
-     shutdown)
-       ;;
-
      monitor)
        monitor_interfaces || exit 1
        ;;