#!/bin/sh # Script to set up one of the nodes as a NAT gateway for all other nodes. # This is used to ensure that all nodes in the cluster can still originate # traffic to the external network even if there are no public addresses # available. # [ -n "$CTDB_BASE" ] || \ CTDB_BASE=$(d=$(dirname "$0") && cd -P "$d" && dirname "$PWD") . "${CTDB_BASE}/functions" service_name="natgw" load_script_options [ -n "$CTDB_NATGW_NODES" ] || exit 0 export CTDB_NATGW_NODES ctdb_setup_state_dir "failover" "$service_name" # script_state_dir set by ctdb_setup_state_dir() # shellcheck disable=SC2154 natgw_cfg_new="${script_state_dir}/cfg_new" natgw_cfg_old="${script_state_dir}/cfg_old" natgw_leader_old="${script_state_dir}/leader_old" ctdb_natgw_follower_only () { _ip_address=$(ctdb_get_ip_address) awk -v my_ip="$_ip_address" \ '$1 == my_ip { if ($2 ~ "follower-only") { exit 0 } else { exit 1 } }' \ "$CTDB_NATGW_NODES" } natgw_check_config () { [ -r "$CTDB_NATGW_NODES" ] || \ die "error: CTDB_NATGW_NODES=${CTDB_NATGW_NODES} unreadable" if ! ctdb_natgw_follower_only ; then [ -n "$CTDB_NATGW_PUBLIC_IP" ] || \ die "Invalid configuration: CTDB_NATGW_PUBLIC_IP not set" [ -n "$CTDB_NATGW_PUBLIC_IFACE" ] || \ die "Invalid configuration: CTDB_NATGW_PUBLIC_IFACE not set" fi [ -n "$CTDB_NATGW_PRIVATE_NETWORK" ] || \ die "Invalid configuration: CTDB_NATGW_PRIVATE_NETWORK not set" # The default is to create a single default route [ -n "$CTDB_NATGW_STATIC_ROUTES" ] || CTDB_NATGW_STATIC_ROUTES="0.0.0.0/0" } natgw_write_config () { _f="$1" cat >"$_f" </dev/null 2>&1 ; then return 1 fi echo "NAT gateway configuration has changed" return 0 } _natgw_clear () { _ip="${CTDB_NATGW_PUBLIC_IP%/*}" _maskbits="${CTDB_NATGW_PUBLIC_IP#*/}" delete_ip_from_iface \ "$CTDB_NATGW_PUBLIC_IFACE" "$_ip" "$_maskbits" >/dev/null 2>&1 for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do _net="${_net_gw%@*}" ip route del "$_net" metric 10 >/dev/null 2>/dev/null done # Delete the masquerading setup from a previous iteration where we # were the NAT-GW iptables -D POSTROUTING -t nat \ -s "$CTDB_NATGW_PRIVATE_NETWORK" ! -d "$CTDB_NATGW_PRIVATE_NETWORK" \ -j MASQUERADE >/dev/null 2>/dev/null iptables -D INPUT -p tcp --syn -d "${_ip}/32" -j REJECT 2>/dev/null } natgw_clear () { if [ -r "$natgw_cfg_old" ] ; then (. "$natgw_cfg_old" ; _natgw_clear) else _natgw_clear fi } natgw_set_leader () { set_proc sys/net/ipv4/ip_forward 1 iptables -A POSTROUTING -t nat \ -s "$CTDB_NATGW_PRIVATE_NETWORK" ! -d "$CTDB_NATGW_PRIVATE_NETWORK" \ -j MASQUERADE # block all incoming connections to the NATGW IP address ctdb_natgw_public_ip_host="${CTDB_NATGW_PUBLIC_IP%/*}/32" iptables -D INPUT -p tcp --syn \ -d "$ctdb_natgw_public_ip_host" -j REJECT 2>/dev/null iptables -I INPUT -p tcp --syn \ -d "$ctdb_natgw_public_ip_host" -j REJECT 2>/dev/null ip addr add "$CTDB_NATGW_PUBLIC_IP" dev "$CTDB_NATGW_PUBLIC_IFACE" for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do _net="${_net_gw%@*}" if [ "$_net" != "$_net_gw" ] ; then _gw="${_net_gw#*@}" else _gw="$CTDB_NATGW_DEFAULT_GATEWAY" fi [ -n "$_gw" ] || continue ip route add "$_net" metric 10 via "$_gw" done } natgw_set_follower () { _natgwip="$1" for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do _net="${_net_gw%@*}" ip route add "$_net" via "$_natgwip" metric 10 done } natgw_ensure_leader () { # Intentional word splitting here # shellcheck disable=SC2046 set -- $("${CTDB_HELPER_BINDIR}/ctdb_natgw" leader) natgwleader="${1:--1}" # Default is -1, for failure above natgwip="$2" if [ "$natgwleader" = "-1" ]; then # Fail... die "There is no NATGW leader node" fi } natgw_leader_has_changed () { if [ -r "$natgw_leader_old" ] ; then read _old_natgwleader <"$natgw_leader_old" else _old_natgwleader="" fi [ "$_old_natgwleader" != "$natgwleader" ] } natgw_save_state () { echo "$natgwleader" >"$natgw_leader_old" # Created by natgw_config_has_changed() mv "$natgw_cfg_new" "$natgw_cfg_old" } case "$1" in setup) natgw_check_config ;; startup) natgw_check_config # Error if CTDB_NATGW_PUBLIC_IP is listed in public addresses ip_pat=$(echo "$CTDB_NATGW_PUBLIC_IP" | sed -e 's@\.@\\.@g') ctdb_public_addresses="${CTDB_BASE}/public_addresses" if grep -q "^${ip_pat}[[:space:]]" "$ctdb_public_addresses" ; then die "ERROR: CTDB_NATGW_PUBLIC_IP same as a public address" fi # do not send out arp requests from loopback addresses set_proc sys/net/ipv4/conf/all/arp_announce 2 ;; updatenatgw|ipreallocated) natgw_check_config natgw_ensure_leader natgw_config_has_changed || natgw_leader_has_changed || exit 0 natgw_clear pnn=$(ctdb_get_pnn) if [ "$pnn" = "$natgwleader" ]; then natgw_set_leader else natgw_set_follower "$natgwip" fi # flush our route cache set_proc sys/net/ipv4/route/flush 1 # Only update saved state when NATGW successfully updated natgw_save_state ;; shutdown|removenatgw) natgw_check_config natgw_clear ;; monitor) natgw_check_config if [ -n "$CTDB_NATGW_PUBLIC_IFACE" ] ; then interface_monitor "$CTDB_NATGW_PUBLIC_IFACE" || exit 1 fi ;; esac exit 0