ctdb-scripts: Clarify logic for success of interface monitoring
[obnox/samba/samba-obnox.git] / ctdb / config / events.d / 10.interface
1 #!/bin/sh
2
3 #################################
4 # interface event script for ctdb
5 # this adds/removes IPs from your 
6 # public interface
7
8 [ -n "$CTDB_BASE" ] || \
9     export CTDB_BASE=$(cd -P $(dirname "$0") ; dirname "$PWD")
10
11 . $CTDB_BASE/functions
12 loadconfig
13
14 [ -z "$CTDB_PUBLIC_ADDRESSES" ] && {
15         CTDB_PUBLIC_ADDRESSES=$CTDB_BASE/public_addresses
16 }
17
18 [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
19         if [ "$1" = "init" ]; then
20                 echo "No public addresses file found. Nothing to do for 10.interfaces"
21         fi
22         exit 0
23 }
24
25 mark_up ()
26 {
27     up_interfaces_found=true
28     ctdb setifacelink $1 up >/dev/null 2>&1
29 }
30
31 mark_down ()
32 {
33     fail=true
34     ctdb setifacelink $1 down >/dev/null 2>&1
35 }
36
37 # This sets $all_interfaces as a side-effect.
38 get_all_interfaces ()
39 {
40     # Get all the interfaces listed in the public_addresses file
41     all_interfaces=$(sed -e "s/^[^\t ]*[\t ]*//" -e "s/,/ /g" -e "s/[\t ]*$//" $CTDB_PUBLIC_ADDRESSES)
42
43     # Add some special interfaces if they're defined
44     [ "$CTDB_PUBLIC_INTERFACE" ] && all_interfaces="$CTDB_PUBLIC_INTERFACE $all_interfaces"
45     [ "$CTDB_NATGW_PUBLIC_IFACE" ] && all_interfaces="$CTDB_NATGW_PUBLIC_IFACE $all_interfaces"
46
47     # Get the interfaces for which CTDB has public IPs configured.
48     # That is, for all but the 1st line, get the 1st field.
49     ctdb_ifaces=$(ctdb -X ifaces | sed -e '1d' -e 's@^|@@' -e 's@|.*@@')
50
51     # Add $ctdb_interfaces and uniquify
52     all_interfaces=$(echo $all_interfaces $ctdb_ifaces | tr ' ' '\n' | sort -u)
53 }
54
55 get_real_iface ()
56 {
57     # Output of "ip link show <iface>"
58     _iface_info="$1"
59
60     # Extract the full interface description to see if it is a VLAN
61     _t=$(echo "$_iface_info" |
62                 awk 'NR == 1 { iface = $2; sub(":$", "", iface) ; \
63                                print iface }')
64     case "$_t" in
65         *@*)
66             # VLAN: use the underlying interface, after the '@'
67             echo "${_t##*@}"
68             ;;
69         *)
70             # Not a regular VLAN.  For backward compatibility, assume
71             # there is some other sort of VLAN that doesn't have the
72             # '@' in the output and only use what is before a '.'.  If
73             # there is no '.' then this will be the whole interface
74             # name.
75             echo "${_t%%.*}"
76     esac
77 }
78
79 monitor_interfaces()
80 {
81         get_all_interfaces
82
83         fail=false
84         up_interfaces_found=false
85
86         # Note that this loop must not exit early.  It must process
87         # all interfaces so that the correct state for each interface
88         # is set in CTDB using mark_up/mark_down.  If there is a
89         # problem with an interface then set fail=true and continue.
90         for iface in $all_interfaces ; do
91
92             _iface_info=$(ip link show $iface 2>&1) || {
93                 echo "ERROR: Interface $iface does not exist but it is used by public addresses."
94                 mark_down $iface
95                 continue
96             }
97
98             # These interfaces are sometimes bond devices
99             # When we use VLANs for bond interfaces, there will only
100             # be an entry in /proc for the underlying real interface
101             realiface=$(get_real_iface "$_iface_info")
102             bi=$(get_proc "net/bonding/$realiface" 2>/dev/null) && {
103                 echo "$bi" | grep -q 'Currently Active Slave: None' && {
104                         echo "ERROR: No active slaves for bond device $realiface"
105                         mark_down $iface
106                         continue
107                 }
108                 echo "$bi" | grep -q '^MII Status: up' || {
109                         echo "ERROR: public network interface $realiface is down"
110                         mark_down $iface
111                         continue
112                 }
113                 echo "$bi" | grep -q '^Bonding Mode: IEEE 802.3ad Dynamic link aggregation' && {
114                         # This works around a bug in the driver where the
115                         # overall bond status can be up but none of the actual
116                         # physical interfaces have a link.
117                         echo "$bi" | grep 'MII Status:' | tail -n +2 | grep -q '^MII Status: up' || {
118                                 echo "ERROR: No active slaves for 802.ad bond device $realiface"
119                                 mark_down $iface
120                                 continue
121                         }
122                 }
123                 mark_up $iface
124                 continue
125             }
126
127             case $iface in
128             lo*)
129                 # loopback is always working
130                 mark_up $iface
131                 ;;
132             ib*)
133                 # we don't know how to test ib links
134                 mark_up $iface
135                 ;;
136             *)
137                 ethtool $iface | grep -q 'Link detected: yes' || {
138                     # On some systems, this is not successful when a
139                     # cable is plugged but the interface has not been
140                     # brought up previously. Bring the interface up
141                     # and try again...
142                     ip link set $iface up
143                     ethtool $iface | grep -q 'Link detected: yes' || {
144                         echo "ERROR: No link on the public network interface $iface"
145                         mark_down $iface
146                         continue
147                     }
148                 }
149                 mark_up $iface
150                 ;;
151             esac
152
153         done
154
155         if $fail ; then
156             if $up_interfaces_found && \
157                     [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" = "yes" ] ; then
158                 return 0
159             else
160                 return 1
161             fi
162         else
163             return 0
164         fi
165 }
166
167 # Sets: iface, ip, maskbits, family
168 get_iface_ip_maskbits_family ()
169 {
170     _iface_in="$1"
171     ip="$2"
172     _maskbits_in="$3"
173
174     set -- $(ip_maskbits_iface "$ip")
175     if [ -n "$1" ] ; then
176         maskbits="$1"
177         iface="$2"
178         family="$3"
179
180         if [ "$iface" != "$_iface_in" ] ; then
181             printf \
182                 'WARNING: Public IP %s hosted on interface %s but VNN says %s\n' \
183                 "$ip" "$iface" "$_iface_in"
184         fi
185         if [ "$maskbits" != "$_maskbits_in" ] ; then
186             printf \
187                 'WARNING: Public IP %s has %s bit netmask but VNN says %s\n' \
188                     "$ip" "$maskbits" "$_maskbits_in"
189         fi
190     else
191         die "ERROR: Unable to determine interface for IP ${ip}"
192     fi
193 }
194
195 ctdb_check_args "$@"
196
197 case "$1" in 
198      #############################
199      # called when ctdbd starts up
200      init)
201         # make sure that we only respond to ARP messages from the NIC where
202         # a particular ip address is associated.
203         get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
204             set_proc sys/net/ipv4/conf/all/arp_filter 1
205         }
206
207         _promote="sys/net/ipv4/conf/all/promote_secondaries"
208         get_proc "$_promote" >/dev/null 2>&1 || \
209             die "Public IPs only supported if promote_secondaries is available"
210
211         # make sure we drop any ips that might still be held if
212         # previous instance of ctdb got killed with -9 or similar
213         drop_all_public_ips
214         ;;
215
216      #############################
217      # called after ctdbd has done its initial recovery
218      # and we start the services to become healthy
219      startup)
220         monitor_interfaces
221         ;;
222
223
224      ################################################
225      # called when ctdbd wants to claim an IP address
226      takeip)
227         iface=$2
228         ip=$3
229         maskbits=$4
230
231         add_ip_to_iface $iface $ip $maskbits || {
232                 exit 1;
233         }
234
235         # cope with the script being killed while we have the interface blocked
236         case "$ip" in
237             *:*) family="inet6" ;;
238             *)   family="inet"  ;;
239         esac
240         iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
241
242         flush_route_cache
243         ;;
244
245
246      ##################################################
247      # called when ctdbd wants to release an IP address
248      releaseip)
249         # releasing an IP is a bit more complex than it seems. Once the IP
250         # is released, any open tcp connections to that IP on this host will end
251         # up being stuck. Some of them (such as NFS connections) will be unkillable
252         # so we need to use the killtcp ctdb function to kill them off. We also
253         # need to make sure that no new connections get established while we are 
254         # doing this! So what we do is this:
255         # 1) firewall this IP, so no new external packets arrive for it
256         # 2) use netstat -tn to find existing connections, and kill them 
257         # 3) remove the IP from the interface
258         # 4) remove the firewall rule
259         shift
260         get_iface_ip_maskbits_family "$@"
261
262         # we do an extra delete to cope with the script being killed
263         iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
264         iptables_wrapper $family -I INPUT -i $iface -d $ip -j DROP
265         kill_tcp_connections $ip
266
267         delete_ip_from_iface $iface $ip $maskbits || {
268             iptables_wrapper $family \
269                              -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
270                 exit 1
271         }
272
273         iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
274
275         flush_route_cache
276         ;;
277
278      ##################################################
279      # called when ctdbd wants to update an IP address
280      updateip)
281         # moving an IP is a bit more complex than it seems.
282         # First we drop all traffic on the old interface.
283         # Then we try to add the ip to the new interface and before
284         # we finally remove it from the old interface.
285         #
286         # 1) firewall this IP, so no new external packets arrive for it
287         # 2) remove the IP from the old interface (and new interface, to be sure)
288         # 3) add the IP to the new interface
289         # 4) remove the firewall rule
290         # 5) use ctdb gratiousarp to propagate the new mac address
291         # 6) use netstat -tn to find existing connections, and tickle them
292         _oiface=$2
293         niface=$3
294         _ip=$4
295         _maskbits=$5
296
297         get_iface_ip_maskbits_family "$_oiface" "$_ip" "$_maskbits"
298         oiface="$iface"
299
300         # we do an extra delete to cope with the script being killed
301         iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
302         iptables_wrapper $family -I INPUT -i $oiface -d $ip -j DROP
303
304         delete_ip_from_iface $oiface $ip $maskbits 2>/dev/null
305         delete_ip_from_iface $niface $ip $maskbits 2>/dev/null
306
307         add_ip_to_iface $niface $ip $maskbits || {
308             iptables_wrapper $family \
309                              -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
310             exit 1
311         }
312
313         # cope with the script being killed while we have the interface blocked
314         iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
315
316         flush_route_cache
317
318         # propagate the new mac address
319         ctdb gratiousarp $ip $niface
320
321         # tickle all existing connections, so that dropped packets
322         # are retransmited and the tcp streams work
323
324         tickle_tcp_connections $ip
325
326         ;;
327
328      monitor)
329         monitor_interfaces || exit 1
330         ;;
331     *)
332         ctdb_standard_event_handler "$@"
333         ;;
334 esac
335
336 exit 0
337