3 #################################
4 # interface event script for ctdb
5 # this adds/removes IPs from your
8 [ -n "$CTDB_BASE" ] || \
9 export CTDB_BASE=$(cd -P $(dirname "$0") ; dirname "$PWD")
11 . $CTDB_BASE/functions
14 [ -z "$CTDB_PUBLIC_ADDRESSES" ] && {
15 CTDB_PUBLIC_ADDRESSES=$CTDB_BASE/public_addresses
18 [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
19 if [ "$1" = "init" ]; then
20 echo "No public addresses file found. Nothing to do for 10.interfaces"
27 up_interfaces_found=true
28 ctdb setifacelink $1 up >/dev/null 2>&1
34 ctdb setifacelink $1 down >/dev/null 2>&1
37 # This sets $all_interfaces as a side-effect.
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)
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"
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@|.*@@')
51 # Add $ctdb_interfaces and uniquify
52 all_interfaces=$(echo $all_interfaces $ctdb_ifaces | tr ' ' '\n' | sort -u)
57 # Output of "ip link show <iface>"
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) ; \
66 # VLAN: use the underlying interface, after the '@'
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
84 up_interfaces_found=false
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
92 _iface_info=$(ip link show $iface 2>&1) || {
93 echo "ERROR: Interface $iface does not exist but it is used by public addresses."
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"
108 echo "$bi" | grep -q '^MII Status: up' || {
109 echo "ERROR: public network interface $realiface is down"
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"
129 # loopback is always working
133 # we don't know how to test ib links
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
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"
156 if $up_interfaces_found && \
157 [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" = "yes" ] ; then
167 # Sets: iface, ip, maskbits, family
168 get_iface_ip_maskbits_family ()
174 set -- $(ip_maskbits_iface "$ip")
175 if [ -n "$1" ] ; then
180 if [ "$iface" != "$_iface_in" ] ; then
182 'WARNING: Public IP %s hosted on interface %s but VNN says %s\n' \
183 "$ip" "$iface" "$_iface_in"
185 if [ "$maskbits" != "$_maskbits_in" ] ; then
187 'WARNING: Public IP %s has %s bit netmask but VNN says %s\n' \
188 "$ip" "$maskbits" "$_maskbits_in"
191 die "ERROR: Unable to determine interface for IP ${ip}"
199 # make sure that we only respond to ARP messages from the NIC where
200 # a particular ip address is associated.
201 get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
202 set_proc sys/net/ipv4/conf/all/arp_filter 1
205 _promote="sys/net/ipv4/conf/all/promote_secondaries"
206 get_proc "$_promote" >/dev/null 2>&1 || \
207 die "Public IPs only supported if promote_secondaries is available"
209 # make sure we drop any ips that might still be held if
210 # previous instance of ctdb got killed with -9 or similar
223 add_ip_to_iface $iface $ip $maskbits || {
227 # cope with the script being killed while we have the interface blocked
229 *:*) family="inet6" ;;
232 iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
238 # releasing an IP is a bit more complex than it seems. Once the IP
239 # is released, any open tcp connections to that IP on this host will end
240 # up being stuck. Some of them (such as NFS connections) will be unkillable
241 # so we need to use the killtcp ctdb function to kill them off. We also
242 # need to make sure that no new connections get established while we are
243 # doing this! So what we do is this:
244 # 1) firewall this IP, so no new external packets arrive for it
245 # 2) use netstat -tn to find existing connections, and kill them
246 # 3) remove the IP from the interface
247 # 4) remove the firewall rule
249 get_iface_ip_maskbits_family "$@"
251 # we do an extra delete to cope with the script being killed
252 iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
253 iptables_wrapper $family -I INPUT -i $iface -d $ip -j DROP
254 kill_tcp_connections $ip
256 delete_ip_from_iface $iface $ip $maskbits || {
257 iptables_wrapper $family \
258 -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
262 iptables_wrapper $family -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
268 # moving an IP is a bit more complex than it seems.
269 # First we drop all traffic on the old interface.
270 # Then we try to add the ip to the new interface and before
271 # we finally remove it from the old interface.
273 # 1) firewall this IP, so no new external packets arrive for it
274 # 2) remove the IP from the old interface (and new interface, to be sure)
275 # 3) add the IP to the new interface
276 # 4) remove the firewall rule
277 # 5) use ctdb gratiousarp to propagate the new mac address
278 # 6) use netstat -tn to find existing connections, and tickle them
284 get_iface_ip_maskbits_family "$_oiface" "$_ip" "$_maskbits"
287 # we do an extra delete to cope with the script being killed
288 iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
289 iptables_wrapper $family -I INPUT -i $oiface -d $ip -j DROP
291 delete_ip_from_iface $oiface $ip $maskbits 2>/dev/null
292 delete_ip_from_iface $niface $ip $maskbits 2>/dev/null
294 add_ip_to_iface $niface $ip $maskbits || {
295 iptables_wrapper $family \
296 -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
300 # cope with the script being killed while we have the interface blocked
301 iptables_wrapper $family -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
305 # propagate the new mac address
306 ctdb gratiousarp $ip $niface
308 # tickle all existing connections, so that dropped packets
309 # are retransmited and the tcp streams work
310 tickle_tcp_connections $ip
314 monitor_interfaces || exit 1
317 ctdb_standard_event_handler "$@"