Eventscripts - redesign and rewrite 13.per_ip_routing
[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 ######################################################################
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 $(basename "$_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 $(basename "$_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 get_config_for_ip ()
170 {
171     _ip="$1"
172
173     if [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ] ; then
174         # When parsing public_addresses also split on '/'.  This means
175         # that we get the maskbits as item #2 without further parsing.
176         while IFS="/$IFS" read _i _maskbits _x ; do
177             if [ "$_ip" = "$_i" ] ; then
178                 echo -n "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
179             fi
180         done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
181     else
182         while read _i _rest ; do
183             if [ "$_ip" = "$_i" ] ; then
184                 printf "%s\t%s\n" "$_ip" "$_rest"
185             fi
186         done <"$CTDB_PER_IP_ROUTING_CONF"
187     fi
188 }
189
190 ip_has_configuration ()
191 {
192     _ip="$1"
193
194     [ -n "$(get_config_for_ip $_ip)" ]
195 }
196
197 add_routing_for_ip ()
198 {
199     _iface="$1"
200     _ip="$2"
201
202     # Do nothing if no config for this IP.
203     ip_has_configuration "$_ip" || return 0
204
205     ensure_table_id_for_ip "$_ip" || \
206         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"
207
208     _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
209     _table_id="${table_id_prefix}${_ip}"
210
211     del_routing_for_ip "$_ip"
212
213     ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
214         die "add_routing_for_ip: failed to add rule for $_ip"
215
216     # Add routes to table for any lines matching the IP.
217     get_config_for_ip "$_ip" |
218     while read _i _dest _gw ; do
219         _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
220         ip route add $_r || \
221             die "add_routing_for_ip: failed to add route: $_r"
222     done
223 }
224
225 del_routing_for_ip ()
226 {
227     _ip="$1"
228
229     _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
230     _table_id="${table_id_prefix}${_ip}"
231
232     # Do this unconditionally since we own any matching table ids...
233     ip rule del from $_ip pref $_pref table $_table_id 2>/dev/null
234     ip route flush table $_table_id 2>/dev/null
235 }
236
237 ######################################################################
238
239 flush_rules_and_routes ()
240 {
241         ip rule show |
242         while read _p _x _i _x _t ; do
243             # Remove trailing colon after priority/preference.
244             _p="${_p%:}"
245             # Only remove rules that match our priority/preference.
246             [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
247
248             echo "Removing ip rule for public address $_i for routing table $_t"
249             ip rule del from "$_i" table "$_t" pref "$_p"
250             ip route flush table "$_t" 2>/dev/null
251         done
252 }
253
254 # Add any missing routes.  Some might have gone missing if, for
255 # example, all IPs on the network were removed (possibly if the
256 # primary was removed).
257 add_missing_routes ()
258 {
259     ctdb ip -v -Y | {
260         read _x # skip header line
261
262         # Read the rest of the lines.  We're only interested in the
263         # "IP" and "ActiveInterface" columns.  The latter is only set
264         # for addresses local to this node, making it easy to skip
265         # non-local addresses.  For each IP local address we check if
266         # the relevant routing table is populated and populate it if
267         # not.
268         while IFS=":" read _x _ip _x _iface _x ; do
269             [ -n "$_iface" ] || continue
270             
271             _table_id="${table_id_prefix}${_ip}"
272             if [ -z "$(ip route show table $_table_id 2>/dev/null)" ]  ; then
273                 add_routing_for_ip "$_iface" "$_ip"
274             fi
275         done
276     }
277 }
278
279 ######################################################################
280
281 ctdb_check_args "$@"
282
283 case "$1" in
284     startup)
285         flush_rules_and_routes
286
287         # make sure that we only respond to ARP messages from the NIC
288         # where a particular ip address is associated.
289         [ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
290             echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
291         }
292         ;;
293
294     shutdown)
295         flush_rules_and_routes
296         clean_up_table_ids
297         ;;
298
299     takeip)
300         iface=$2
301         ip=$3
302         maskbits=$4
303
304         ensure_ipv4_is_valid_addr "$1" "$ip"
305         add_routing_for_ip "$iface" "$ip"
306
307         # flush our route cache
308         echo 1 > /proc/sys/net/ipv4/route/flush
309
310         ctdb gratiousarp "$ip" "$iface"
311         ;;
312
313     updateip)
314         oiface=$2
315         niface=$3
316         ip=$4
317         maskbits=$5
318
319         ensure_ipv4_is_valid_addr "$1" "$ip"
320         add_routing_for_ip "$niface" "$ip"
321
322         # flush our route cache
323         echo 1 > /proc/sys/net/ipv4/route/flush
324
325         ctdb gratiousarp "$ip" "$niface"
326         tickle_tcp_connections "$ip"
327         ;;
328
329     releaseip)
330         iface=$2
331         ip=$3
332         maskbits=$4
333
334         ensure_ipv4_is_valid_addr "$1" "$ip"
335         del_routing_for_ip "$ip"
336         ;;
337
338     ipreallocated)
339         add_missing_routes
340         ;;
341
342     *)
343         ctdb_standard_event_handler "$@"
344         ;;
345 esac
346
347 exit 0