- portblock: add IPv6 support

- portblock: fix IPv6 rule detection in the iptables backend

  Resolves: RHEL-171167, RHEL-183131
This commit is contained in:
Oyvind Albrigtsen 2026-06-19 09:06:42 +02:00
parent 7a5c9e0f3f
commit be90666493
3 changed files with 211 additions and 1 deletions

View File

@ -0,0 +1,125 @@
From 269264e5a9859f9d2d31207d5067fc7673025db0 Mon Sep 17 00:00:00 2001
From: Yusuf Yildiz <yusuf@upforge.at>
Date: Wed, 22 Apr 2026 16:09:23 +0200
Subject: [PATCH] portblock: add IPv6 support to iptables and nft backends
Previously, portblock rejected IPv6 service IPs because:
- The iptables path fed the address to $IPTABLES (=iptables),
which only accepts IPv4.
- The nft path emitted `ip daddr`/`ip saddr`, which match IPv4
only even inside an `inet` table; IPv6 requires
`ip6 daddr`/`ip6 saddr`.
Detect the IP family from $OCF_RESKEY_ip once (a colon indicates
IPv6) and route both backends through a single ${ip_family}
variable:
- iptables path: IPTABLES=${ip_family}tables selects `iptables`
or `ip6tables` as appropriate.
- nft path: rule templates emit `${ip_family} daddr`/
`${ip_family} saddr`; the same keyword is used in the
active_grep_pat regex generator so monitor matches what write
emits.
- The `iptables -V` capability probe now uses $IPTABLES.
Verified on RHEL 9.7 across all four combinations of
(firewall=iptables|nft) x (ip=IPv4|IPv6):
- iptables + IPv4: installs iptables DROP rule (regression ok).
- nft + IPv4: emits `ip daddr ...` rule (regression ok).
- iptables + IPv6: installs ip6tables DROP rule (new).
- nft + IPv6: emits `ip6 daddr ...` rule (new).
stop cleans up fully in every case (no leftover rules in
`nft list ruleset`, `iptables -S`, or `ip6tables -S` after the
run).
tickle_tcp/tickle_local paths and documentation updates are not
covered by this change; they can be handled in follow-up PRs.
Closes #2151
Signed-off-by: Yusuf Yildiz <yusuf@upforge.at>
---
heartbeat/portblock | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/heartbeat/portblock b/heartbeat/portblock
index 803eea55f6..8fef3beec8 100755
--- a/heartbeat/portblock
+++ b/heartbeat/portblock
@@ -49,6 +49,14 @@ fi
: ${OCF_RESKEY_method=${OCF_RESKEY_method_default}}
: ${OCF_RESKEY_status_check=${OCF_RESKEY_status_check_default}}
: ${OCF_RESKEY_ip=${OCF_RESKEY_ip_default}}
+
+# IPv6 support: detect family from service IP and parameterise both backends.
+case "$OCF_RESKEY_ip" in
+ *:*) ip_family=ip6 ;;
+ *) ip_family=ip ;;
+esac
+IPTABLES=${ip_family}tables
+
: ${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}}
@@ -332,9 +340,9 @@ active_grep_pat()
local ip
[ "$4" = "s" ] && ip=$src || ip=$dst
if [ "$method" = "DROP" ]; then
- echo "^\s\+ip${w}$4addr${w}${ip}${w}$1${w}$4port${w}$2${w}ct${w}state${w}{${w}established,${w}related,${w}new${w}}${w}drop$"
+ echo "^\s\+${ip_family}${w}$4addr${w}${ip}${w}$1${w}$4port${w}$2${w}ct${w}state${w}{${w}established,${w}related,${w}new${w}}${w}drop$"
else
- echo "^\s\+ip${w}$4addr${w}${ip}${w}$1${w}$4port${w}$2${w}ct${w}state${w}{${w}established,${w}related,${w}new${w}}${w}reject${w}with${w}tcp${w}reset$"
+ echo "^\s\+${ip_family}${w}$4addr${w}${ip}${w}$1${w}$4port${w}$2${w}ct${w}state${w}{${w}established,${w}related,${w}new${w}}${w}reject${w}with${w}tcp${w}reset$"
fi
else
if [ "$method" = "DROP" ]; then
@@ -545,7 +553,7 @@ 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}')
+ local handles=$($NFTABLES -a list chain inet $TABLE $chain 2>/dev/null | awk "/\s+${ip_family} ${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 || {
@@ -570,9 +578,9 @@ DoPort()
nft)
if [ "$op" = "insert" ]; then
if [ "$method" = "DROP" ]; then
- $NFTABLES $op rule inet $TABLE $chain ip ${ds}addr $ip $proto ${ds}port $ports ct state { established, related, new } drop
+ $NFTABLES $op rule inet $TABLE $chain ${ip_family} ${ds}addr $ip $proto ${ds}port $ports ct state { established, related, new } drop
else
- $NFTABLES $op rule inet $TABLE $chain ip ${ds}addr $ip $proto ${ds}port $ports ct state { established, related, new } reject with tcp reset
+ $NFTABLES $op rule inet $TABLE $chain ${ip_family} ${ds}addr $ip $proto ${ds}port $ports ct state { established, related, new } reject with tcp reset
fi
elif [ "$op" = "delete" ]; then
NftDelete "$chain" "$proto" "$ds" "$ip" "$ports"
@@ -609,13 +617,13 @@ PortBLOCK()
else
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
+ $NFTABLES insert rule inet $TABLE OUTPUT ${ip_family} saddr $3 $1 sport $2 ct state { established, related, new } reject with tcp reset
tickle_local
fi
if [ "$method" = "DROP" ]; then
- $NFTABLES insert rule inet $TABLE INPUT ip daddr $3 $1 dport $2 ct state { established, related, new } drop
+ $NFTABLES insert rule inet $TABLE INPUT ${ip_family} daddr $3 $1 dport $2 ct state { established, related, new } drop
else
- $NFTABLES insert rule inet $TABLE INPUT ip daddr $3 $1 dport $2 ct state { established, related, new } reject with tcp reset
+ $NFTABLES insert rule inet $TABLE INPUT ${ip_family} daddr $3 $1 dport $2 ct state { established, related, new } reject with tcp reset
fi
rc_in=$?
if $try_reset ; then
@@ -937,7 +945,7 @@ 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]+')
+ version=$($IPTABLES -V | grep -oE '[0-9]+[\.0-9]+')
ocf_version_cmp "$version" "1.4.19.1"
if [ "$?" -eq "2" ]; then
wait="-w"

