3 [ -n "$CTDB_BASE" ] || \
4 CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
6 . "${CTDB_BASE}/functions"
10 service_name=per_ip_routing
12 # Do nothing if unconfigured
13 [ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
15 table_id_prefix="ctdb."
17 [ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
18 die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
20 [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
21 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"
23 if [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -le 253 -a \
24 255 -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
25 die "error: range CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW]..CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] must not include 253-255"
28 have_link_local_config ()
30 [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ]
33 if ! have_link_local_config && [ ! -r "$CTDB_PER_IP_ROUTING_CONF" ] ; then
34 die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
37 ######################################################################
44 # Get the shell to break up the address into 1 word per octet
45 for _o in $(export IFS="." ; echo $_ip) ; do
46 # The 2>/dev/null stops output from failures where an "octet"
47 # is not numeric. The test will still fail.
48 if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
51 _count=$(($_count + 1))
54 # A valid IPv4 address has 4 octets
58 ensure_ipv4_is_valid_addr ()
63 ipv4_is_valid_addr "$_ip" || {
64 echo "$0: $_event not an ipv4 address skipping IP:$_ip"
69 ipv4_host_addr_to_net ()
74 # Convert the host address to an unsigned long by splitting out
75 # the octets and doing the math.
77 for _o in $(export IFS="." ; echo $_host) ; do
78 _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
81 # Calculate the mask and apply it.
82 _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
83 _net_ul=$(( $_host_ul & $_mask_ul ))
85 # Now convert to a network address one byte at a time.
87 for _o in $(seq 1 4) ; do
88 _net="$(($_net_ul & 255))${_net:+.}${_net}"
89 _net_ul=$(($_net_ul >> 8))
92 echo "${_net}/${_maskbits}"
95 ######################################################################
99 rt_tables="$CTDB_SYS_ETCDIR/iproute2/rt_tables"
101 # This file should always exist. Even if this didn't exist on the
102 # system, adding a route will have created it. What if we startup
103 # and immediately shutdown? Let's be sure.
104 if [ ! -f "$rt_tables" ] ; then
105 mkdir -p "${rt_tables%/*}" # dirname
110 # Setup a table id to use for the given IP. We don't need to know it,
111 # it just needs to exist in /etc/iproute2/rt_tables. Fail if no free
112 # table id could be found in the configured range.
113 ensure_table_id_for_ip ()
119 # Maintain a table id for each IP address we've ever seen in
120 # rt_tables. We use a "ctdb." prefix on the label.
121 _label="${table_id_prefix}${_ip}"
123 # This finds either the table id corresponding to the label or a
124 # new unused one (that is greater than all the used ones in the
127 # Note that die() just gets us out of the subshell...
128 flock --timeout 30 0 || \
129 die "ensure_table_id_for_ip: failed to lock file $rt_tables"
131 _new=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
132 while read _t _l ; do
137 # Found existing: done
138 if [ "$_l" = "$_label" ] ; then
141 # Potentially update the new table id to be used. The
142 # redirect stops error spam for a non-numeric value.
143 if [ $_new -le $_t -a \
144 $_t -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] 2>/dev/null ; then
149 # If the new table id is legal then add it to the file and
151 if [ $_new -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] ; then
152 printf "%d\t%s\n" "$_new" "$_label" >>"$rt_tables"
160 # Clean up all the table ids that we might own.
161 clean_up_table_ids ()
166 # Note that die() just gets us out of the subshell...
167 flock --timeout 30 0 || \
168 die "clean_up_table_ids: failed to lock file $rt_tables"
170 # Delete any items from the file that have a table id in our
171 # range or a label matching our label. Preserve comments.
172 _tmp="${rt_tables}.$$.ctdb"
173 awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
174 -v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
175 -v pre="$table_id_prefix" \
177 !(min <= $1 && $1 <= max) && \
178 !(index($2, pre) == 1) \
179 { print $0 }' "$rt_tables" >"$_tmp"
181 mv "$_tmp" "$rt_tables"
182 # The lock is gone - don't do anything else here
186 ######################################################################
188 # This prints the config for an IP, which is either relevant entries
189 # from the config file or, if set to the magic link local value, some
190 # link local routing config for the IP.
195 if have_link_local_config ; then
196 # When parsing public_addresses also split on '/'. This means
197 # that we get the maskbits as item #2 without further parsing.
198 while IFS="/$IFS" read _i _maskbits _x ; do
199 if [ "$_ip" = "$_i" ] ; then
200 echo -n "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
202 done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
204 while read _i _rest ; do
205 if [ "$_ip" = "$_i" ] ; then
206 printf "%s\t%s\n" "$_ip" "$_rest"
208 done <"$CTDB_PER_IP_ROUTING_CONF"
212 ip_has_configuration ()
216 [ -n "$(get_config_for_ip $_ip)" ]
219 add_routing_for_ip ()
224 # Do nothing if no config for this IP.
225 ip_has_configuration "$_ip" || return 0
227 ensure_table_id_for_ip "$_ip" || \
228 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"
230 _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
231 _table_id="${table_id_prefix}${_ip}"
233 del_routing_for_ip "$_ip" >/dev/null 2>&1
235 ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
236 die "add_routing_for_ip: failed to add rule for $_ip"
238 # Add routes to table for any lines matching the IP.
239 get_config_for_ip "$_ip" |
240 while read _i _dest _gw ; do
241 _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
242 ip route add $_r || \
243 die "add_routing_for_ip: failed to add route: $_r"
247 del_routing_for_ip ()
251 _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
252 _table_id="${table_id_prefix}${_ip}"
254 # Do this unconditionally since we own any matching table ids.
255 # However, print a meaningful message if something goes wrong.
256 _cmd="ip rule del from $_ip pref $_pref table $_table_id"
257 _out=$($_cmd 2>&1) || \
259 WARNING: Failed to delete policy routing rule
260 Command "$_cmd" failed:
263 # This should never usually fail, so don't redirect output.
264 # However, it can fail when deleting a rogue IP, since there will
265 # be no routes for that IP. In this case it should only fail when
266 # the rule deletion above has already failed because the table id
267 # is invalid. Therefore, go to a little bit of trouble to indent
268 # the failure message so that it is associated with the above
269 # warning message and doesn't look too nasty.
270 ip route flush table "$_table_id" 2>&1 | sed -e 's@^.@ &@'
273 ######################################################################
275 flush_rules_and_routes ()
278 while read _p _x _i _x _t ; do
279 # Remove trailing colon after priority/preference.
281 # Only remove rules that match our priority/preference.
282 [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
284 echo "Removing ip rule for public address $_i for routing table $_t"
285 ip rule del from "$_i" table "$_t" pref "$_p"
286 ip route flush table "$_t" 2>/dev/null
290 # Add any missing routes. Some might have gone missing if, for
291 # example, all IPs on the network were removed (possibly if the
292 # primary was removed). If $1 is "force" then (re-)add all the
294 add_missing_routes ()
297 read _x # skip header line
299 # Read the rest of the lines. We're only interested in the
300 # "IP" and "ActiveInterface" columns. The latter is only set
301 # for addresses local to this node, making it easy to skip
302 # non-local addresses. For each IP local address we check if
303 # the relevant routing table is populated and populate it if
305 while IFS="|" read _x _ip _x _iface _x ; do
306 [ -n "$_iface" ] || continue
308 _table_id="${table_id_prefix}${_ip}"
309 if [ -z "$(ip route show table $_table_id 2>/dev/null)" -o \
310 "$1" = "force" ] ; then
311 add_routing_for_ip "$_iface" "$_ip"
317 # Remove rules/routes for addresses that we're not hosting. If a
318 # releaseip event failed in an earlier script then we might not have
319 # had a chance to remove the corresponding rules/routes.
320 remove_bogus_routes ()
322 # Get a IPs current hosted by this node, each anchored with '@'.
323 _ips=$($CTDB ip -v -X | awk -F'|' 'NR > 1 && $4 != "" {printf "@%s@\n", $2}')
326 while read _p _x _i _x _t ; do
327 # Remove trailing colon after priority/preference.
329 # Only remove rules that match our priority/preference.
330 [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
331 # Only remove rules for which we don't have an IP. This could
332 # be done with grep, but let's do it with shell prefix removal
333 # to avoid unnecessary processes. This falls through if
334 # "@${_i}@" isn't present in $_ips.
335 [ "$_ips" = "${_ips#*@${_i}@}" ] || continue
337 echo "Removing ip rule/routes for unhosted public address $_i"
338 del_routing_for_ip "$_i"
342 ######################################################################
344 service_reconfigure ()
346 add_missing_routes "force"
349 # flush our route cache
350 set_proc sys/net/ipv4/route/flush 1
353 ######################################################################
357 ctdb_service_check_reconfigure
361 flush_rules_and_routes
363 # make sure that we only respond to ARP messages from the NIC
364 # where a particular ip address is associated.
365 get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
366 set_proc sys/net/ipv4/conf/all/arp_filter 1
371 flush_rules_and_routes
380 ensure_ipv4_is_valid_addr "$1" "$ip"
381 add_routing_for_ip "$iface" "$ip"
383 # flush our route cache
384 set_proc sys/net/ipv4/route/flush 1
386 $CTDB gratiousarp "$ip" "$iface"
395 ensure_ipv4_is_valid_addr "$1" "$ip"
396 add_routing_for_ip "$niface" "$ip"
398 # flush our route cache
399 set_proc sys/net/ipv4/route/flush 1
401 $CTDB gratiousarp "$ip" "$niface"
402 tickle_tcp_connections "$ip"
410 ensure_ipv4_is_valid_addr "$1" "$ip"
411 del_routing_for_ip "$ip"
420 ctdb_standard_event_handler "$@"