Eventscripts: fix basename -> dirname typo
[ctdb.git] / config / events.d / 13.per_ip_routing
1 #!/bin/sh
2
3 . $CTDB_BASE/functions
4 loadconfig
5
6 # Do nothing if unconfigured 
7 [ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
8
9 table_id_prefix="ctdb."
10
11 [ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
12     die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
13
14 [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
15     die "error: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW] and/or CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] improperly configured"
16
17 ######################################################################
18
19 ipv4_is_valid_addr()
20 {
21     _ip="$1"
22
23     _count=0
24     # Get the shell to break up the address into 1 word per octet 
25     for _o in $(export IFS="." ; echo $_ip) ; do
26         # The 2>/dev/null stops output from failures where an "octet"
27         # is not numeric.  The test will still fail.
28         if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
29             return 1
30         fi
31         _count=$(($_count + 1))
32     done
33
34     # A valid IPv4 address has 4 octets
35     [ $_count -eq 4 ]
36 }
37
38 ensure_ipv4_is_valid_addr ()
39 {
40     _event="$1"
41     _ip="$2"
42
43     ipv4_is_valid_addr "$_ip" || {
44         echo "$0: $_event not an ipv4 address skipping IP:$_ip"
45         exit 0
46     }
47 }
48
49 ipv4_host_addr_to_net ()
50 {
51     _host="$1"
52     _maskbits="$2"
53
54     # Convert the host address to an unsigned long by splitting out
55     # the octets and doing the math.
56     _host_ul=0
57     for _o in $(export IFS="." ; echo $_host) ; do
58         _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
59     done
60
61     # Calculate the mask and apply it.
62     _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
63     _net_ul=$(( $_host_ul & $_mask_ul ))
64  
65     # Now convert to a network address one byte at a time.
66     _net=""
67     for _o in $(seq 1 4) ; do
68         _net="$(($_net_ul & 255))${_net:+.}${_net}"
69         _net_ul=$(($_net_ul >> 8))
70     done
71
72     echo "${_net}/${_maskbits}"
73 }
74
75 ######################################################################
76
77 # Setup a table id to use for the given IP.  We don't need to know it,
78 # it just needs to exist in /etc/iproute2/rt_tables.  Fail if no free
79 # table id could be found in the configured range.
80 ensure_table_id_for_ip ()
81 {
82     _ip=$1
83
84     _f="$CTDB_ETCDIR/iproute2/rt_tables"
85     # This file should always exist, but...
86     if [ ! -f "$_f" ] ; then
87         mkdir -p $(dirname "$_f")
88         touch "$_f"
89     fi
90
91     # Maintain a table id for each IP address we've ever seen in
92     # rt_tables.  We use a "ctdb." prefix on the label.
93     _label="${table_id_prefix}${_ip}"
94
95     # This finds either the table id corresponding to the label or a
96     # new unused one (that is greater than all the used ones in the
97     # range).
98     (
99         # Note that die() just gets us out of the subshell...
100         flock --timeout 30 0 || \
101             die "ensure_table_id_for_ip: failed to lock file $_f"
102
103         _new=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
104         while read _t _l ; do
105             # Skip comments
106             case "$_t" in
107                 \#*) continue ;;
108             esac
109             # Found existing: done
110             if [ "$_l" = "$_label" ] ; then
111                 return 0
112             fi
113             # Potentially update the new table id to be used.  The
114             # redirect stops error spam for a non-numeric value.
115             if [ $_new -le $_t -a \
116                 $_t -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] 2>/dev/null ; then
117                 _new=$(($_t + 1))
118             fi
119         done
120
121         # If the new table id is legal then add it to the file and
122         # print it.
123         if [ $_new -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] ; then
124             printf "%d\t%s\n" "$_new" "$_label" >>"$_f"
125             return 0
126         else
127             return 1
128         fi
129     ) <"$_f"
130 }
131
132 # Clean up all the table ids that we might own.
133 clean_up_table_ids ()
134 {
135     _f="$CTDB_ETCDIR/iproute2/rt_tables"
136     # Even if this didn't exist on the system, adding a route will
137     # have created it.  What if we startup and immediately shutdown?
138     if [ ! -f "$_f" ] ; then
139         mkdir -p $(dirname "$_f")
140         touch "$_f"
141     fi
142
143     (
144         # Note that die() just gets us out of the subshell...
145         flock --timeout 30 0 || \
146             die "clean_up_table_ids: failed to lock file $_f"
147
148         # Delete any items from the file that have a table id in our
149         # range or a label matching our label.  Preserve comments.
150         _tmp="${_f}.$$.ctdb"
151         awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
152             -v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
153             -v pre="$table_id_prefix" \
154             '/^#/ || \
155              !(min <= $1 && $1 <= max) && \
156              !(index($2, pre) == 1) \
157              { print $0 }' "$_f" >"$_tmp"
158
159         mv "$_tmp" "$_f"
160         # The lock is gone - don't do anything else here
161     ) <"$_f"
162 }
163
164 ######################################################################
165
166 # This prints the config for an IP, which is either relevant entries
167 # from the config file or, if set to the magic link local value, some
168 # link local routing config for the IP.
169 # NOTE: non-zero return indicates missing configuration file
170 get_config_for_ip ()
171 {
172     _ip="$1"
173
174     if [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ] ; then
175         # When parsing public_addresses also split on '/'.  This means
176         # that we get the maskbits as item #2 without further parsing.
177         while IFS="/$IFS" read _i _maskbits _x ; do
178             if [ "$_ip" = "$_i" ] ; then
179                 echo -n "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
180             fi
181         done <"${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE:-/dev/null}${CTDB_BASE:+/public_addresses}}"
182     else
183         [ -f "$CTDB_PER_IP_ROUTING_CONF" ] || return 1
184
185         while read _i _rest ; do
186             if [ "$_ip" = "$_i" ] ; then
187                 printf "%s\t%s\n" "$_ip" "$_rest"
188             fi
189         done <"$CTDB_PER_IP_ROUTING_CONF"
190     fi
191 }
192
193 ip_has_configuration ()
194 {
195     _ip="$1"
196
197     _config="$(get_config_for_ip $_ip)" || \
198         die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
199     [ -n "$_config" ]
200 }
201
202 add_routing_for_ip ()
203 {
204     _iface="$1"
205     _ip="$2"
206
207     # Do nothing if no config for this IP.
208     ip_has_configuration "$_ip" || return 0
209
210     ensure_table_id_for_ip "$_ip" || \
211         die "add_routing_for_ip: out of table ids in range $CTDB_PER_IP_ROUTING_TABLE_ID_LOW - $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
212
213     _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
214     _table_id="${table_id_prefix}${_ip}"
215
216     del_routing_for_ip "$_ip"
217
218     ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
219         die "add_routing_for_ip: failed to add rule for $_ip"
220
221     # Add routes to table for any lines matching the IP.
222     get_config_for_ip "$_ip" |
223     while read _i _dest _gw ; do
224         _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
225         ip route add $_r || \
226             die "add_routing_for_ip: failed to add route: $_r"
227     done
228 }
229
230 del_routing_for_ip ()
231 {
232     _ip="$1"
233
234     _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
235     _table_id="${table_id_prefix}${_ip}"
236
237     # Do this unconditionally since we own any matching table ids...
238     ip rule del from $_ip pref $_pref table $_table_id 2>/dev/null
239     ip route flush table $_table_id 2>/dev/null
240 }
241
242 ######################################################################
243
244 flush_rules_and_routes ()
245 {
246         ip rule show |
247         while read _p _x _i _x _t ; do
248             # Remove trailing colon after priority/preference.
249             _p="${_p%:}"
250             # Only remove rules that match our priority/preference.
251             [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
252
253             echo "Removing ip rule for public address $_i for routing table $_t"
254             ip rule del from "$_i" table "$_t" pref "$_p"
255             ip route flush table "$_t" 2>/dev/null
256         done
257 }
258
259 # Add any missing routes.  Some might have gone missing if, for
260 # example, all IPs on the network were removed (possibly if the
261 # primary was removed).
262 add_missing_routes ()
263 {
264     ctdb ip -v -Y | {
265         read _x # skip header line
266
267         # Read the rest of the lines.  We're only interested in the
268         # "IP" and "ActiveInterface" columns.  The latter is only set
269         # for addresses local to this node, making it easy to skip
270         # non-local addresses.  For each IP local address we check if
271         # the relevant routing table is populated and populate it if
272         # not.
273         while IFS=":" read _x _ip _x _iface _x ; do
274             [ -n "$_iface" ] || continue
275             
276             _table_id="${table_id_prefix}${_ip}"
277             if [ -z "$(ip route show table $_table_id 2>/dev/null)" ]  ; then
278                 add_routing_for_ip "$_iface" "$_ip"
279             fi
280         done
281     } || exit $?
282 }
283
284 ######################################################################
285
286 ctdb_check_args "$@"
287
288 case "$1" in
289     startup)
290         flush_rules_and_routes
291
292         # make sure that we only respond to ARP messages from the NIC
293         # where a particular ip address is associated.
294         get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
295             set_proc sys/net/ipv4/conf/all/arp_filter 1
296         }
297         ;;
298
299     shutdown)
300         flush_rules_and_routes
301         clean_up_table_ids
302         ;;
303
304     takeip)
305         iface=$2
306         ip=$3
307         maskbits=$4
308
309         ensure_ipv4_is_valid_addr "$1" "$ip"
310         add_routing_for_ip "$iface" "$ip"
311
312         # flush our route cache
313         set_proc sys/net/ipv4/route/flush 1
314
315         ctdb gratiousarp "$ip" "$iface"
316         ;;
317
318     updateip)
319         oiface=$2
320         niface=$3
321         ip=$4
322         maskbits=$5
323
324         ensure_ipv4_is_valid_addr "$1" "$ip"
325         add_routing_for_ip "$niface" "$ip"
326
327         # flush our route cache
328         set_proc sys/net/ipv4/route/flush 1
329
330         ctdb gratiousarp "$ip" "$niface"
331         tickle_tcp_connections "$ip"
332         ;;
333
334     releaseip)
335         iface=$2
336         ip=$3
337         maskbits=$4
338
339         ensure_ipv4_is_valid_addr "$1" "$ip"
340         del_routing_for_ip "$ip"
341         ;;
342
343     ipreallocated)
344         add_missing_routes
345         ;;
346
347     *)
348         ctdb_standard_event_handler "$@"
349         ;;
350 esac
351
352 exit 0