View File

@ -0,0 +1,75 @@
From 81386a499106053f658aad336865cc36f67ab168 Mon Sep 17 00:00:00 2001
From: Yusuf Yildiz <yusuf@upforge.at>
Date: Fri, 5 Jun 2026 12:42:42 +0200
Subject: [PATCH] portblock: fix IPv6 rule detection in the iptables backend
active_grep_pat() hardcoded the IPv4 "anywhere" address 0.0.0.0/0 when
building the regex used to detect existing DROP/REJECT rules from
"iptables -L" output. ip6tables prints ::/0 in the source/destination
"anywhere" column, so for an IPv6 service IP the grep never matched an
existing rule and chain_isactive() always reported the rule as absent.
Because rule handling is gated on that detection, an IPv6 OCF_RESKEY_ip
caused:
- "unblock start" to never remove the DROP rule (DoIptables() sees the
rule as already absent and skips the -D), so the port stayed blocked
after the protected service was up;
- "block stop" to likewise leave the DROP rule behind;
- monitor/status to always report inactive;
- duplicate rules to potentially accumulate across restarts.
"block start" appeared to work only because IptablesBLOCK() inserts the
rule when detection reports it absent, which masked the problem.
Select the "anywhere" literal with a case on $ip_family (::/0 for
ip6tables, 0.0.0.0/0 otherwise), matching the family-aware handling the
nft backend already had.
A second variant of the same detection failure remained: the "opt" column
("--") is printed by legacy iptables but omitted by nft-backed ip6tables,
so the mandatory "--" in the regex still failed to match on those builds
and the DROP rule was again never removed on unblock. Make the opt column
optional so the pattern matches both legacy iptables (prints "--") and
nft-backed ip6tables (omits it); IPv4 with legacy "--" is unchanged.
Tested on a 3-node AlmaLinux 9.7 cluster (iptables/ip6tables v1.8.10,
nf_tables) driving an IPv6 service IP end-to-end through LINSTOR Gateway
kernel NFS (portblock action=block + action=unblock): the DROP rule is now
inserted on block and removed on unblock with no leftover rule, monitor
reports correctly, and the IPv4 path is unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Yusuf Yildiz <yusuf@upforge.at>
---
heartbeat/portblock | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/heartbeat/portblock b/heartbeat/portblock
index 8fef3beec8..f922ee0db6 100755
--- a/heartbeat/portblock
+++ b/heartbeat/portblock
@@ -323,7 +323,10 @@ END
active_grep_pat()
{
w="[ ][ ]*"
- any="0\\.0\\.0\\.0/0"
+ case $ip_family in
+ ip6) any="::/0" ;;
+ *) any="0\\.0\\.0\\.0/0" ;;
+ esac
src=$any dst=$3
if [ "$4" = "s" ]; then
local src=$3
@@ -346,9 +349,9 @@ active_grep_pat()
fi
else
if [ "$method" = "DROP" ]; then
- echo "^DROP${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$"
+ echo "^DROP${w}${prot}${w}\(--${w}\)\?${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}$"
else
- echo "^REJECT${w}${prot}${w}--${w}${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}${w}ctstate${w}NEW,RELATED,ESTABLISHED${w}reject-with${w}tcp-reset$"
+ echo "^REJECT${w}${prot}${w}\(--${w}\)\?${src}${w}${dst}${w}multiport${w}${4}ports${w}${2}${w}ctstate${w}NEW,RELATED,ESTABLISHED${w}reject-with${w}tcp-reset$"
fi
fi
}

