3 #################################
4 # interface event script for ctdb
5 # this adds/removes IPs from your
8 [ -n "$CTDB_BASE" ] || \
9 CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
11 . "${CTDB_BASE}/functions"
15 [ -z "$CTDB_PUBLIC_ADDRESSES" ] && {
16 CTDB_PUBLIC_ADDRESSES="${CTDB_BASE}/public_addresses"
19 [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
20 if [ "$1" = "init" ]; then
21 echo "No public addresses file found. Nothing to do for 10.interfaces"
26 # This sets $all_interfaces as a side-effect.
29 # Get all the interfaces listed in the public_addresses file
30 all_interfaces=$(sed -e "s/^[^\t ]*[\t ]*//" \
32 -e "s/[\t ]*$//" "$CTDB_PUBLIC_ADDRESSES")
34 # Add some special interfaces if they're defined
35 [ "$CTDB_PUBLIC_INTERFACE" ] && all_interfaces="$CTDB_PUBLIC_INTERFACE $all_interfaces"
37 # Get the interfaces for which CTDB has public IPs configured.
38 # That is, for all but the 1st line, get the 1st field.
39 ctdb_ifaces=$($CTDB -X ifaces | sed -e '1d' -e 's@^|@@' -e 's@|.*@@')
41 # Add $ctdb_interfaces and uniquify
42 # Use word splitting to squash whitespace
43 # shellcheck disable=SC2086
44 all_interfaces=$(echo $all_interfaces $ctdb_ifaces | tr ' ' '\n' | sort -u)
51 down_interfaces_found=false
52 up_interfaces_found=false
54 # Note that this loop must not exit early. It must process
55 # all interfaces so that the correct state for each interface
56 # is set in CTDB using setifacelink.
57 for _iface in $all_interfaces ; do
58 if interface_monitor "$_iface" ; then
59 up_interfaces_found=true
60 $CTDB setifacelink "$_iface" up >/dev/null 2>&1
62 down_interfaces_found=true
63 $CTDB setifacelink "$_iface" down >/dev/null 2>&1
67 if ! $down_interfaces_found ; then
71 if ! $up_interfaces_found ; then
75 if [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" != "yes" ]; then
82 # Sets: iface, ip, maskbits
83 get_iface_ip_maskbits ()
89 # Intentional word splitting here
90 # shellcheck disable=SC2046
91 set -- $(ip_maskbits_iface "$ip")
96 if [ "$iface" != "$_iface_in" ] ; then
98 'WARNING: Public IP %s hosted on interface %s but VNN says %s\n' \
99 "$ip" "$iface" "$_iface_in"
101 if [ "$maskbits" != "$_maskbits_in" ] ; then
103 'WARNING: Public IP %s has %s bit netmask but VNN says %s\n' \
104 "$ip" "$maskbits" "$_maskbits_in"
107 die "ERROR: Unable to determine interface for IP ${ip}"
117 *:*) _family="inet6" ;;
121 # Extra delete copes with previously killed script
122 iptables_wrapper "$_family" \
123 -D INPUT -i "$_iface" -d "$_ip" -j DROP 2>/dev/null
124 iptables_wrapper "$_family" \
125 -I INPUT -i "$_iface" -d "$_ip" -j DROP
134 *:*) _family="inet6" ;;
138 iptables_wrapper "$_family" \
139 -D INPUT -i "$_iface" -d "$_ip" -j DROP 2>/dev/null
146 # make sure that we only respond to ARP messages from the NIC where
147 # a particular ip address is associated.
148 get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
149 set_proc sys/net/ipv4/conf/all/arp_filter 1
152 _promote="sys/net/ipv4/conf/all/promote_secondaries"
153 get_proc "$_promote" >/dev/null 2>&1 || \
154 die "Public IPs only supported if promote_secondaries is available"
156 # make sure we drop any ips that might still be held if
157 # previous instance of ctdb got killed with -9 or similar
170 add_ip_to_iface "$iface" "$ip" "$maskbits" || {
174 # In case a previous "releaseip" for this IP was killed...
175 ip_unblock "$ip" "$iface"
181 # releasing an IP is a bit more complex than it seems. Once the IP
182 # is released, any open tcp connections to that IP on this host will end
183 # up being stuck. Some of them (such as NFS connections) will be unkillable
184 # so we need to use the killtcp ctdb function to kill them off. We also
185 # need to make sure that no new connections get established while we are
186 # doing this! So what we do is this:
187 # 1) firewall this IP, so no new external packets arrive for it
188 # 2) find existing connections, and kill them
189 # 3) remove the IP from the interface
190 # 4) remove the firewall rule
192 get_iface_ip_maskbits "$@"
194 ip_block "$ip" "$iface"
196 kill_tcp_connections "$iface" "$ip"
198 delete_ip_from_iface "$iface" "$ip" "$maskbits" || {
199 ip_unblock "$ip" "$iface"
203 ip_unblock "$ip" "$iface"
209 # moving an IP is a bit more complex than it seems.
210 # First we drop all traffic on the old interface.
211 # Then we try to add the ip to the new interface and before
212 # we finally remove it from the old interface.
214 # 1) firewall this IP, so no new external packets arrive for it
215 # 2) remove the IP from the old interface (and new interface, to be sure)
216 # 3) add the IP to the new interface
217 # 4) remove the firewall rule
218 # 5) use ctdb gratarp to propagate the new mac address
219 # 6) use netstat -tn to find existing connections, and tickle them
225 get_iface_ip_maskbits "$_oiface" "$_ip" "$_maskbits"
228 # Could check maskbits too. However, that should never change
229 # so we want to notice if it does.
230 if [ "$oiface" = "$niface" ] ; then
231 echo "Redundant \"updateip\" - ${ip} already on ${niface}"
235 ip_block "$ip" "$oiface"
237 delete_ip_from_iface "$oiface" "$ip" "$maskbits" 2>/dev/null
238 delete_ip_from_iface "$niface" "$ip" "$maskbits" 2>/dev/null
240 add_ip_to_iface "$niface" "$ip" "$maskbits" || {
241 ip_unblock "$ip" "$oiface"
245 ip_unblock "$ip" "$oiface"
249 # propagate the new mac address
250 $CTDB gratarp "$ip" "$niface"
252 # tickle all existing connections, so that dropped packets
253 # are retransmited and the tcp streams work
254 tickle_tcp_connections "$ip"
258 monitor_interfaces || exit 1