6 # Do nothing if unconfigured
7 [ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
9 table_id_prefix="ctdb."
11 [ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
12 die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
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"
17 have_link_local_config ()
19 [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ]
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"
26 ######################################################################
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
40 _count=$(($_count + 1))
43 # A valid IPv4 address has 4 octets
47 ensure_ipv4_is_valid_addr ()
52 ipv4_is_valid_addr "$_ip" || {
53 echo "$0: $_event not an ipv4 address skipping IP:$_ip"
58 ipv4_host_addr_to_net ()
63 # Convert the host address to an unsigned long by splitting out
64 # the octets and doing the math.
66 for _o in $(export IFS="." ; echo $_host) ; do
67 _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
70 # Calculate the mask and apply it.
71 _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
72 _net_ul=$(( $_host_ul & $_mask_ul ))
74 # Now convert to a network address one byte at a time.
76 for _o in $(seq 1 4) ; do
77 _net="$(($_net_ul & 255))${_net:+.}${_net}"
78 _net_ul=$(($_net_ul >> 8))
81 echo "${_net}/${_maskbits}"
84 ######################################################################
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 ()
93 _f="$CTDB_ETCDIR/iproute2/rt_tables"
94 # This file should always exist, but...
95 if [ ! -f "$_f" ] ; then
96 mkdir -p $(dirname "$_f")
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}"
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
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"
112 _new=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
113 while read _t _l ; do
118 # Found existing: done
119 if [ "$_l" = "$_label" ] ; then
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
130 # If the new table id is legal then add it to the file and
132 if [ $_new -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] ; then
133 printf "%d\t%s\n" "$_new" "$_label" >>"$_f"
141 # Clean up all the table ids that we might own.
142 clean_up_table_ids ()
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")
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"
157 # Delete any items from the file that have a table id in our
158 # range or a label matching our label. Preserve comments.
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" \
164 !(min <= $1 && $1 <= max) && \
165 !(index($2, pre) == 1) \
166 { print $0 }' "$_f" >"$_tmp"
169 # The lock is gone - don't do anything else here
173 ######################################################################
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.
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"
189 done <"${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE:-/dev/null}${CTDB_BASE:+/public_addresses}}"
191 while read _i _rest ; do
192 if [ "$_ip" = "$_i" ] ; then
193 printf "%s\t%s\n" "$_ip" "$_rest"
195 done <"$CTDB_PER_IP_ROUTING_CONF"
199 ip_has_configuration ()
203 [ -n "$(get_config_for_ip $_ip)" ]
206 add_routing_for_ip ()
211 # Do nothing if no config for this IP.
212 ip_has_configuration "$_ip" || return 0
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"
217 _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
218 _table_id="${table_id_prefix}${_ip}"
220 del_routing_for_ip "$_ip"
222 ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
223 die "add_routing_for_ip: failed to add rule for $_ip"
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"
234 del_routing_for_ip ()
238 _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
239 _table_id="${table_id_prefix}${_ip}"
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
246 ######################################################################
248 flush_rules_and_routes ()
251 while read _p _x _i _x _t ; do
252 # Remove trailing colon after priority/preference.
254 # Only remove rules that match our priority/preference.
255 [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
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
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 ()
269 read _x # skip header line
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
277 while IFS=":" read _x _ip _x _iface _x ; do
278 [ -n "$_iface" ] || continue
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"
288 ######################################################################
294 flush_rules_and_routes
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
304 flush_rules_and_routes
313 ensure_ipv4_is_valid_addr "$1" "$ip"
314 add_routing_for_ip "$iface" "$ip"
316 # flush our route cache
317 set_proc sys/net/ipv4/route/flush 1
319 ctdb gratiousarp "$ip" "$iface"
328 ensure_ipv4_is_valid_addr "$1" "$ip"
329 add_routing_for_ip "$niface" "$ip"
331 # flush our route cache
332 set_proc sys/net/ipv4/route/flush 1
334 ctdb gratiousarp "$ip" "$niface"
335 tickle_tcp_connections "$ip"
343 ensure_ipv4_is_valid_addr "$1" "$ip"
344 del_routing_for_ip "$ip"
352 ctdb_standard_event_handler "$@"