View File

@ -45,7 +45,7 @@
Name: resource-agents
Summary: Open Source HA Reusable Cluster Resource Scripts
Version: 4.16.0
Release: 70%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}
Release: 71%{?rcver:%{rcver}}%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}
License: GPL-2.0-or-later AND LGPL-2.1-or-later
URL: https://github.com/ClusterLabs/resource-agents
Source0: %{upstream_prefix}-%{upstream_version}.tar.gz
@ -141,6 +141,8 @@ Patch88: RHEL-180765-2-nfsserver-fixes-incorrect-indentation.patch
Patch89: RHEL-156729-sybaseASE-fix-for-missing-sybaseASE-env-during-probe-action.patch
Patch90: RHEL-180765-3-nfsserver-do-not-try-to-stop-fsidd-when-not-present.patch
Patch91: RHEL-182592-nfsserver-monitor-nfsdcld-and-nfs-mountd-services-to-trigger-recovery-on-failure.patch
Patch92: RHEL-171167-portblock-add-IPv6-support.patch
Patch93: RHEL-183131-portblock-fix-IPv6-rule-detection-in-the-iptables-backend.patch
# bundled ha-cloud-support libs
Patch500: ha-cloud-support-aliyun.patch
@ -403,6 +405,8 @@ exit 1
%patch -p1 -P 89
%patch -p1 -P 90
%patch -p1 -P 91
%patch -p1 -P 92
%patch -p1 -P 93
# bundled ha-cloud-support libs
%patch -p1 -P 500
@ -735,6 +739,12 @@ rm -rf %{buildroot}/usr/share/doc/resource-agents
%{_usr}/lib/ocf/lib/heartbeat/OCF_*.pm
%changelog
* Fri Jun 19 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.16.0-71
- portblock: add IPv6 support
- portblock: fix IPv6 rule detection in the iptables backend
Resolves: RHEL-171167, RHEL-183131
* Thu Jun 11 2026 Arslan Ahmad <arahmad@redhat.com> - 4.16.0-70
- nfsserver: monitor nfsdcld and nfs-mountd services to trigger
recovery on failure