2009-12-19 20:26:01 +03:00
#!/bin/sh
2013-01-03 08:26:12 +04:00
[ -n "$CTDB_BASE" ] || \
2016-06-29 10:36:05 +03:00
CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
. "${CTDB_BASE}/functions"
2013-01-03 08:26:12 +04:00
2009-12-19 20:26:01 +03:00
loadconfig
2018-03-07 03:12:29 +03:00
service_name="per_ip_routing"
2013-04-29 21:32:29 +04:00
2012-03-01 08:23:53 +04:00
# Do nothing if unconfigured
[ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
2010-12-16 00:42:44 +03:00
2012-03-01 08:23:53 +04:00
table_id_prefix="ctdb."
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
[ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
[ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
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"
2009-12-19 20:26:01 +03:00
2014-01-16 07:48:39 +04:00
if [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -le 253 -a \
255 -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
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"
fi
2012-07-30 06:51:12 +04:00
have_link_local_config ()
{
[ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ]
}
if ! have_link_local_config && [ ! -r "$CTDB_PER_IP_ROUTING_CONF" ] ; then
die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
fi
2018-03-07 03:12:29 +03:00
ctdb_setup_state_dir "failover" "$service_name"
2016-07-11 13:53:56 +03:00
2012-03-01 08:23:53 +04:00
######################################################################
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
ipv4_is_valid_addr()
2009-12-19 20:26:01 +03:00
{
2012-03-01 08:23:53 +04:00
_ip="$1"
_count=0
# Get the shell to break up the address into 1 word per octet
2016-07-06 10:31:51 +03:00
# Intentional word splitting here
# shellcheck disable=SC2086
2012-03-01 08:23:53 +04:00
for _o in $(export IFS="." ; echo $_ip) ; do
# The 2>/dev/null stops output from failures where an "octet"
# is not numeric. The test will still fail.
if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
return 1
2009-12-19 20:26:01 +03:00
fi
2016-07-06 09:50:30 +03:00
_count=$((_count + 1))
2012-03-01 08:23:53 +04:00
done
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
# A valid IPv4 address has 4 octets
[ $_count -eq 4 ]
2009-12-19 20:26:01 +03:00
}
2012-03-01 08:23:53 +04:00
ensure_ipv4_is_valid_addr ()
{
_event="$1"
_ip="$2"
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
ipv4_is_valid_addr "$_ip" || {
echo "$0: $_event not an ipv4 address skipping IP:$_ip"
exit 0
}
}
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
ipv4_host_addr_to_net ()
{
_host="$1"
_maskbits="$2"
# Convert the host address to an unsigned long by splitting out
# the octets and doing the math.
_host_ul=0
2016-07-06 10:31:51 +03:00
# Intentional word splitting here
# shellcheck disable=SC2086
2012-03-01 08:23:53 +04:00
for _o in $(export IFS="." ; echo $_host) ; do
2016-07-06 09:50:30 +03:00
_host_ul=$(( (_host_ul << 8) + _o)) # work around Emacs color bug
2012-03-01 08:23:53 +04:00
done
# Calculate the mask and apply it.
2016-07-06 09:50:30 +03:00
_mask_ul=$(( 0xffffffff << (32 - _maskbits) ))
_net_ul=$(( _host_ul & _mask_ul ))
2012-03-01 08:23:53 +04:00
# Now convert to a network address one byte at a time.
_net=""
for _o in $(seq 1 4) ; do
2016-07-06 09:50:30 +03:00
_net="$((_net_ul & 255))${_net:+.}${_net}"
_net_ul=$((_net_ul >> 8))
2012-03-01 08:23:53 +04:00
done
echo "${_net}/${_maskbits}"
}
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
######################################################################
2009-12-19 20:26:01 +03:00
2015-08-17 06:39:10 +03:00
ensure_rt_tables ()
{
2015-08-17 07:12:15 +03:00
rt_tables="$CTDB_SYS_ETCDIR/iproute2/rt_tables"
2018-03-07 03:12:29 +03:00
# script_state_dir set by ctdb_setup_state_dir()
# shellcheck disable=SC2154
rt_tables_lock="${script_state_dir}/rt_tables_lock"
2015-08-17 06:39:10 +03:00
# This file should always exist. Even if this didn't exist on the
# system, adding a route will have created it. What if we startup
# and immediately shutdown? Let's be sure.
if [ ! -f "$rt_tables" ] ; then
mkdir -p "${rt_tables%/*}" # dirname
touch "$rt_tables"
fi
}
2012-03-01 08:23:53 +04:00
# Setup a table id to use for the given IP. We don't need to know it,
# it just needs to exist in /etc/iproute2/rt_tables. Fail if no free
# table id could be found in the configured range.
ensure_table_id_for_ip ()
{
_ip=$1
2015-08-17 06:39:10 +03:00
ensure_rt_tables
2012-03-01 08:23:53 +04:00
# Maintain a table id for each IP address we've ever seen in
# rt_tables. We use a "ctdb." prefix on the label.
_label="${table_id_prefix}${_ip}"
# This finds either the table id corresponding to the label or a
# new unused one (that is greater than all the used ones in the
# range).
(
# Note that die() just gets us out of the subshell...
2016-07-11 13:53:56 +03:00
flock --timeout 30 9 || \
2015-08-17 06:39:10 +03:00
die "ensure_table_id_for_ip: failed to lock file $rt_tables"
2012-03-01 08:23:53 +04:00
2016-07-06 10:31:51 +03:00
_new="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
2012-03-01 08:23:53 +04:00
while read _t _l ; do
# Skip comments
case "$_t" in
\#*) continue ;;
esac
# Found existing: done
if [ "$_l" = "$_label" ] ; then
return 0
fi
# Potentially update the new table id to be used. The
# redirect stops error spam for a non-numeric value.
2016-07-06 10:31:51 +03:00
if [ "$_new" -le "$_t" -a \
"$_t" -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null ; then
2016-07-06 09:50:30 +03:00
_new=$((_t + 1))
2012-03-01 08:23:53 +04:00
fi
2016-07-11 13:53:56 +03:00
done <"$rt_tables"
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
# If the new table id is legal then add it to the file and
# print it.
2016-07-06 10:31:51 +03:00
if [ "$_new" -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
2015-08-17 06:39:10 +03:00
printf "%d\t%s\n" "$_new" "$_label" >>"$rt_tables"
2012-03-01 08:23:53 +04:00
return 0
else
return 1
fi
2016-07-11 13:53:56 +03:00
) 9>"$rt_tables_lock"
2009-12-19 20:26:01 +03:00
}
2012-03-01 08:23:53 +04:00
# Clean up all the table ids that we might own.
clean_up_table_ids ()
2009-12-19 20:26:01 +03:00
{
2015-08-17 06:39:10 +03:00
ensure_rt_tables
2012-03-01 08:23:53 +04:00
(
# Note that die() just gets us out of the subshell...
2016-07-11 13:53:56 +03:00
flock --timeout 30 9 || \
2015-08-17 06:39:10 +03:00
die "clean_up_table_ids: failed to lock file $rt_tables"
2012-03-01 08:23:53 +04:00
# Delete any items from the file that have a table id in our
# range or a label matching our label. Preserve comments.
2015-08-17 06:39:10 +03:00
_tmp="${rt_tables}.$$.ctdb"
2012-03-01 08:23:53 +04:00
awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
-v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
-v pre="$table_id_prefix" \
2016-07-06 13:09:07 +03:00
'/^#/ ||
!(min <= $1 && $1 <= max) &&
2017-01-15 23:24:15 +03:00
!(index($2, pre) == 1) {
print $0 }' "$rt_tables" >"$_tmp"
2012-03-01 08:23:53 +04:00
2015-08-17 06:39:10 +03:00
mv "$_tmp" "$rt_tables"
2016-07-11 13:53:56 +03:00
) 9>"$rt_tables_lock"
2012-03-01 08:23:53 +04:00
}
2010-03-04 18:06:11 +03:00
2012-03-01 08:23:53 +04:00
######################################################################
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
# This prints the config for an IP, which is either relevant entries
# from the config file or, if set to the magic link local value, some
# link local routing config for the IP.
get_config_for_ip ()
{
_ip="$1"
2012-07-30 06:51:12 +04:00
if have_link_local_config ; then
2012-03-01 08:23:53 +04:00
# When parsing public_addresses also split on '/'. This means
# that we get the maskbits as item #2 without further parsing.
while IFS="/$IFS" read _i _maskbits _x ; do
if [ "$_ip" = "$_i" ] ; then
2016-07-06 23:41:27 +03:00
printf "%s" "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
2012-03-01 08:23:53 +04:00
fi
2018-03-08 07:11:51 +03:00
done <"${CTDB_BASE}/public_addresses"
2012-03-01 08:23:53 +04:00
else
while read _i _rest ; do
if [ "$_ip" = "$_i" ] ; then
printf "%s\t%s\n" "$_ip" "$_rest"
fi
done <"$CTDB_PER_IP_ROUTING_CONF"
fi
2009-12-19 20:26:01 +03:00
}
2012-03-01 08:23:53 +04:00
ip_has_configuration ()
2009-12-19 20:26:01 +03:00
{
2012-03-01 08:23:53 +04:00
_ip="$1"
2009-12-19 20:26:01 +03:00
2016-07-06 10:31:51 +03:00
_conf=$(get_config_for_ip "$_ip")
[ -n "$_conf" ]
2009-12-19 20:26:01 +03:00
}
2012-03-01 08:23:53 +04:00
add_routing_for_ip ()
2009-12-19 20:26:01 +03:00
{
2012-03-01 08:23:53 +04:00
_iface="$1"
_ip="$2"
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
# Do nothing if no config for this IP.
ip_has_configuration "$_ip" || return 0
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
ensure_table_id_for_ip "$_ip" || \
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"
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
_pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
_table_id="${table_id_prefix}${_ip}"
2009-12-19 20:26:01 +03:00
2012-06-13 07:49:49 +04:00
del_routing_for_ip "$_ip" >/dev/null 2>&1
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
die "add_routing_for_ip: failed to add rule for $_ip"
2010-02-09 18:34:59 +03:00
2012-03-01 08:23:53 +04:00
# Add routes to table for any lines matching the IP.
get_config_for_ip "$_ip" |
while read _i _dest _gw ; do
_r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
2016-07-06 10:31:51 +03:00
# Intentionally unquoted multi-word value here
# shellcheck disable=SC2086
2012-03-01 08:23:53 +04:00
ip route add $_r || \
die "add_routing_for_ip: failed to add route: $_r"
done
2009-12-19 20:26:01 +03:00
}
2012-03-01 08:23:53 +04:00
del_routing_for_ip ()
2010-02-09 18:34:59 +03:00
{
2012-03-01 08:23:53 +04:00
_ip="$1"
2010-02-09 18:34:59 +03:00
2012-03-01 08:23:53 +04:00
_pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
_table_id="${table_id_prefix}${_ip}"
2010-02-09 18:34:59 +03:00
2012-06-13 07:49:49 +04:00
# Do this unconditionally since we own any matching table ids.
# However, print a meaningful message if something goes wrong.
_cmd="ip rule del from $_ip pref $_pref table $_table_id"
_out=$($_cmd 2>&1) || \
cat <<EOF
WARNING: Failed to delete policy routing rule
Command "$_cmd" failed:
$_out
EOF
2012-07-17 14:19:37 +04:00
# This should never usually fail, so don't redirect output.
# However, it can fail when deleting a rogue IP, since there will
# be no routes for that IP. In this case it should only fail when
# the rule deletion above has already failed because the table id
# is invalid. Therefore, go to a little bit of trouble to indent
# the failure message so that it is associated with the above
# warning message and doesn't look too nasty.
2016-06-29 11:11:44 +03:00
ip route flush table "$_table_id" 2>&1 | sed -e 's@^.@ &@'
2012-03-01 08:23:53 +04:00
}
2010-02-09 18:34:59 +03:00
2012-03-01 08:23:53 +04:00
######################################################################
2010-02-09 18:34:59 +03:00
2012-03-01 08:23:53 +04:00
flush_rules_and_routes ()
{
ip rule show |
while read _p _x _i _x _t ; do
# Remove trailing colon after priority/preference.
_p="${_p%:}"
# Only remove rules that match our priority/preference.
[ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
echo "Removing ip rule for public address $_i for routing table $_t"
ip rule del from "$_i" table "$_t" pref "$_p"
ip route flush table "$_t" 2>/dev/null
2010-02-09 18:34:59 +03:00
done
2012-03-01 08:23:53 +04:00
}
2010-02-09 18:34:59 +03:00
2012-03-01 08:23:53 +04:00
# Add any missing routes. Some might have gone missing if, for
# example, all IPs on the network were removed (possibly if the
2012-08-03 04:54:30 +04:00
# primary was removed). If $1 is "force" then (re-)add all the
# routes.
2012-03-01 08:23:53 +04:00
add_missing_routes ()
{
2016-06-08 14:23:07 +03:00
$CTDB ip -v -X | {
2012-03-01 08:23:53 +04:00
read _x # skip header line
# Read the rest of the lines. We're only interested in the
# "IP" and "ActiveInterface" columns. The latter is only set
# for addresses local to this node, making it easy to skip
# non-local addresses. For each IP local address we check if
# the relevant routing table is populated and populate it if
# not.
2014-11-20 06:32:46 +03:00
while IFS="|" read _x _ip _x _iface _x ; do
2012-03-01 08:23:53 +04:00
[ -n "$_iface" ] || continue
2016-07-06 10:31:51 +03:00
2012-03-01 08:23:53 +04:00
_table_id="${table_id_prefix}${_ip}"
2016-07-06 10:31:51 +03:00
if [ -z "$(ip route show table "$_table_id" 2>/dev/null)" -o \
2012-08-03 04:54:30 +04:00
"$1" = "force" ] ; then
2012-03-01 08:23:53 +04:00
add_routing_for_ip "$_iface" "$_ip"
fi
done
2012-07-30 06:51:12 +04:00
} || exit $?
2010-02-09 18:34:59 +03:00
}
2012-06-15 11:22:02 +04:00
# Remove rules/routes for addresses that we're not hosting. If a
# releaseip event failed in an earlier script then we might not have
# had a chance to remove the corresponding rules/routes.
remove_bogus_routes ()
{
# Get a IPs current hosted by this node, each anchored with '@'.
2016-06-08 14:23:07 +03:00
_ips=$($CTDB ip -v -X | awk -F'|' 'NR > 1 && $4 != "" {printf "@%s@\n", $2}')
2012-06-15 11:22:02 +04:00
2016-07-06 10:16:44 +03:00
# x is intentionally ignored
# shellcheck disable=SC2034
2012-06-15 11:22:02 +04:00
ip rule show |
while read _p _x _i _x _t ; do
# Remove trailing colon after priority/preference.
_p="${_p%:}"
# Only remove rules that match our priority/preference.
[ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
# Only remove rules for which we don't have an IP. This could
# be done with grep, but let's do it with shell prefix removal
# to avoid unnecessary processes. This falls through if
# "@${_i}@" isn't present in $_ips.
[ "$_ips" = "${_ips#*@${_i}@}" ] || continue
echo "Removing ip rule/routes for unhosted public address $_i"
del_routing_for_ip "$_i"
done
}
2012-03-01 08:23:53 +04:00
######################################################################
2011-08-23 10:36:19 +04:00
ctdb_check_args "$@"
2009-12-19 20:26:01 +03:00
case "$1" in
2016-07-06 07:44:14 +03:00
startup)
2012-03-01 08:23:53 +04:00
flush_rules_and_routes
2009-12-19 20:26:01 +03:00
2012-03-01 08:23:53 +04:00
# make sure that we only respond to ARP messages from the NIC
# where a particular ip address is associated.
2012-03-20 09:20:10 +04:00
get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
set_proc sys/net/ipv4/conf/all/arp_filter 1
2009-12-19 20:26:01 +03:00
}
;;
2016-07-06 07:44:14 +03:00
shutdown)
2012-03-01 08:23:53 +04:00
flush_rules_and_routes
clean_up_table_ids
2009-12-19 20:26:01 +03:00
;;
2016-07-06 07:44:14 +03:00
takeip)
2009-12-19 20:26:01 +03:00
iface=$2
ip=$3
2016-07-06 10:16:44 +03:00
# maskbits included here so argument order is obvious
# shellcheck disable=SC2034
2009-12-19 20:26:01 +03:00
maskbits=$4
2012-03-01 08:23:53 +04:00
ensure_ipv4_is_valid_addr "$1" "$ip"
add_routing_for_ip "$iface" "$ip"
2010-02-12 11:52:09 +03:00
2009-12-19 20:26:01 +03:00
# flush our route cache
2012-03-20 09:20:10 +04:00
set_proc sys/net/ipv4/route/flush 1
2009-12-19 20:26:01 +03:00
2017-01-16 05:38:50 +03:00
$CTDB gratarp "$ip" "$iface"
2009-12-19 20:26:01 +03:00
;;
2016-07-06 07:44:14 +03:00
updateip)
2016-07-06 10:16:44 +03:00
# oiface, maskbits included here so argument order is obvious
# shellcheck disable=SC2034
2009-12-21 10:45:19 +03:00
oiface=$2
niface=$3
ip=$4
2016-07-06 10:16:44 +03:00
# shellcheck disable=SC2034
2009-12-21 10:45:19 +03:00
maskbits=$5
2012-03-01 08:23:53 +04:00
ensure_ipv4_is_valid_addr "$1" "$ip"
add_routing_for_ip "$niface" "$ip"
2010-02-12 11:52:09 +03:00
2009-12-21 10:45:19 +03:00
# flush our route cache
2012-03-20 09:20:10 +04:00
set_proc sys/net/ipv4/route/flush 1
2009-12-21 10:45:19 +03:00
2017-01-16 05:38:50 +03:00
$CTDB gratarp "$ip" "$niface"
2012-03-01 08:23:53 +04:00
tickle_tcp_connections "$ip"
2009-12-21 10:45:19 +03:00
;;
2009-12-19 20:26:01 +03:00
2016-07-06 07:44:14 +03:00
releaseip)
2009-12-19 20:26:01 +03:00
iface=$2
ip=$3
2016-07-06 10:16:44 +03:00
# maskbits included here so argument order is obvious
# shellcheck disable=SC2034
2009-12-19 20:26:01 +03:00
maskbits=$4
2012-03-01 08:23:53 +04:00
ensure_ipv4_is_valid_addr "$1" "$ip"
del_routing_for_ip "$ip"
2009-12-19 20:26:01 +03:00
;;
2016-07-06 07:44:14 +03:00
ipreallocated)
2012-03-01 08:23:53 +04:00
add_missing_routes
2012-06-15 11:22:02 +04:00
remove_bogus_routes
2009-12-19 20:26:01 +03:00
;;
2016-12-14 07:06:45 +03:00
reconfigure)
2018-02-06 04:00:31 +03:00
echo "Reconfiguring service \"${service_name}\"..."
add_missing_routes "force"
remove_bogus_routes
# flush our route cache
set_proc sys/net/ipv4/route/flush 1
2016-12-14 07:06:45 +03:00
;;
2009-12-19 20:26:01 +03:00
esac
exit 0