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