diff --git a/heartbeat/ocf-binaries.in b/heartbeat/ocf-binaries.in index e11ae1d6f..aed6eecae 100644 --- a/heartbeat/ocf-binaries.in +++ b/heartbeat/ocf-binaries.in @@ -1,76 +1,77 @@ # Make sure PATH contains all the usual suspects PATH="$PATH:/sbin:/bin:/usr/sbin:/usr/bin" # Include /usr/ucb for finding whoami on Solaris PATH="$PATH:/usr/ucb" export PATH # Binaries and binary options for use in Resource Agents : ${AWK:=@AWK@} : ${EGREP:="@EGREP@"} : ${FGREP:="@FGREP@"} : ${IFCONFIG_A_OPT:="@IFCONFIG_A_OPT@"} : ${MAILCMD:=@MAILCMD@} : ${PING:=@PING@} : ${SH:=@SHELL@} : ${TEST:=@TEST@} : ${TESTPROG:=@TEST@} # Entries that should probably be removed : ${BASENAME:=basename} : ${BLOCKDEV:=blockdev} : ${CAT:=cat} : ${FSCK:=fsck} : ${FUSER:=fuser} : ${GETENT:=getent} : ${GREP:=grep} : ${IFCONFIG:=ifconfig} +: ${NFTABLES:=nft} : ${IPTABLES:=iptables} ## for cases that are known not to be serviceable with iptables-nft impl. : ${IPTABLES_LEGACY:=iptables-legacy} : ${IP2UTIL:=ip} : ${MDADM:=mdadm} : ${MODPROBE:=modprobe} : ${MOUNT:=mount} : ${MSGFMT:=msgfmt} : ${NETSTAT:=netstat} : ${PERL:=perl} : ${RAIDSTART:=raidstart} : ${RAIDSTOP:=raidstop} : ${ROUTE:=route} : ${UMOUNT:=umount} : ${REBOOT:=reboot} : ${POWEROFF_CMD:=poweroff} : ${WGET:=wget} : ${WHOAMI:=whoami} : ${STRINGSCMD:=strings} : ${SCP:=scp} : ${SSH:=@SSH@} : ${SWIG:=swig} : ${GZIP_PROG:=gzip} : ${TAR:=tar} : ${MD5:=md5} : ${DRBDADM:=drbdadm} : ${DRBDSETUP:=drbdsetup} check_binary () { if ! have_binary "$1"; then if [ "$OCF_NOT_RUNNING" = 7 ]; then # Chances are we have a fully setup OCF environment ocf_exit_reason "Setup problem: couldn't find command: $1" else echo "Setup problem: couldn't find command: $1" fi exit $OCF_ERR_INSTALLED fi } have_binary () { if [ "$OCF_TESTER_FAIL_HAVE_BINARY" = "1" ]; then false else local bin=`echo $1 | sed -e 's/ -.*//'` test -x "`which $bin 2>/dev/null`" fi } diff --git a/heartbeat/portblock b/heartbeat/portblock index 9b4f5db39..1eea28a6d 100755 --- a/heartbeat/portblock +++ b/heartbeat/portblock @@ -1,672 +1,895 @@ #!/bin/sh # -# portblock: iptables temporary portblocking control +# portblock: iptables temporary portblocking control # # Author: Sun Jiang Dong (initial version) # Philipp Reisner (per-IP filtering) +# Sebastian Baszczyj (nftables code) # # License: GNU General Public License (GPL) # # Copyright: (C) 2005 International Business Machines # # OCF parameters are as below: # OCF_RESKEY_protocol # OCF_RESKEY_portno # OCF_RESKEY_action # OCF_RESKEY_ip # OCF_RESKEY_tickle_dir # OCF_RESKEY_sync_script ####################################################################### # Initialization: : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs # Defaults +OCF_RESKEY_firewall_default="auto" OCF_RESKEY_protocol_default="" OCF_RESKEY_portno_default="" OCF_RESKEY_direction_default="in" OCF_RESKEY_action_default="" OCF_RESKEY_ip_default="0.0.0.0/0" OCF_RESKEY_reset_local_on_unblock_stop_default="false" OCF_RESKEY_tickle_dir_default="" OCF_RESKEY_sync_script_default="" +: ${OCF_RESKEY_firewall=${OCF_RESKEY_firewall_default}} : ${OCF_RESKEY_protocol=${OCF_RESKEY_protocol_default}} : ${OCF_RESKEY_portno=${OCF_RESKEY_portno_default}} : ${OCF_RESKEY_direction=${OCF_RESKEY_direction_default}} : ${OCF_RESKEY_action=${OCF_RESKEY_action_default}} : ${OCF_RESKEY_ip=${OCF_RESKEY_ip_default}} : ${OCF_RESKEY_reset_local_on_unblock_stop=${OCF_RESKEY_reset_local_on_unblock_stop_default}} : ${OCF_RESKEY_tickle_dir=${OCF_RESKEY_tickle_dir_default}} : ${OCF_RESKEY_sync_script=${OCF_RESKEY_sync_script_default}} ####################################################################### CMD=`basename $0` TICKLETCP=$HA_BIN/tickle_tcp +TABLE="portblock" +# Promotion scores +SCORE_UNPROMOTED=5 +SCORE_PROMOTED=10 usage() { cat <&2 - usage: $CMD {start|stop|status|monitor|meta-data|validate-all} + usage: $CMD {start|stop|promote|demote|status|monitor|meta-data|validate-all} - $CMD is used to temporarily block ports using iptables. + $CMD is used to temporarily block ports using nftables or iptables. It can be used to blackhole a port before bringing up an IP address, and enable it after a service is started. To do that for samba, the following can be used: crm configure < 1.0 -Resource script for portblock. It is used to temporarily block ports -using iptables. In addition, it may allow for faster TCP reconnects -for clients on failover. Use that if there are long lived TCP -connections to an HA service. This feature is enabled by setting the -tickle_dir parameter and only in concert with action set to unblock. +Resource script for portblock. It is used to block ports using nftables +or iptables. In addition, it may allow for faster TCP reconnects for +clients on failover. Use that if there are long lived TCP connections +to an HA service. This feature is enabled by setting the tickle_dir +parameter and only in concert with action set to unblock. Note that the tickle ACK function is new as of version 3.0.2 and hasn't yet seen widespread use. + +In Promotable mode, the promote action unblocks the ports on the Promoted node +and blocks the ports on the Unpromoted node(s) when action=block, and vice versa +when action=unblock. Block and unblocks access to TCP and UDP ports + + +Firewall to use, e.g. auto (default), nft, or iptables. + +Firewall + + + The protocol used to be blocked/unblocked. protocol The port number used to be blocked/unblocked. portno The action (block/unblock) to be done on the protocol::portno. + +In Promotable mode it is the initial action for start/demote actions, +and the promote action will change the state to the opposite. action If for some reason the long lived server side TCP sessions won't be cleaned up by a reconfiguration/flush/stop of whatever services this portblock protects, they would linger in the connection table, even after the IP is gone and services have been switched over to another node. An example would be the default NFS kernel server. These "known" connections may seriously confuse and delay a later switchback. Enabling this option will cause this agent to try to get rid of these connections by injecting a temporary iptables rule to TCP-reset outgoing packets from the blocked ports, and additionally tickle them locally, just before it starts to DROP incoming packets on "unblock stop". (try to) reset server TCP sessions when unblock stops The IP address used to be blocked/unblocked. ip -The shared or local directory (_must_ be absolute path) which +The shared or local directory (_must_ be absolute path) which stores the established TCP connections. Tickle directory If the tickle_dir is a local directory, then the TCP connection state file has to be replicated to other nodes in the cluster. It can be csync2 (default), some wrapper of rsync, or whatever. It takes the file name as a single argument. For csync2, set it to "csync2 -xv". Connection state file synchronization script Whether to block incoming or outgoing traffic. Can be either "in", "out", or "both". If "in" is used, the incoming ports are blocked on the INPUT chain. If "out" is used, the outgoing ports are blocked on the OUTPUT chain. If "both" is used, both the incoming and outgoing ports are blocked. Whether to block incoming or outgoing traffic, or both + + END } # # Because this is the normal usage, we consider "block" # resources to be pseudo-resources -- that is, their status can't # be reliably determined through external means. # This is because we expect an "unblock" resource to come along # and disable us -- but we're still in some sense active... # #active_grep_pat {udp|tcp} portno,portno ip {d|s} # d = look for destination ports # s = look for source ports active_grep_pat() { w="[ ][ ]*" any="0\\.0\\.0\\.0/0" src=$any dst=$3 if [ "$4" = "s" ]; then local src=$3 local dst=$any fi # iptables 1.8.9 briefly broke the output format, returning the # numeric protocol value instead of a string. Support both variants. if [ "$1" = "tcp" ]; then - local prot="(tcp|6)" + local prot="\(tcp\|6\)" else - local prot="(udp|17)" + local prot="\(udp\|17\)" + fi + if [ "$FIREWALL" = "nft" ]; then + local ip + [ "$4" = "s" ] && ip=$src || ip=$dst + echo "^\s\+ip $4addr ${ip} $1 $4port $2 ct state { established, related, new } drop$" + else + echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$" fi - echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$" } #chain_isactive {udp|tcp} portno,portno ip chain chain_isactive() { [ "$4" = "OUTPUT" ] && ds="s" || ds="d" PAT=$(active_grep_pat "$1" "$2" "$3" "$ds") - $IPTABLES $wait -n -L "$4" | grep -qE "$PAT" + if [ "$FIREWALL" = "nft" ]; then + $NFTABLES list chain inet $TABLE $4 2>&1 | grep -q "$PAT" + else + $IPTABLES $wait -n -L "$4" | grep -q "$PAT" + fi } # netstat -tn and ss -Htn, split on whitespace and colon, # look very similar: # tcp 0 0 10.43.55.1 675 10.43.9.8 2049 ESTABLISHED # ESTAB 0 0 10.43.55.1 675 10.43.9.8 2049 # so we can write one awk script for both get_established_tcp_connections() { local columns if [ -z "$1" ] ; then columns='$4,$5, $6,$7' else # swap local and remote for "tickle_local" columns='$6,$7, $4,$5' fi $ss_or_netstat | awk -F '[:[:space:]]+' ' ( $8 == "ESTABLISHED" || $1 == "ESTAB" ) && $4 == "'$OCF_RESKEY_ip'" \ {printf "%s:%s\t%s:%s\n", '"$columns"'}' } save_tcp_connections() { [ -z "$OCF_RESKEY_tickle_dir" ] && return statefile=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip # If we have _no_ sync script, we probably have a shared # (or replicated) directory, and need to fsync, or we might # end up with the just truncated file after failover, exactly # when we need it. # # If we _do_ have a sync script, it is not that important whether # the local state file is fsync'ed or not, the sync script is # responsible to "atomically" communicate the state to the peer(s). if [ -z "$OCF_RESKEY_sync_script" ]; then get_established_tcp_connections | dd of="$statefile".new conv=fsync status=none && mv "$statefile".new "$statefile" else get_established_tcp_connections > $statefile $OCF_RESKEY_sync_script $statefile > /dev/null 2>&1 & fi } tickle_remote() { [ -z "$OCF_RESKEY_tickle_dir" ] && return f=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip [ -r $f ] || return $TICKLETCP -n 3 < $f } tickle_local() { [ -z "$OCF_RESKEY_tickle_dir" ] && return f=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip [ -r $f ] || return # swap "local" and "remote" address, # so we tickle ourselves. # We set up a REJECT with tcp-reset before we do so, so we get rid of # the no longer wanted potentially long lived "ESTABLISHED" connection # entries on the IP we are going to delet in a sec. These would get in # the way if we switch-over and then switch-back in quick succession. local i awk '{ print $2, $1; }' $f | $TICKLETCP $ss_or_netstat | grep -Fw $OCF_RESKEY_ip || return for i in 0.1 0.5 1 2 4 ; do sleep $i # now kill what is currently in the list, # not what was recorded during last monitor get_established_tcp_connections swap | $TICKLETCP $ss_or_netstat | grep -Fw $OCF_RESKEY_ip || break done } SayActive() { ocf_log debug "$CMD DROP rule [$*] is running (OK)" } SayConsideredActive() { ocf_log debug "$CMD DROP rule [$*] considered to be running (OK)" } SayInactive() { ocf_log debug "$CMD DROP rule [$*] is inactive" } -#IptablesStatus {udp|tcp} portno,portno ip {in|out|both} {block|unblock} -IptablesStatus() { +#PortStatus {udp|tcp} portno,portno ip {in|out|both} {block|unblock} +PortStatus() { local rc rc=$OCF_ERR_GENERIC is_active=0 if [ "$4" = "in" ] || [ "$4" = "both" ]; then chain_isactive "$1" "$2" "$3" INPUT is_active=$? fi if [ "$4" = "out" ] || [ "$4" = "both" ]; then chain_isactive "$1" "$2" "$3" OUTPUT r=$? [ $r -gt $is_active ] && is_active=$r fi if [ $is_active -eq 0 ]; then case $5 in block) SayActive $* rc=$OCF_SUCCESS ;; *) SayInactive $* rc=$OCF_NOT_RUNNING ;; esac + elif ocf_is_ms; then + case $5 in + block) + SayInactive $* + rc=$OCF_NOT_RUNNING + ;; + *) + SayActive $* + rc=$OCF_SUCCESS + ;; + esac else case $5 in block) if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then SayConsideredActive $* rc=$OCF_SUCCESS else SayInactive $* rc=$OCF_NOT_RUNNING fi ;; *) if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then SayActive $* #This is only run on real monitor events. save_tcp_connections rc=$OCF_SUCCESS else SayInactive $* rc=$OCF_NOT_RUNNING fi ;; esac fi return $rc } -#DoIptables {-I|-D} {udp|tcp} portno,portno ip chain -DoIptables() +#NftDelete chain proto {d|s} ip ports +NftDelete() +{ + local chain=$1 proto=$2 ds=$3 ip=$(echo "$4" | sed "s#/#\\\/#") ports=$5 + # Try both single port and multi-port patterns for handle search + local handles=$($NFTABLES -a list chain inet $TABLE $chain 2>/dev/null | awk "/\s+ip ${ds}addr $ip $proto ${ds}port $ports/"' {printf "%d ", $NF}') + for handle in $handles; do + ocf_log debug "NftDelete: Deleting $chain rule with handle $handle" + nft delete rule inet $TABLE $chain handle $handle || { + ocf_exit_reason "NftDelete: Failed to delete $chain handle $handle." + return $OCF_ERR_GENERIC + } + done +} + +#DoPort {-I|-D} {udp|tcp} portno,portno ip chain +DoPort() { op=$1 proto=$2 ports=$3 ip=$4 chain=$5 active=0; chain_isactive "$proto" "$ports" "$ip" "$chain" && active=1 - want_active=0; [ "$op" = "-I" ] && want_active=1 + want_active=0; { [ "$op" = "insert" ] || [ "$op" = "-I" ] && want_active=1; } ocf_log debug "active: $active want_active: $want_active" if [ $active -eq $want_active ] ; then : Chain already in desired state else [ "$chain" = "OUTPUT" ] && ds="s" || ds="d" - $IPTABLES $wait "$op" "$chain" -p "$proto" -${ds} "$ip" -m multiport --${ds}ports "$ports" -j DROP + case $FIREWALL in + nft) + if [ "$op" = "insert" ]; then + $NFTABLES $op rule inet $TABLE $chain ip ${ds}addr $ip $proto ${ds}port $ports ct state { established, related, new } drop + elif [ "$op" = "delete" ]; then + NftDelete "$chain" "$proto" "$ds" "$ip" "$ports" + fi + ;; + iptables) + $IPTABLES $wait "$op" "$chain" -p "$proto" -${ds} "$ip" -m multiport --${ds}ports "$ports" -j DROP + ;; + esac fi } -#IptablesBLOCK {udp|tcp} portno,portno ip {in|out|both} {block|unblock} -IptablesBLOCK() +#PortBLOCK {udp|tcp} portno,portno ip {in|out|both} {block|unblock} +PortBLOCK() { local rc_in=0 local rc_out=0 + [ "$FIREWALL" = "nft" ] && action="insert" || action="-I" if [ "$4" = "in" ] || [ "$4" = "both" ]; then local try_reset=false - if [ "$1/$5/$__OCF_ACTION" = tcp/unblock/stop ] && + if [ "$1/$5/$__OCF_ACTION" = tcp/unblock/stop ] && ocf_is_true $reset_local_on_unblock_stop then try_reset=true fi if chain_isactive "$1" "$2" "$3" INPUT then : OK -- chain already active else - if $try_reset ; then - $IPTABLES $wait -I OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset - tickle_local - fi - $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP - rc_in=$? - if $try_reset ; then - $IPTABLES $wait -D OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset + if [ "$FIREWALL" = "nft" ]; then + if $try_reset ; then + $NFTABLES insert rule inet $TABLE OUTPUT ip saddr $3 $1 sport $2 ct state { established, related, new } reject with tcp reset + tickle_local + fi + $NFTABLES insert rule inet $TABLE INPUT ip daddr $3 $1 dport $2 ct state { established, related, new } drop + rc_in=$? + if $try_reset ; then + NftDelete "OUTPUT" "$1" "s" "$ports" + fi + else + if $try_reset ; then + $IPTABLES $wait -I OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset + tickle_local + fi + $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP + rc_in=$? + if $try_reset ; then + $IPTABLES $wait -D OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset + fi fi fi fi if [ "$4" = "out" ] || [ "$4" = "both" ]; then - DoIptables -I "$1" "$2" "$3" OUTPUT + DoPort "$action" "$1" "$2" "$3" OUTPUT rc_out=$? fi [ $rc_in -gt $rc_out ] && return $rc_in || return $rc_out } -#IptablesUNBLOCK {udp|tcp} portno,portno ip {in|out|both} -IptablesUNBLOCK() +#PortUNBLOCK {udp|tcp} portno,portno ip {in|out|both} +PortUNBLOCK() { + local action + [ "$FIREWALL" = "nft" ] && action="delete" || action="-D" if [ "$4" = "in" ] || [ "$4" = "both" ]; then - DoIptables -D "$1" "$2" "$3" INPUT + DoPort "$action" "$1" "$2" "$3" INPUT fi if [ "$4" = "out" ] || [ "$4" = "both" ]; then - DoIptables -D "$1" "$2" "$3" OUTPUT + DoPort "$action" "$1" "$2" "$3" OUTPUT fi return $? } -#IptablesStart {udp|tcp} portno,portno ip {in|out|both} {block|unblock} -IptablesStart() +#PortStart {udp|tcp} portno,portno ip {in|out|both} {block|unblock} +PortStart() { ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start + + if [ "$FIREWALL" = "nft" ]; then + $NFTABLES add table inet $TABLE || { + ocf_exit_reason "Failed to create nftables table $TABLE" + return $OCF_ERR_GENERIC + } + ocf_log debug "Created nftables table $TABLE" + + $NFTABLES add chain inet $TABLE INPUT { type filter hook input priority 0\; } || { + ocf_exit_reason "Failed to create INPUT chain" + return $OCF_ERR_GENERIC + } + ocf_log debug "Created INPUT chain" + + $NFTABLES add chain inet $TABLE OUTPUT { type filter hook output priority 0\; } || { + ocf_exit_reason "Failed to create OUTPUT chain" + return $OCF_ERR_GENERIC + } + ocf_log debug "Created OUTPUT chain" + fi + case $5 in - block) IptablesBLOCK "$@";; + block) PortBLOCK "$@" + rc=$? + ;; unblock) - IptablesUNBLOCK "$@" + PortUNBLOCK "$@" rc=$? tickle_remote #ignore run_tickle_tcp exit code! - return $rc ;; - *) usage; return 1; + *) usage; return $OCF_ERR_CONFIGURED ; esac - return $? + ocf_is_ms && ocf_promotion_score -v $SCORE_UNPROMOTED -N $nodename + + return $rc } -#IptablesStop {udp|tcp} portno,portno ip {in|out|both} {block|unblock} -IptablesStop() +#PortStop {udp|tcp} portno,portno ip {in|out|both} {block|unblock} +PortStop() { ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop + case $5 in - block) IptablesUNBLOCK "$@";; + block) PortUNBLOCK "$@" + rc=$? + ;; unblock) save_tcp_connections - IptablesBLOCK "$@" + PortBLOCK "$@" + rc=$? ;; - *) usage; return 1;; + *) usage; return $OCF_ERR_CONFIGURED ;; esac + ocf_is_ms && ocf_promotion_score -D -N $nodename + + return $rc +} + +PortPromote() { + PortStatus "$@" + rc=$? + if [ $rc -eq $OCF_SUCCESS ] && [ $promotion_score -eq $SCORE_PROMOTED ]; then + ocf_log info "Promote: resource already promoted." + return $rc + elif [ $rc -ne $OCF_SUCCESS ] && [ $rc -ne $OCF_NOT_RUNNING ]; then + ocf_exit_reason "Promote: PortStatus failed with rc: $rc." + return $rc + fi + case $5 in + block) PortBLOCK "$@" + rc=$? + ;; + unblock) + PortUNBLOCK "$@" + rc=$? + tickle_remote + #ignore run_tickle_tcp exit code! + ;; + *) usage; return $OCF_ERR_CONFIGURED ; + esac + ocf_promotion_score -v $SCORE_PROMOTED -N $nodename return $? } +PortDemote() { + PortStatus "$@" + rc=$? + if [ $rc -eq $OCF_SUCCESS ] && [ $promotion_score -eq $SCORE_UNPROMOTED ]; then + ocf_log info "Demote: resource already demoted." + return $rc + elif [ $rc -ne $OCF_SUCCESS ] && [ $rc -ne $OCF_NOT_RUNNING ]; then + ocf_exit_reason "Demote: PortStatus failed with rc: $rc." + return $rc + fi + case $5 in + block) + save_tcp_connections + PortBLOCK "$@" + rc=$? + ;; + unblock) PortUNBLOCK "$@" + rc=$? + ;; + *) usage; return $OCF_ERR_CONFIGURED ;; + esac + ocf_promotion_score -v $SCORE_UNPROMOTED -N $nodename + return $rc +} + # # Check if the port is valid, this function code is not decent, but works # CheckPort() { # Examples of valid port: "1080", "1", "0080" # Examples of invalid port: "1080bad", "0", "0000", "" echo $1 | $EGREP -qx '[0-9]+(:[0-9]+)?(,[0-9]+(:[0-9]+)?)*' } -IptablesValidateAll() +PortValidateAll() { - check_binary $IPTABLES + case $FIREWALL in + nft) + check_binary $IPTABLES ;; + iptables) + check_binary $NFTABLES ;; + esac + case $protocol in tcp|udp) ;; *) ocf_log err "Invalid protocol $protocol!" exit $OCF_ERR_CONFIGURED ;; esac if CheckPort "$portno"; then : else ocf_log err "Invalid port number $portno!" exit $OCF_ERR_CONFIGURED fi if [ -n "$OCF_RESKEY_tickle_dir" ]; then if [ x"$action" != x"unblock" ]; then ocf_log err "Tickles are only useful with action=unblock!" exit $OCF_ERR_CONFIGURED fi if [ ! -d "$OCF_RESKEY_tickle_dir" ]; then ocf_log err "The tickle dir doesn't exist!" - exit $OCF_ERR_INSTALLED + exit $OCF_ERR_INSTALLED fi fi case $action in - block|unblock) + block|unblock) ;; - *) + *) ocf_log err "Invalid action $action!" exit $OCF_ERR_CONFIGURED - ;; + ;; esac if ocf_is_true $reset_local_on_unblock_stop; then if [ $action != unblock ] ; then ocf_log err "reset_local_on_unblock_stop is only relevant with action=unblock" exit $OCF_ERR_CONFIGURED fi if [ -z $OCF_RESKEY_tickle_dir ] ; then ocf_log warn "reset_local_on_unblock_stop works best with tickle_dir enabled as well" fi fi return $OCF_SUCCESS } +# Detect firewall tool +detect_firewall_tool() { + if have_binary nft; then + FIREWALL="nft" + ocf_log debug "Detected nftables" + elif have_binary iptables; then + FIREWALL="iptables" + ocf_log debug "Detected iptables" + else + ocf_exit_reason "No firewall tool available" + return $OCF_ERR_CONFIGURED + fi +} + if ( [ $# -ne 1 ] ) then usage exit $OCF_ERR_ARGS fi -case $1 in +case $__OCF_ACTION in meta-data) meta_data exit $OCF_SUCCESS ;; usage) usage exit $OCF_SUCCESS ;; *) ;; esac if [ -z "$OCF_RESKEY_protocol" ]; then ocf_log err "Please set OCF_RESKEY_protocol" exit $OCF_ERR_CONFIGURED -fi +fi if [ -z "$OCF_RESKEY_portno" ]; then ocf_log err "Please set OCF_RESKEY_portno" exit $OCF_ERR_CONFIGURED -fi +fi if [ -z "$OCF_RESKEY_action" ]; then ocf_log err "Please set OCF_RESKEY_action" exit $OCF_ERR_CONFIGURED -fi - -# iptables v1.4.20+ is required to use -w (wait) -version=$(iptables -V | grep -oE '[0-9]+[\.0-9]+') -ocf_version_cmp "$version" "1.4.19.1" -if [ "$?" -eq "2" ]; then - wait="-w" -else - wait="" fi protocol=$OCF_RESKEY_protocol portno=$OCF_RESKEY_portno direction=$OCF_RESKEY_direction action=$OCF_RESKEY_action ip=$OCF_RESKEY_ip reset_local_on_unblock_stop=$OCF_RESKEY_reset_local_on_unblock_stop +nodename=$(ocf_local_nodename) # If "tickle" is enabled, we need to record the list of currently established # connections during monitor. Use ss where available, and netstat otherwise. if [ -n "$OCF_RESKEY_tickle_dir" ] ; then if have_binary ss ; then ss_or_netstat="ss -Htn" elif have_binary netstat ; then ss_or_netstat="netstat -tn" else ocf_log err "Neither ss nor netstat found, but needed to record estblished connections." exit $OCF_ERR_INSTALLED fi fi -case $1 in - start) - IptablesStart $protocol $portno $ip $direction $action +case $OCF_RESKEY_firewall in + auto) + detect_firewall_tool + ;; + nft|iptables) + FIREWALL="$OCF_RESKEY_firewall" + ;; + *) + ocf_exit_reason "Firewall '$OCF_RESKEY_firewall' not supported." + exit $OCF_ERR_CONFIGURED + ;; +esac + +if [ "$FIREWALL" = "nft" ]; then + echo "$portno" | grep -q "," && portno="{ $(echo $portno | sed 's/,/, /g') }" +elif [ "$FIREWALL" = "iptables" ]; then + # iptables v1.4.20+ is required to use -w (wait) + version=$(iptables -V | grep -oE '[0-9]+[\.0-9]+') + ocf_version_cmp "$version" "1.4.19.1" + if [ "$?" -eq "2" ]; then + wait="-w" + else + wait="" + fi +fi + +if ocf_is_ms; then + promotion_score=$(ocf_promotion_score -G -N $nodename -q 2> /dev/null) + if { [ "$__OCF_ACTION" = "monitor" ] && [ "$promotion_score" = "$SCORE_UNPROMOTED" ]; } || [ "$__OCF_ACTION" = "demote" ] || [ "$__OCF_ACTION" = "start" ]; then + case $action in + block) action="unblock" ;; + unblock) action="block" ;; + esac + fi +fi + +case $__OCF_ACTION in + start) + PortStart "$protocol" "$portno" "$ip" "$direction" "$action" + ;; + + stop) + PortStop "$protocol" "$portno" "$ip" "$direction" "$action" + ;; + + promote) + PortPromote $protocol "$portno" "$ip" "$direction" "$action" ;; - stop) - IptablesStop $protocol $portno $ip $direction $action + demote) + PortDemote "$protocol" "$portno" "$ip" "$direction" "$action" ;; - status|monitor) - IptablesStatus $protocol $portno $ip $direction $action + status|monitor) + PortStatus "$protocol" "$portno" "$ip" "$direction" "$action" ;; validate-all) - IptablesValidateAll + PortValidateAll ;; *) usage exit $OCF_ERR_UNIMPLEMENTED ;; esac exit $?