Compare commits

...

No commits in common. "c8" and "c9-beta" have entirely different histories.
c8 ... c9-beta

49 changed files with 2467 additions and 4241 deletions

View File

@ -1 +1 @@
e5b8b96e901d81ea8e806f44306acbf73487f3ad SOURCES/firewalld-0.9.11.tar.gz c72d4a04e7726d3352c911f20ddc44cff5515c4c SOURCES/firewalld-1.3.4.tar.bz2

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/firewalld-0.9.11.tar.gz SOURCES/firewalld-1.3.4.tar.bz2

View File

@ -1,7 +1,7 @@
From feb06c3d50c737183c08fd05592d5c9209f4b966 Mon Sep 17 00:00:00 2001 From f113f17734cfb964bd2b72f233c48e650e205cb9 Mon Sep 17 00:00:00 2001
From: Eric Garver <e@erig.me> From: Eric Garver <egarver@redhat.com>
Date: Mon, 9 Jul 2018 11:29:33 -0400 Date: Tue, 25 May 2021 13:31:41 -0400
Subject: [PATCH 01/10] RHEL only: Add cockpit by default to some zones Subject: [PATCH 1/4] RHEL only: Add cockpit by default to some zones
Fixes: #1581578 Fixes: #1581578
--- ---
@ -9,57 +9,75 @@ Fixes: #1581578
config/zones/internal.xml | 1 + config/zones/internal.xml | 1 +
config/zones/public.xml | 1 + config/zones/public.xml | 1 +
config/zones/work.xml | 1 + config/zones/work.xml | 1 +
src/tests/functions.at | 19 +++++++++++++++++++ src/tests/features/startup_failsafe.at | 1 +
5 files changed, 23 insertions(+) src/tests/functions.at | 20 ++++++++++++++++++++
6 files changed, 25 insertions(+)
diff --git a/config/zones/home.xml b/config/zones/home.xml diff --git a/config/zones/home.xml b/config/zones/home.xml
index 42b29b2f2d50..8aa8afa0e8aa 100644 index d73c9bdb16b6..33064688367e 100644
--- a/config/zones/home.xml --- a/config/zones/home.xml
+++ b/config/zones/home.xml +++ b/config/zones/home.xml
@@ -6,4 +6,5 @@ @@ -6,5 +6,6 @@
<service name="mdns"/> <service name="mdns"/>
<service name="samba-client"/> <service name="samba-client"/>
<service name="dhcpv6-client"/> <service name="dhcpv6-client"/>
+ <service name="cockpit"/> + <service name="cockpit"/>
<forward/>
</zone> </zone>
diff --git a/config/zones/internal.xml b/config/zones/internal.xml diff --git a/config/zones/internal.xml b/config/zones/internal.xml
index e646b48c94e8..40cb7e14424b 100644 index 053c18ccda8b..852b16ad94dd 100644
--- a/config/zones/internal.xml --- a/config/zones/internal.xml
+++ b/config/zones/internal.xml +++ b/config/zones/internal.xml
@@ -6,4 +6,5 @@ @@ -6,5 +6,6 @@
<service name="mdns"/> <service name="mdns"/>
<service name="samba-client"/> <service name="samba-client"/>
<service name="dhcpv6-client"/> <service name="dhcpv6-client"/>
+ <service name="cockpit"/> + <service name="cockpit"/>
<forward/>
</zone> </zone>
diff --git a/config/zones/public.xml b/config/zones/public.xml diff --git a/config/zones/public.xml b/config/zones/public.xml
index 49795d8c9068..617e131a4895 100644 index 49fc4c20af52..62bc751de448 100644
--- a/config/zones/public.xml --- a/config/zones/public.xml
+++ b/config/zones/public.xml +++ b/config/zones/public.xml
@@ -4,4 +4,5 @@ @@ -4,5 +4,6 @@
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description> <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/> <service name="ssh"/>
<service name="dhcpv6-client"/> <service name="dhcpv6-client"/>
+ <service name="cockpit"/> + <service name="cockpit"/>
<forward/>
</zone> </zone>
diff --git a/config/zones/work.xml b/config/zones/work.xml diff --git a/config/zones/work.xml b/config/zones/work.xml
index 6ea5550a40bd..9609ee6f65c2 100644 index f1a14a9b4682..27b54a7783c4 100644
--- a/config/zones/work.xml --- a/config/zones/work.xml
+++ b/config/zones/work.xml +++ b/config/zones/work.xml
@@ -4,4 +4,5 @@ @@ -4,5 +4,6 @@
<description>For use in work areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description> <description>For use in work areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/> <service name="ssh"/>
<service name="dhcpv6-client"/> <service name="dhcpv6-client"/>
+ <service name="cockpit"/> + <service name="cockpit"/>
<forward/>
</zone> </zone>
diff --git a/src/tests/features/startup_failsafe.at b/src/tests/features/startup_failsafe.at
index 3cdf7c3c307a..b9401d460114 100644
--- a/src/tests/features/startup_failsafe.at
+++ b/src/tests/features/startup_failsafe.at
@@ -20,6 +20,7 @@ NFT_LIST_RULES([inet], [filter_IN_public_allow], 0, [dnl
chain filter_IN_public_allow {
tcp dport 22 accept
ip6 daddr fe80::/64 udp dport 546 accept
+ tcp dport 9090 accept
tcp dport 443 accept
}
}
diff --git a/src/tests/functions.at b/src/tests/functions.at diff --git a/src/tests/functions.at b/src/tests/functions.at
index 72db26d5ce0c..2f8183966760 100644 index 244d24686c86..ad3462c6715f 100644
--- a/src/tests/functions.at --- a/src/tests/functions.at
+++ b/src/tests/functions.at +++ b/src/tests/functions.at
@@ -112,6 +112,13 @@ m4_define([FWD_START_TEST], [ @@ -128,6 +128,14 @@ m4_define([FWD_START_TEST], [
fi fi
m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [ m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [
+ AT_KEYWORDS(offline)
+ dnl cockpit is added by default downstream, but upstream tests don't expect + dnl cockpit is added by default downstream, but upstream tests don't expect
+ dnl it. Simply remove it at the start of every test. + dnl it. Simply remove it at the start of every test.
+ dnl + dnl
@ -68,9 +86,9 @@ index 72db26d5ce0c..2f8183966760 100644
+ FWD_OFFLINE_CHECK([--zone public --remove-service-from-zone cockpit], 0, [ignore]) + FWD_OFFLINE_CHECK([--zone public --remove-service-from-zone cockpit], 0, [ignore])
+ FWD_OFFLINE_CHECK([--zone work --remove-service-from-zone cockpit], 0, [ignore]) + FWD_OFFLINE_CHECK([--zone work --remove-service-from-zone cockpit], 0, [ignore])
], [ ], [
dnl don't unload modules or bother cleaning up, the namespace will be deleted dnl set the appropriate backend
AT_CHECK([sed -i 's/^CleanupOnExit.*/CleanupOnExit=no/' ./firewalld.conf]) AT_CHECK([sed -i 's/^FirewallBackend.*/FirewallBackend=FIREWALL_BACKEND/' ./firewalld.conf])
@@ -229,6 +236,18 @@ m4_define([FWD_START_TEST], [ @@ -259,6 +267,18 @@ m4_define([FWD_START_TEST], [
]) ])
FWD_START_FIREWALLD FWD_START_FIREWALLD
@ -90,5 +108,5 @@ index 72db26d5ce0c..2f8183966760 100644
]) ])
-- --
2.39.1 2.39.3

View File

@ -1,80 +0,0 @@
From 6b88f757186f0b6479c2a334c0c0362a2ba05570 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 4 Feb 2020 09:12:17 -0500
Subject: [PATCH 02/10] RHEL only: default to AllowZoneDrifting=yes
---
config/firewalld.conf | 4 ++--
doc/xml/firewalld.conf.xml | 2 +-
doc/xml/firewalld.dbus.xml | 2 +-
src/firewall/config/__init__.py.in | 2 +-
src/tests/functions.at | 5 +++++
5 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 99d573dcf06f..a0556c0bbf5b 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -73,5 +73,5 @@ RFC3964_IPv4=yes
# Note: If "yes" packets will only drift from source based zones to interface
# based zones (including the default zone). Packets never drift from interface
# based zones to other interfaces based zones (including the default zone).
-# Possible values; "yes", "no". Defaults to "no".
-AllowZoneDrifting=no
+# Possible values; "yes", "no". Defaults to "yes".
+AllowZoneDrifting=yes
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 8155c547a216..0a6e8f2fdebf 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -206,7 +206,7 @@
to interface based zones (including the default zone). Packets
never drift from interface based zones to other interfaces
based zones (including the default zone).
- Valid values; "yes", "no". Defaults to "no".
+ Valid values; "yes", "no". Defaults to "yes".
</para>
</listitem>
</varlistentry>
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index da442f3f41b9..1c33ad5ee918 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2787,7 +2787,7 @@
to interface based zones (including the default zone). Packets
never drift from interface based zones to other interfaces
based zones (including the default zone).
- Valid values; "yes", "no". Defaults to "no".
+ Valid values; "yes", "no". Defaults to "yes".
</para></listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Properties.AutomaticHelpers">
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index e875e849dec1..0dec7913f694 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -133,4 +133,4 @@ FALLBACK_AUTOMATIC_HELPERS = "no"
FALLBACK_FIREWALL_BACKEND = "nftables"
FALLBACK_FLUSH_ALL_ON_RELOAD = True
FALLBACK_RFC3964_IPV4 = True
-FALLBACK_ALLOW_ZONE_DRIFTING = False
+FALLBACK_ALLOW_ZONE_DRIFTING = True
diff --git a/src/tests/functions.at b/src/tests/functions.at
index 2f8183966760..a2989c6345da 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -126,6 +126,11 @@ m4_define([FWD_START_TEST], [
dnl set the appropriate backend
AT_CHECK([sed -i 's/^FirewallBackend.*/FirewallBackend=FIREWALL_BACKEND/' ./firewalld.conf])
+ dnl Expected test results assume this is set to "no", but downstream
+ dnl RHEL overrides it to "yes". Override it back to "no" so we don't
+ dnl have to fix up all the tests when bringing them from upstream.
+ AT_CHECK([sed -i 's/^AllowZoneDrifting.*/AllowZoneDrifting=no/' ./firewalld.conf])
+
dnl fib matching is pretty new in nftables. Don't use rpfilter on older
dnl kernels.
m4_if(nftables, FIREWALL_BACKEND, [
--
2.39.1

View File

@ -1,13 +1,13 @@
From 3a56ea30acb41358742a94f088f12bd4f1ba1f80 Mon Sep 17 00:00:00 2001 From 0598abdbc3dd1816e9cc19186de9e95c6485519d Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life> From: Eric Garver <eric@garver.life>
Date: Tue, 31 Jan 2023 09:24:56 -0500 Date: Tue, 31 Jan 2023 09:24:56 -0500
Subject: [PATCH 27/30] v2.0.0: test(atlocal): pass EBTABLES to testsuite Subject: [PATCH 2/4] v1.4.0: test(atlocal): pass EBTABLES to testsuite
(cherry picked from commit a5adb26a5eebdaa6e978c580d4fb73f7aa06802f) (cherry picked from commit a5adb26a5eebdaa6e978c580d4fb73f7aa06802f)
--- ---
src/tests/atlocal.in | 1 + src/tests/atlocal.in | 1 +
src/tests/functions.at | 2 +- src/tests/functions.at | 3 ++-
2 files changed, 2 insertions(+), 1 deletion(-) 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/tests/atlocal.in b/src/tests/atlocal.in diff --git a/src/tests/atlocal.in b/src/tests/atlocal.in
index 8c5493ac38df..595a96f0f5c9 100644 index 8c5493ac38df..595a96f0f5c9 100644
@ -21,10 +21,18 @@ index 8c5493ac38df..595a96f0f5c9 100644
export IPTABLES_RESTORE="@IPTABLES_RESTORE@" export IPTABLES_RESTORE="@IPTABLES_RESTORE@"
export IP6TABLES="@IP6TABLES@" export IP6TABLES="@IP6TABLES@"
diff --git a/src/tests/functions.at b/src/tests/functions.at diff --git a/src/tests/functions.at b/src/tests/functions.at
index a2989c6345da..35e3271ce68d 100644 index ad3462c6715f..f454ca980046 100644
--- a/src/tests/functions.at --- a/src/tests/functions.at
+++ b/src/tests/functions.at +++ b/src/tests/functions.at
@@ -368,7 +368,7 @@ m4_define([EBTABLES_LIST_RULES_NORMALIZE], [dnl @@ -104,6 +104,7 @@ m4_define([FWD_START_TEST], [
dnl in atlocal.
dnl
test -z "$PYTHON" && export PYTHON="python3"
+ test -z "$EBTABLES" && export EBTABLES="ebtables"
test -z "$IPTABLES" && export IPTABLES="iptables"
test -z "$IPTABLES_RESTORE" && export IPTABLES_RESTORE="iptables-restore"
test -z "$IP6TABLES" && export IP6TABLES="ip6tables"
@@ -398,7 +399,7 @@ m4_define([EBTABLES_LIST_RULES_NORMALIZE], [dnl
m4_define([EBTABLES_LIST_RULES], [ m4_define([EBTABLES_LIST_RULES], [
dnl ebtables commit 5f508b76a0ce change list output for inversion. dnl ebtables commit 5f508b76a0ce change list output for inversion.
m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [], [ m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [], [
@ -34,5 +42,5 @@ index a2989c6345da..35e3271ce68d 100644
]) ])
]) ])
-- --
2.43.0 2.39.3

View File

@ -1,56 +0,0 @@
From 17a69c4dd7feff3c6101b5541497b8304447ed40 Mon Sep 17 00:00:00 2001
From: Vrinda Punj <vpunj@redhat.com>
Date: Tue, 1 Dec 2020 11:58:19 -0500
Subject: [PATCH 03/10] v1.0.0: feat(service): add galera service Fixes:
rhbz1696260
(cherry picked from commit 11632147677464cb7121d17526ead242e68be041)
---
config/Makefile.am | 1 +
config/services/galera.xml | 9 +++++++++
po/POTFILES.in | 1 +
3 files changed, 11 insertions(+)
create mode 100644 config/services/galera.xml
diff --git a/config/Makefile.am b/config/Makefile.am
index fef3b55dc527..f844a5a00e2f 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -159,6 +159,7 @@ CONFIG_FILES = \
services/freeipa-replication.xml \
services/freeipa-trust.xml \
services/ftp.xml \
+ services/galera.xml \
services/ganglia-client.xml \
services/ganglia-master.xml \
services/git.xml \
diff --git a/config/services/galera.xml b/config/services/galera.xml
new file mode 100644
index 000000000000..2305713fbcab
--- /dev/null
+++ b/config/services/galera.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>Galera</short>
+ <description>MariaDB-Galera Database Server</description>
+ <port protocol="tcp" port="3306"/>
+ <port protocol="tcp" port="4567"/>
+ <port protocol="tcp" port="4568"/>
+ <port protocol="tcp" port="4444"/>
+</service>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 666eb677855b..249cff8d0d2f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -91,6 +91,7 @@ config/services/freeipa-ldap.xml
config/services/freeipa-replication.xml
config/services/freeipa-trust.xml
config/services/ftp.xml
+config/services/galera.xml
config/services/ganglia-client.xml
config/services/ganglia-master.xml
config/services/git.xml
--
2.39.1

View File

@ -1,7 +1,7 @@
From 17c70eba7ddfd8a8687b16102cf5ee988e33993f Mon Sep 17 00:00:00 2001 From 35a4e98cfee37b2883a58ac586f0bdb34810293b Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life> From: Eric Garver <eric@garver.life>
Date: Mon, 30 Jan 2023 16:42:50 -0500 Date: Mon, 30 Jan 2023 16:42:50 -0500
Subject: [PATCH 29/30] v2.0.0: feat(direct): avoid iptables flush if using Subject: [PATCH 3/4] v1.4.0: feat(direct): avoid iptables flush if using
nftables backend nftables backend
If FirewallBackend=nftables and there are no direct rules; then we can If FirewallBackend=nftables and there are no direct rules; then we can
@ -11,25 +11,25 @@ applications can control iptables while firewalld only touches nftables.
Fixes: #863 Fixes: #863
(cherry picked from commit b7faa74db15e2d1ebd9fdfcdc7579874d3a2fa87) (cherry picked from commit b7faa74db15e2d1ebd9fdfcdc7579874d3a2fa87)
--- ---
src/firewall/core/fw.py | 31 +++++++++++++++++++++++++++---- src/firewall/core/fw.py | 30 ++++++++++++++++++++++++++----
src/firewall/core/fw_direct.py | 9 +++++++++ src/firewall/core/fw_direct.py | 9 +++++++++
2 files changed, 36 insertions(+), 4 deletions(-) 2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 5cef18b5f889..a2ad39bd9f5f 100644 index e9db1c6fede0..f1bc124b9443 100644
--- a/src/firewall/core/fw.py --- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py +++ b/src/firewall/core/fw.py
@@ -425,7 +425,8 @@ class Firewall(object): @@ -473,7 +473,8 @@ class Firewall(object):
def _start_apply_objects(self, reload=False, complete_reload=False):
transaction = FirewallTransaction(self) transaction = FirewallTransaction(self)
# flush rules
- self.flush(use_transaction=transaction) - self.flush(use_transaction=transaction)
+ if not reload: + if not reload:
+ self.flush(use_transaction=transaction) + self.flush(use_transaction=transaction)
# If modules need to be unloaded in complete reload or if there are # If modules need to be unloaded in complete reload or if there are
# ipsets to get applied, limit the transaction to flush. # ipsets to get applied, limit the transaction to flush.
@@ -836,7 +837,26 @@ class Firewall(object): @@ -943,7 +944,26 @@ class Firewall(object):
if use_transaction is None: if use_transaction is None:
transaction.execute(True) transaction.execute(True)
@ -57,7 +57,7 @@ index 5cef18b5f889..a2ad39bd9f5f 100644
def flush(self, use_transaction=None): def flush(self, use_transaction=None):
if use_transaction is None: if use_transaction is None:
@@ -846,7 +866,10 @@ class Firewall(object): @@ -953,7 +973,10 @@ class Firewall(object):
log.debug1("Flushing rule set") log.debug1("Flushing rule set")
@ -69,17 +69,16 @@ index 5cef18b5f889..a2ad39bd9f5f 100644
rules = backend.build_flush_rules() rules = backend.build_flush_rules()
transaction.add_rules(backend, rules) transaction.add_rules(backend, rules)
@@ -1002,7 +1025,7 @@ class Firewall(object): @@ -1114,7 +1137,6 @@ class Firewall(object):
if not _panic: if not _panic:
self.set_policy("DROP") self.set_policy("DROP")
- # stop - # stop
+ self.flush() self.flush()
self.cleanup() self.cleanup()
start_exception = None
diff --git a/src/firewall/core/fw_direct.py b/src/firewall/core/fw_direct.py diff --git a/src/firewall/core/fw_direct.py b/src/firewall/core/fw_direct.py
index a35ebce1f276..5d4cc6a6918e 100644 index 508cfa54f7fa..a4cd8a77e773 100644
--- a/src/firewall/core/fw_direct.py --- a/src/firewall/core/fw_direct.py
+++ b/src/firewall/core/fw_direct.py +++ b/src/firewall/core/fw_direct.py
@@ -219,6 +219,9 @@ class FirewallDirect(object): @@ -219,6 +219,9 @@ class FirewallDirect(object):
@ -89,29 +88,29 @@ index a35ebce1f276..5d4cc6a6918e 100644
+ if self._fw.may_skip_flush_direct_backends(): + if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends) + transaction.add_pre(self._fw.flush_direct_backends)
+ +
#TODO: policy="ACCEPT" if self._fw.ipset_enabled and self._fw.ipset.omit_native_ipset():
self._chain(True, ipv, table, chain, transaction) transaction.add_pre(self._fw.ipset.apply_ipsets, [self._fw.ipset_backend])
@@ -265,6 +268,9 @@ class FirewallDirect(object): @@ -268,6 +271,9 @@ class FirewallDirect(object):
else: else:
transaction = use_transaction transaction = use_transaction
+ if self._fw.may_skip_flush_direct_backends(): + if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends) + transaction.add_pre(self._fw.flush_direct_backends)
+ +
self._rule(True, ipv, table, chain, priority, args, transaction) if self._fw.ipset_enabled and self._fw.ipset.omit_native_ipset():
transaction.add_pre(self._fw.ipset.apply_ipsets, [self._fw.ipset_backend])
if use_transaction is None: @@ -353,6 +359,9 @@ class FirewallDirect(object):
@@ -347,6 +353,9 @@ class FirewallDirect(object):
else: else:
transaction = use_transaction transaction = use_transaction
+ if self._fw.may_skip_flush_direct_backends(): + if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends) + transaction.add_pre(self._fw.flush_direct_backends)
+ +
self._passthrough(True, ipv, list(args), transaction) if self._fw.ipset_enabled and self._fw.ipset.omit_native_ipset():
transaction.add_pre(self._fw.ipset.apply_ipsets, [self._fw.ipset_backend])
if use_transaction is None:
-- --
2.43.0 2.39.3

View File

@ -1,242 +0,0 @@
From 430dee713b69a32e5c5bf6b1f68a605564fe93ef Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 12 Feb 2021 14:23:21 -0500
Subject: [PATCH 04/10] v1.0.0: fix(ipset): normalize entries in CIDR notation
This will convert things like 10.0.1.0/22 to 10.0.0.0/22. Fix up test
cases in which the error code changed due to this.
(cherry picked from commit e4dc44fcfd214b27c718eb4d99d3b137495b9626)
---
src/firewall/client.py | 9 ++++++++-
src/firewall/core/fw_ipset.py | 11 ++++++++++-
src/firewall/core/ipset.py | 13 +++++++++++++
src/firewall/server/config_ipset.py | 10 ++++++++--
src/tests/regression/rhbz1601610.at | 19 +++++++++++++------
5 files changed, 52 insertions(+), 10 deletions(-)
diff --git a/src/firewall/client.py b/src/firewall/client.py
index 51bf09c8fad6..aa6bd7cd282b 100644
--- a/src/firewall/client.py
+++ b/src/firewall/client.py
@@ -34,6 +34,7 @@ from firewall.core.base import DEFAULT_ZONE_TARGET, DEFAULT_POLICY_TARGET, DEFAU
from firewall.dbus_utils import dbus_to_python
from firewall.functions import b2u
from firewall.core.rich import Rich_Rule
+from firewall.core.ipset import normalize_ipset_entry
from firewall import errors
from firewall.errors import FirewallError
@@ -1616,12 +1617,16 @@ class FirewallClientIPSetSettings(object):
if "timeout" in self.settings[4] and \
self.settings[4]["timeout"] != "0":
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
- self.settings[5] = entries
+ _entries = set()
+ for _entry in dbus_to_python(entries, list):
+ _entries.add(normalize_ipset_entry(_entry))
+ self.settings[5] = list(_entries)
@handle_exceptions
def addEntry(self, entry):
if "timeout" in self.settings[4] and \
self.settings[4]["timeout"] != "0":
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
+ entry = normalize_ipset_entry(entry)
if entry not in self.settings[5]:
self.settings[5].append(entry)
else:
@@ -1631,6 +1636,7 @@ class FirewallClientIPSetSettings(object):
if "timeout" in self.settings[4] and \
self.settings[4]["timeout"] != "0":
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
+ entry = normalize_ipset_entry(entry)
if entry in self.settings[5]:
self.settings[5].remove(entry)
else:
@@ -1640,6 +1646,7 @@ class FirewallClientIPSetSettings(object):
if "timeout" in self.settings[4] and \
self.settings[4]["timeout"] != "0":
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
+ entry = normalize_ipset_entry(entry)
return entry in self.settings[5]
# ipset config
diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py
index e90082407562..57e0e6cb51db 100644
--- a/src/firewall/core/fw_ipset.py
+++ b/src/firewall/core/fw_ipset.py
@@ -24,7 +24,8 @@
__all__ = [ "FirewallIPSet" ]
from firewall.core.logger import log
-from firewall.core.ipset import remove_default_create_options as rm_def_cr_opts
+from firewall.core.ipset import remove_default_create_options as rm_def_cr_opts, \
+ normalize_ipset_entry
from firewall.core.io.ipset import IPSet
from firewall import errors
from firewall.errors import FirewallError
@@ -189,6 +190,7 @@ class FirewallIPSet(object):
def add_entry(self, name, entry):
obj = self.get_ipset(name, applied=True)
+ entry = normalize_ipset_entry(entry)
IPSet.check_entry(entry, obj.options, obj.type)
if entry in obj.entries:
@@ -207,6 +209,7 @@ class FirewallIPSet(object):
def remove_entry(self, name, entry):
obj = self.get_ipset(name, applied=True)
+ entry = normalize_ipset_entry(entry)
# no entry check for removal
if entry not in obj.entries:
@@ -224,6 +227,7 @@ class FirewallIPSet(object):
def query_entry(self, name, entry):
obj = self.get_ipset(name, applied=True)
+ entry = normalize_ipset_entry(entry)
if "timeout" in obj.options and obj.options["timeout"] != "0":
# no entries visible for ipsets with timeout
raise FirewallError(errors.IPSET_WITH_TIMEOUT, name)
@@ -237,6 +241,11 @@ class FirewallIPSet(object):
def set_entries(self, name, entries):
obj = self.get_ipset(name, applied=True)
+ _entries = set()
+ for _entry in entries:
+ _entries.add(normalize_ipset_entry(_entry))
+ entries = list(_entries)
+
for entry in entries:
IPSet.check_entry(entry, obj.options, obj.type)
if "timeout" not in obj.options or obj.options["timeout"] == "0":
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
index 0d632143ce13..5bb21856f648 100644
--- a/src/firewall/core/ipset.py
+++ b/src/firewall/core/ipset.py
@@ -24,6 +24,7 @@
__all__ = [ "ipset", "check_ipset_name", "remove_default_create_options" ]
import os.path
+import ipaddress
from firewall import errors
from firewall.errors import FirewallError
@@ -289,3 +290,15 @@ def remove_default_create_options(options):
IPSET_DEFAULT_CREATE_OPTIONS[opt] == _options[opt]:
del _options[opt]
return _options
+
+def normalize_ipset_entry(entry):
+ """ Normalize IP addresses in entry """
+ _entry = []
+ for _part in entry.split(","):
+ try:
+ _part.index("/")
+ _entry.append(str(ipaddress.ip_network(_part, strict=False)))
+ except ValueError:
+ _entry.append(_part)
+
+ return ",".join(_entry)
diff --git a/src/firewall/server/config_ipset.py b/src/firewall/server/config_ipset.py
index 8c647bc29ab9..18ef5783de62 100644
--- a/src/firewall/server/config_ipset.py
+++ b/src/firewall/server/config_ipset.py
@@ -33,7 +33,7 @@ from firewall.dbus_utils import dbus_to_python, \
dbus_introspection_prepare_properties, \
dbus_introspection_add_properties
from firewall.core.io.ipset import IPSet
-from firewall.core.ipset import IPSET_TYPES
+from firewall.core.ipset import IPSET_TYPES, normalize_ipset_entry
from firewall.core.logger import log
from firewall.server.decorators import handle_exceptions, \
dbus_handle_exceptions, dbus_service_method
@@ -406,7 +406,10 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
in_signature='as')
@dbus_handle_exceptions
def setEntries(self, entries, sender=None):
- entries = dbus_to_python(entries, list)
+ _entries = set()
+ for _entry in dbus_to_python(entries, list):
+ _entries.add(normalize_ipset_entry(_entry))
+ entries = list(_entries)
log.debug1("%s.setEntries('[%s]')", self._log_prefix,
",".join(entries))
self.parent.accessCheck(sender)
@@ -421,6 +424,7 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
@dbus_handle_exceptions
def addEntry(self, entry, sender=None):
entry = dbus_to_python(entry, str)
+ entry = normalize_ipset_entry(entry)
log.debug1("%s.addEntry('%s')", self._log_prefix, entry)
self.parent.accessCheck(sender)
settings = list(self.getSettings())
@@ -436,6 +440,7 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
@dbus_handle_exceptions
def removeEntry(self, entry, sender=None):
entry = dbus_to_python(entry, str)
+ entry = normalize_ipset_entry(entry)
log.debug1("%s.removeEntry('%s')", self._log_prefix, entry)
self.parent.accessCheck(sender)
settings = list(self.getSettings())
@@ -451,6 +456,7 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
@dbus_handle_exceptions
def queryEntry(self, entry, sender=None): # pylint: disable=W0613
entry = dbus_to_python(entry, str)
+ entry = normalize_ipset_entry(entry)
log.debug1("%s.queryEntry('%s')", self._log_prefix, entry)
settings = list(self.getSettings())
if "timeout" in settings[4] and settings[4]["timeout"] != "0":
diff --git a/src/tests/regression/rhbz1601610.at b/src/tests/regression/rhbz1601610.at
index ede2c45b88c1..a716539a8acf 100644
--- a/src/tests/regression/rhbz1601610.at
+++ b/src/tests/regression/rhbz1601610.at
@@ -6,11 +6,14 @@ CHECK_IPSET
FWD_CHECK([-q --new-ipset=foobar --permanent --type=hash:net])
FWD_RELOAD
-FWD_CHECK([-q --ipset=foobar --add-entry=10.1.1.0/22])
-FWD_CHECK([-q --ipset=foobar --add-entry=10.1.2.0/22], 13, ignore, ignore)
-FWD_CHECK([-q --ipset=foobar --add-entry=10.2.0.0/22])
+FWD_CHECK([--ipset=foobar --add-entry=10.1.1.0/22], 0, [ignore])
+FWD_CHECK([--ipset=foobar --query-entry 10.1.2.0/22], 0, [ignore])
+FWD_CHECK([--ipset=foobar --add-entry=10.1.2.0/22], 0, [ignore], [dnl
+Warning: ALREADY_ENABLED: '10.1.0.0/22' already is in 'foobar'
+])
+FWD_CHECK([--ipset=foobar --add-entry=10.2.0.0/22], 0, [ignore])
FWD_CHECK([--ipset=foobar --get-entries], 0, [dnl
-10.1.1.0/22
+10.1.0.0/22
10.2.0.0/22
])
NFT_LIST_SET([foobar], 0, [dnl
@@ -31,6 +34,9 @@ Members:
])
FWD_CHECK([-q --ipset=foobar --remove-entry=10.1.1.0/22])
+FWD_CHECK([--ipset=foobar --query-entry 10.1.1.0/22], 1, [ignore])
+FWD_CHECK([--ipset=foobar --query-entry 10.1.2.0/22], 1, [ignore])
+FWD_CHECK([--ipset=foobar --query-entry 10.2.0.0/22], 0, [ignore])
FWD_CHECK([--ipset=foobar --get-entries], 0, [dnl
10.2.0.0/22
])
@@ -52,7 +58,7 @@ Members:
FWD_CHECK([-q --permanent --ipset=foobar --add-entry=10.1.1.0/22])
FWD_CHECK([--permanent --ipset=foobar --get-entries], 0, [dnl
-10.1.1.0/22
+10.1.0.0/22
])
FWD_CHECK([-q --permanent --ipset=foobar --remove-entry=10.1.1.0/22])
FWD_CHECK([--permanent --ipset=foobar --get-entries], 0, [
@@ -101,4 +107,5 @@ Members:
FWD_END_TEST([-e '/ERROR: COMMAND_FAILED:.*already added.*/d'dnl
-e '/ERROR: COMMAND_FAILED:.*element.*exists/d'dnl
- -e '/Kernel support protocol versions/d'])
+ -e '/Kernel support protocol versions/d'dnl
+ -e '/WARNING: ALREADY_ENABLED:/d'])
--
2.39.1

View File

@ -1,32 +1,32 @@
From 2e34d7361f8a7528f5e5d86f794bc87c94f8214e Mon Sep 17 00:00:00 2001 From 6069c94b5033fc82d8f35f2068a61374559d22de Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life> From: Eric Garver <eric@garver.life>
Date: Mon, 30 Jan 2023 14:43:18 -0500 Date: Mon, 30 Jan 2023 14:43:18 -0500
Subject: [PATCH 30/30] v2.0.0: test(direct): avoid iptables flush if using Subject: [PATCH 4/4] v1.4.0: test(direct): avoid iptables flush if using
nftables backend nftables backend
Coverage: #863 Coverage: #863
(cherry picked from commit dcd0dd3674ea8ef757a1b41f6b53717a45e821aa) (cherry picked from commit dcd0dd3674ea8ef757a1b41f6b53717a45e821aa)
--- ---
src/tests/features/features.at | 1 + src/tests/features/features.at | 1 +
.../features/iptables_no_flush_on_shutdown.at | 143 ++++++++++++++++++ .../features/iptables_no_flush_on_shutdown.at | 144 ++++++++++++++++++
2 files changed, 144 insertions(+) 2 files changed, 145 insertions(+)
create mode 100644 src/tests/features/iptables_no_flush_on_shutdown.at create mode 100644 src/tests/features/iptables_no_flush_on_shutdown.at
diff --git a/src/tests/features/features.at b/src/tests/features/features.at diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index 381bf6dba0e4..cfe8e88b46a9 100644 index 78fe78c483ad..f59baea1cd70 100644
--- a/src/tests/features/features.at --- a/src/tests/features/features.at
+++ b/src/tests/features/features.at +++ b/src/tests/features/features.at
@@ -14,3 +14,4 @@ m4_include([features/icmp_blocks.at]) @@ -19,3 +19,4 @@ m4_include([features/zone_combine.at])
m4_include([features/rpfilter.at]) m4_include([features/startup_failsafe.at])
m4_include([features/zone_combine.at]) m4_include([features/ipset.at])
m4_include([features/rich_destination_ipset.at]) m4_include([features/reset_defaults.at])
+m4_include([features/iptables_no_flush_on_shutdown.at]) +m4_include([features/iptables_no_flush_on_shutdown.at])
diff --git a/src/tests/features/iptables_no_flush_on_shutdown.at b/src/tests/features/iptables_no_flush_on_shutdown.at diff --git a/src/tests/features/iptables_no_flush_on_shutdown.at b/src/tests/features/iptables_no_flush_on_shutdown.at
new file mode 100644 new file mode 100644
index 000000000000..a3bb1395791d index 000000000000..fbd7c793375c
--- /dev/null --- /dev/null
+++ b/src/tests/features/iptables_no_flush_on_shutdown.at +++ b/src/tests/features/iptables_no_flush_on_shutdown.at
@@ -0,0 +1,143 @@ @@ -0,0 +1,144 @@
+m4_if(nftables, FIREWALL_BACKEND, [ +m4_if(nftables, FIREWALL_BACKEND, [
+ +
+dnl If FirewallBackend=nftables, and there are no --direct rules, then we can +dnl If FirewallBackend=nftables, and there are no --direct rules, then we can
@ -35,6 +35,7 @@ index 000000000000..a3bb1395791d
+dnl first direct rule added. +dnl first direct rule added.
+FWD_START_TEST([avoid iptables flush if using nftables]) +FWD_START_TEST([avoid iptables flush if using nftables])
+AT_KEYWORDS(direct gh863) +AT_KEYWORDS(direct gh863)
+CHECK_IPTABLES()
+ +
+dnl no flush on reload if no direct rules +dnl no flush on reload if no direct rules
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite]) +NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
@ -46,20 +47,20 @@ index 000000000000..a3bb1395791d
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite]) +NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT]) +NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0 + ACCEPT 0 -- ::/0 ::/0
+]) +])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl +EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT + -j ACCEPT
+]) +])
+FWD_RELOAD() +FWD_RELOAD()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0 + ACCEPT 0 -- ::/0 ::/0
+]) +])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl +EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT + -j ACCEPT
@ -68,10 +69,10 @@ index 000000000000..a3bb1395791d
+dnl no flush on restart (or stop) if no direct rules +dnl no flush on restart (or stop) if no direct rules
+FWD_RESTART() +FWD_RESTART()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0 + ACCEPT 0 -- ::/0 ::/0
+]) +])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl +EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT + -j ACCEPT
@ -83,7 +84,7 @@ index 000000000000..a3bb1395791d
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore]) +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore]) +EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite]) +NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT]) +NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
@ -94,10 +95,10 @@ index 000000000000..a3bb1395791d
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite]) +NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT]) +NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0 + ACCEPT 0 -- ::/0 ::/0
+]) +])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl +EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT + -j ACCEPT
@ -126,7 +127,7 @@ index 000000000000..a3bb1395791d
+FWD_RELOAD() +FWD_RELOAD()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore]) +IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl +IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 + ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0
+]) +])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore]) +IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl +IP6TABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
@ -171,5 +172,5 @@ index 000000000000..a3bb1395791d
+ +
+]) +])
-- --
2.43.0 2.39.3

View File

@ -1,157 +0,0 @@
From bba9a6860dd358791d0be3f075718d7cf8dca261 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 23 Feb 2021 09:18:33 -0500
Subject: [PATCH 05/10] v1.0.0: fix(ipset): disallow overlapping entries
These are already being blocked by the ipset backend, but we should
catch them higher up to avoid differences in the backends.
(cherry picked from commit 5b4e8918715a1d2e4abf77ed4eb3252486a19109)
---
src/firewall/client.py | 4 +++-
src/firewall/core/fw_ipset.py | 4 +++-
src/firewall/core/ipset.py | 13 +++++++++++++
src/firewall/server/config_ipset.py | 5 ++++-
src/tests/regression/ipset_netmask_allowed.at | 14 ++++++++------
5 files changed, 31 insertions(+), 9 deletions(-)
diff --git a/src/firewall/client.py b/src/firewall/client.py
index aa6bd7cd282b..3715ffd29316 100644
--- a/src/firewall/client.py
+++ b/src/firewall/client.py
@@ -34,7 +34,7 @@ from firewall.core.base import DEFAULT_ZONE_TARGET, DEFAULT_POLICY_TARGET, DEFAU
from firewall.dbus_utils import dbus_to_python
from firewall.functions import b2u
from firewall.core.rich import Rich_Rule
-from firewall.core.ipset import normalize_ipset_entry
+from firewall.core.ipset import normalize_ipset_entry, check_entry_overlaps_existing
from firewall import errors
from firewall.errors import FirewallError
@@ -1619,6 +1619,7 @@ class FirewallClientIPSetSettings(object):
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
_entries = set()
for _entry in dbus_to_python(entries, list):
+ check_entry_overlaps_existing(_entry, _entries)
_entries.add(normalize_ipset_entry(_entry))
self.settings[5] = list(_entries)
@handle_exceptions
@@ -1628,6 +1629,7 @@ class FirewallClientIPSetSettings(object):
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
entry = normalize_ipset_entry(entry)
if entry not in self.settings[5]:
+ check_entry_overlaps_existing(entry, self.settings[5])
self.settings[5].append(entry)
else:
raise FirewallError(errors.ALREADY_ENABLED, entry)
diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py
index 57e0e6cb51db..711c86a062be 100644
--- a/src/firewall/core/fw_ipset.py
+++ b/src/firewall/core/fw_ipset.py
@@ -25,7 +25,7 @@ __all__ = [ "FirewallIPSet" ]
from firewall.core.logger import log
from firewall.core.ipset import remove_default_create_options as rm_def_cr_opts, \
- normalize_ipset_entry
+ normalize_ipset_entry, check_entry_overlaps_existing
from firewall.core.io.ipset import IPSet
from firewall import errors
from firewall.errors import FirewallError
@@ -196,6 +196,7 @@ class FirewallIPSet(object):
if entry in obj.entries:
raise FirewallError(errors.ALREADY_ENABLED,
"'%s' already is in '%s'" % (entry, name))
+ check_entry_overlaps_existing(entry, obj.entries)
try:
for backend in self.backends():
@@ -243,6 +244,7 @@ class FirewallIPSet(object):
_entries = set()
for _entry in entries:
+ check_entry_overlaps_existing(_entry, _entries)
_entries.add(normalize_ipset_entry(_entry))
entries = list(_entries)
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
index 5bb21856f648..d6defa395241 100644
--- a/src/firewall/core/ipset.py
+++ b/src/firewall/core/ipset.py
@@ -302,3 +302,16 @@ def normalize_ipset_entry(entry):
_entry.append(_part)
return ",".join(_entry)
+
+def check_entry_overlaps_existing(entry, entries):
+ """ Check if entry overlaps any entry in the list of entries """
+ # Only check simple types
+ if len(entry.split(",")) > 1:
+ return
+
+ for itr in entries:
+ try:
+ if ipaddress.ip_network(itr, strict=False).overlaps(ipaddress.ip_network(entry, strict=False)):
+ raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps with existing entry '{}'".format(itr, entry))
+ except ValueError:
+ pass
diff --git a/src/firewall/server/config_ipset.py b/src/firewall/server/config_ipset.py
index 18ef5783de62..f33c2a02926f 100644
--- a/src/firewall/server/config_ipset.py
+++ b/src/firewall/server/config_ipset.py
@@ -33,7 +33,8 @@ from firewall.dbus_utils import dbus_to_python, \
dbus_introspection_prepare_properties, \
dbus_introspection_add_properties
from firewall.core.io.ipset import IPSet
-from firewall.core.ipset import IPSET_TYPES, normalize_ipset_entry
+from firewall.core.ipset import IPSET_TYPES, normalize_ipset_entry, \
+ check_entry_overlaps_existing
from firewall.core.logger import log
from firewall.server.decorators import handle_exceptions, \
dbus_handle_exceptions, dbus_service_method
@@ -408,6 +409,7 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
def setEntries(self, entries, sender=None):
_entries = set()
for _entry in dbus_to_python(entries, list):
+ check_entry_overlaps_existing(_entry, _entries)
_entries.add(normalize_ipset_entry(_entry))
entries = list(_entries)
log.debug1("%s.setEntries('[%s]')", self._log_prefix,
@@ -432,6 +434,7 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
if entry in settings[5]:
raise FirewallError(errors.ALREADY_ENABLED, entry)
+ check_entry_overlaps_existing(entry, settings[5])
settings[5].append(entry)
self.update(settings)
diff --git a/src/tests/regression/ipset_netmask_allowed.at b/src/tests/regression/ipset_netmask_allowed.at
index b5165d94b220..fd08afd3b57c 100644
--- a/src/tests/regression/ipset_netmask_allowed.at
+++ b/src/tests/regression/ipset_netmask_allowed.at
@@ -9,15 +9,17 @@ dnl an add for the whole range. i.e. 1.2.3.4/24 --> 1.2.3.[0.255] (256
dnl entries).
dnl
dnl In nftables, we allow this by using actual intervals.
-FWD_CHECK([--permanent --ipset foobar --add-entry 1.2.3.0/24], 0, [ignore])
-FWD_CHECK([ --ipset foobar --add-entry 1.2.3.0/24], 0, [ignore])
+FWD_CHECK([--permanent --ipset foobar --add-entry 1.2.3.4/24], 0, [ignore])
+FWD_CHECK([ --ipset foobar --add-entry 1.2.3.4/24], 0, [ignore])
dnl check the edge case
FWD_CHECK([--permanent --ipset foobar --add-entry 4.3.2.1/32], 0, [ignore])
FWD_CHECK([ --ipset foobar --add-entry 4.3.2.1/32], 0, [ignore])
-dnl overlaps should be denied by ipset
-FWD_CHECK([ --ipset foobar --add-entry 1.2.3.0/22], 13, [ignore], [ignore])
-FWD_CHECK([ --ipset foobar --add-entry 1.2.3.0/30], 13, [ignore], [ignore])
+dnl overlaps should be denied
+FWD_CHECK([--permanent --ipset foobar --add-entry 1.2.3.0/22], 136, [ignore], [ignore])
+FWD_CHECK([ --ipset foobar --add-entry 1.2.3.0/22], 136, [ignore], [ignore])
+FWD_CHECK([--permanent --ipset foobar --add-entry 1.2.3.4/30], 136, [ignore], [ignore])
+FWD_CHECK([ --ipset foobar --add-entry 1.2.3.4/30], 136, [ignore], [ignore])
-FWD_END_TEST([-e '/ERROR: COMMAND_FAILED:/d'])
+FWD_END_TEST([-e '/ERROR: INVALID_ENTRY:/d'])
--
2.39.1

View File

@ -0,0 +1,53 @@
From a27f6afa21de35aa98e5309430dbcab9e6056f9c Mon Sep 17 00:00:00 2001
From: Pat Riehecky <riehecky@fnal.gov>
Date: Wed, 1 Feb 2023 09:52:43 -0600
Subject: [PATCH 05/22] v2.0.0: feat(service): add OpenTelemetry (OTLP) service
(cherry picked from commit 77c7061cc191bec6d8a36d2666c2d3c3e0ccbb4a)
---
config/Makefile.am | 1 +
config/services/opentelemetry.xml | 7 +++++++
po/POTFILES.in | 1 +
3 files changed, 9 insertions(+)
create mode 100644 config/services/opentelemetry.xml
diff --git a/config/Makefile.am b/config/Makefile.am
index d66398563ff2..47f30c1566e0 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -247,6 +247,7 @@ CONFIG_FILES = \
services/ntp.xml \
services/nut.xml \
services/openvpn.xml \
+ services/opentelemetry.xml \
services/ovirt-imageio.xml \
services/ovirt-storageconsole.xml \
services/ovirt-vmconsole.xml \
diff --git a/config/services/opentelemetry.xml b/config/services/opentelemetry.xml
new file mode 100644
index 000000000000..46c0e5258957
--- /dev/null
+++ b/config/services/opentelemetry.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>OTLP</short>
+ <description>OpenTelemetry Protocol (OTLP) specification describes the encoding, transport, and delivery mechanism of telemetry data between telemetry sources, intermediate nodes such as collectors and telemetry backends.</description>
+ <port protocol="tcp" port="4317"/>
+ <port protocol="tcp" port="4318"/>
+</service>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f3c0595980f9..1c990542ac4d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -180,6 +180,7 @@ config/services/nrpe.xml
config/services/ntp.xml
config/services/nut.xml
config/services/openvpn.xml
+config/services/opentelemetry.xml
config/services/ovirt-imageio.xml
config/services/ovirt-storageconsole.xml
config/services/ovirt-vmconsole.xml
--
2.43.5

View File

@ -1,302 +0,0 @@
From 4779d5bf08ff1c24777df4b88b4af2e8e5918f84 Mon Sep 17 00:00:00 2001
From: Paul Laufer <50234787+refual@users.noreply.github.com>
Date: Fri, 27 Nov 2020 12:23:11 +0100
Subject: [PATCH 06/10] v1.0.0: feat(config): add CleanupModulesOnExit
configuration option
Fixes: rhbz 1520532
Fixes: #533
Closes: #721
(cherry picked from commit 152a51537a7840afd0879ab4b60178bef4ec16a2)
---
config/firewalld.conf | 9 +++++++-
doc/xml/firewalld.conf.xml | 11 ++++++++++
doc/xml/firewalld.dbus.xml | 9 ++++++++
src/firewall/config/__init__.py.in | 1 +
src/firewall/core/fw.py | 29 +++++++++++++++++++-------
src/firewall/core/io/firewalld_conf.py | 19 +++++++++++++----
src/firewall/server/config.py | 23 +++++++++++++-------
src/tests/dbus/firewalld.conf.at | 2 ++
8 files changed, 82 insertions(+), 21 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index a0556c0bbf5b..3abbc9c998c1 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -7,10 +7,17 @@ DefaultZone=public
# Clean up on exit
# If set to no or false the firewall configuration will not get cleaned up
-# on exit or stop of firewalld
+# on exit or stop of firewalld.
# Default: yes
CleanupOnExit=yes
+# Clean up kernel modules on exit
+# If set to yes or true the firewall related kernel modules will be
+# unloaded on exit or stop of firewalld. This might attempt to unload
+# modules not originally loaded by firewalld.
+# Default: no
+CleanupModulesOnExit=no
+
# Lockdown
# If set to enabled, firewall changes with the D-Bus interface will be limited
# to applications that are listed in the lockdown whitelist.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 0a6e8f2fdebf..3ae531bcd94a 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -88,6 +88,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>CleanupModulesOnExit</option></term>
+ <listitem>
+ <para>
+ Setting this option to yes or true unloads all firewall-related
+ kernel modules when firewalld is stopped. The default value is no
+ or false.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>CleanupOnExit</option></term>
<listitem>
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index 1c33ad5ee918..cc4593e1883f 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2798,6 +2798,15 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry id="FirewallD1.config.Properties.CleanupModulesOnExit">
+ <term>CleanupModulesOnExit - s - (rw)</term>
+ <listitem>
+ <para>
+ Setting this option to yes or true unloads all firewall-related
+ kernel modules when firewalld is stopped.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="FirewallD1.config.Properties.CleanupOnExit">
<term>CleanupOnExit - s - (rw)</term>
<listitem>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 0dec7913f694..5d6d769fbf15 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -125,6 +125,7 @@ FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
FALLBACK_ZONE = "public"
FALLBACK_MINIMAL_MARK = 100
FALLBACK_CLEANUP_ON_EXIT = True
+FALLBACK_CLEANUP_MODULES_ON_EXIT = False
FALLBACK_LOCKDOWN = False
FALLBACK_IPV6_RPFILTER = True
FALLBACK_INDIVIDUAL_CALLS = False
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 3eb54e37ab5c..4171697bdb94 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -105,12 +105,13 @@ class Firewall(object):
self.__init_vars()
def __repr__(self):
- return '%s(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)' % \
+ return '%s(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)' % \
(self.__class__, self.ip4tables_enabled, self.ip6tables_enabled,
self.ebtables_enabled, self._state, self._panic,
self._default_zone, self._module_refcount, self._marks,
- self.cleanup_on_exit, self.ipv6_rpfilter_enabled,
- self.ipset_enabled, self._individual_calls, self._log_denied)
+ self.cleanup_on_exit, self.cleanup_modules_on_exit,
+ self.ipv6_rpfilter_enabled, self.ipset_enabled,
+ self._individual_calls, self._log_denied)
def __init_vars(self):
self._state = "INIT"
@@ -120,6 +121,7 @@ class Firewall(object):
self._marks = [ ]
# fallback settings will be overloaded by firewalld.conf
self.cleanup_on_exit = config.FALLBACK_CLEANUP_ON_EXIT
+ self.cleanup_modules_on_exit = config.FALLBACK_CLEANUP_MODULES_ON_EXIT
self.ipv6_rpfilter_enabled = config.FALLBACK_IPV6_RPFILTER
self._individual_calls = config.FALLBACK_INDIVIDUAL_CALLS
self._log_denied = config.FALLBACK_LOG_DENIED
@@ -232,6 +234,13 @@ class Firewall(object):
log.debug1("CleanupOnExit is set to '%s'",
self.cleanup_on_exit)
+ if self._firewalld_conf.get("CleanupModulesOnExit"):
+ value = self._firewalld_conf.get("CleanupModulesOnExit")
+ if value is not None and value.lower() in [ "yes", "true" ]:
+ self.cleanup_modules_on_exit = True
+ log.debug1("CleanupModulesOnExit is set to '%s'",
+ self.cleanup_modules_on_exit)
+
if self._firewalld_conf.get("Lockdown"):
value = self._firewalld_conf.get("Lockdown")
if value is not None and value.lower() in [ "yes", "true" ]:
@@ -667,11 +676,15 @@ class Firewall(object):
self.__init_vars()
def stop(self):
- if self.cleanup_on_exit and not self._offline:
- self.flush()
- self.ipset.flush()
- self.set_policy("ACCEPT")
- self.modules_backend.unload_firewall_modules()
+ if not self._offline:
+ if self.cleanup_on_exit:
+ self.flush()
+ self.ipset.flush()
+ self.set_policy("ACCEPT")
+
+ if self.cleanup_modules_on_exit:
+ log.debug1('Unloading firewall kernel modules')
+ self.modules_backend.unload_firewall_modules()
self.cleanup()
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 7c7092120676..70258400ef06 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -28,10 +28,11 @@ from firewall import config
from firewall.core.logger import log
from firewall.functions import b2u, u2b, PY2
-valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit", "Lockdown",
- "IPv6_rpfilter", "IndividualCalls", "LogDenied",
- "AutomaticHelpers", "FirewallBackend", "FlushAllOnReload",
- "RFC3964_IPv4", "AllowZoneDrifting" ]
+valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
+ "CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
+ "IndividualCalls", "LogDenied", "AutomaticHelpers",
+ "FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
+ "AllowZoneDrifting" ]
class firewalld_conf(object):
def __init__(self, filename):
@@ -75,6 +76,7 @@ class firewalld_conf(object):
self.set("DefaultZone", config.FALLBACK_ZONE)
self.set("MinimalMark", str(config.FALLBACK_MINIMAL_MARK))
self.set("CleanupOnExit", "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no")
+ self.set("CleanupModulesOnExit", "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no")
self.set("Lockdown", "yes" if config.FALLBACK_LOCKDOWN else "no")
self.set("IPv6_rpfilter","yes" if config.FALLBACK_IPV6_RPFILTER else "no")
self.set("IndividualCalls", "yes" if config.FALLBACK_INDIVIDUAL_CALLS else "no")
@@ -135,6 +137,15 @@ class firewalld_conf(object):
config.FALLBACK_CLEANUP_ON_EXIT)
self.set("CleanupOnExit", "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no")
+ # check module cleanup on exit
+ value = self.get("CleanupModulesOnExit")
+ if not value or value.lower() not in [ "no", "false", "yes", "true" ]:
+ if value is not None:
+ log.warning("CleanupModulesOnExit '%s' is not valid, using default "
+ "value %s", value if value else '',
+ config.FALLBACK_CLEANUP_MODULES_ON_EXIT)
+ self.set("CleanupModulesOnExit", "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no")
+
# check lockdown
value = self.get("Lockdown")
if not value or value.lower() not in [ "yes", "true", "no", "false" ]:
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index 031ef5d1afaa..8815920c6893 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -100,6 +100,7 @@ class FirewallDConfig(slip.dbus.service.Object):
dbus_introspection_prepare_properties(self,
config.dbus.DBUS_INTERFACE_CONFIG,
{ "CleanupOnExit": "readwrite",
+ "CleanupModulesOnExit": "readwrite",
"IPv6_rpfilter": "readwrite",
"Lockdown": "readwrite",
"MinimalMark": "readwrite",
@@ -554,9 +555,9 @@ class FirewallDConfig(slip.dbus.service.Object):
@dbus_handle_exceptions
def _get_property(self, prop):
if prop not in [ "DefaultZone", "MinimalMark", "CleanupOnExit",
- "Lockdown", "IPv6_rpfilter", "IndividualCalls",
- "LogDenied", "AutomaticHelpers", "FirewallBackend",
- "FlushAllOnReload", "RFC3964_IPv4",
+ "CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
+ "IndividualCalls", "LogDenied", "AutomaticHelpers",
+ "FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
"AllowZoneDrifting" ]:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
@@ -578,6 +579,10 @@ class FirewallDConfig(slip.dbus.service.Object):
if value is None:
value = "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no"
return dbus.String(value)
+ elif prop == "CleanupModulesOnExit":
+ if value is None:
+ value = "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no"
+ return dbus.String(value)
elif prop == "Lockdown":
if value is None:
value = "yes" if config.FALLBACK_LOCKDOWN else "no"
@@ -623,6 +628,8 @@ class FirewallDConfig(slip.dbus.service.Object):
return dbus.Int32(self._get_property(prop))
elif prop == "CleanupOnExit":
return dbus.String(self._get_property(prop))
+ elif prop == "CleanupModulesOnExit":
+ return dbus.String(self._get_property(prop))
elif prop == "Lockdown":
return dbus.String(self._get_property(prop))
elif prop == "IPv6_rpfilter":
@@ -679,9 +686,9 @@ class FirewallDConfig(slip.dbus.service.Object):
ret = { }
if interface_name == config.dbus.DBUS_INTERFACE_CONFIG:
for x in [ "DefaultZone", "MinimalMark", "CleanupOnExit",
- "Lockdown", "IPv6_rpfilter", "IndividualCalls",
- "LogDenied", "AutomaticHelpers", "FirewallBackend",
- "FlushAllOnReload", "RFC3964_IPv4",
+ "CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
+ "IndividualCalls", "LogDenied", "AutomaticHelpers",
+ "FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
"AllowZoneDrifting" ]:
ret[x] = self._get_property(x)
elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
@@ -706,12 +713,12 @@ class FirewallDConfig(slip.dbus.service.Object):
self.accessCheck(sender)
if interface_name == config.dbus.DBUS_INTERFACE_CONFIG:
- if property_name in [ "CleanupOnExit", "Lockdown",
+ if property_name in [ "CleanupOnExit", "Lockdown", "CleanupModulesOnExit",
"IPv6_rpfilter", "IndividualCalls",
"LogDenied",
"FirewallBackend", "FlushAllOnReload",
"RFC3964_IPv4", "AllowZoneDrifting" ]:
- if property_name in [ "CleanupOnExit", "Lockdown",
+ if property_name in [ "CleanupOnExit", "Lockdown", "CleanupModulesOnExit",
"IPv6_rpfilter", "IndividualCalls" ]:
if new_value.lower() not in [ "yes", "no",
"true", "false" ]:
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 9fc5502a8d0b..9a04a3bd491c 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -17,6 +17,7 @@ dnl Verify defaults over dbus. Should be inline with default firewalld.conf.
DBUS_GETALL([config], [config], 0, [dnl
string "AllowZoneDrifting" : variant string "no"
string "AutomaticHelpers" : variant string "no"
+string "CleanupModulesOnExit" : variant string "no"
string "CleanupOnExit" : variant string "no"
string "DefaultZone" : variant string "public"
string "FirewallBackend" : variant string "nftables"
@@ -45,6 +46,7 @@ _helper([IPv6_rpfilter], [string:"yes"], [variant string "yes"])
_helper([IndividualCalls], [string:"yes"], [variant string "yes"])
_helper([FirewallBackend], [string:"iptables"], [variant string "iptables"])
_helper([FlushAllOnReload], [string:"no"], [variant string "no"])
+_helper([CleanupModulesOnExit], [string:"yes"], [variant string "yes"])
_helper([CleanupOnExit], [string:"yes"], [variant string "yes"])
_helper([RFC3964_IPv4], [string:"no"], [variant string "no"])
_helper([AllowZoneDrifting], [string:"yes"], [variant string "yes"])
--
2.39.1

View File

@ -1,7 +1,7 @@
From b18ab581731a302ddba0428b685360d315293e73 Mon Sep 17 00:00:00 2001 From 6f221d65193cda838e241a18dd07b6da2ae22f78 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com> From: Thomas Haller <thaller@redhat.com>
Date: Wed, 29 Nov 2023 17:02:07 +0100 Date: Wed, 29 Nov 2023 17:02:07 +0100
Subject: [PATCH 21/26] v2.1.0: feat(icmp): add ICMPv6 Multicast Listener Subject: [PATCH 06/22] v2.1.0: feat(icmp): add ICMPv6 Multicast Listener
Discovery (MLD) types Discovery (MLD) types
Note that ip6tables does not support these ICMPv6 types. Currently, Note that ip6tables does not support these ICMPv6 types. Currently,
@ -30,7 +30,7 @@ name as from RFC 4890.
create mode 100644 config/icmptypes/mld2-listener-report.xml create mode 100644 config/icmptypes/mld2-listener-report.xml
diff --git a/config/Makefile.am b/config/Makefile.am diff --git a/config/Makefile.am b/config/Makefile.am
index f844a5a00e2f..a11c6abae583 100644 index 47f30c1566e0..edae25fd9de0 100644
--- a/config/Makefile.am --- a/config/Makefile.am
+++ b/config/Makefile.am +++ b/config/Makefile.am
@@ -83,6 +83,10 @@ CONFIG_FILES = \ @@ -83,6 +83,10 @@ CONFIG_FILES = \
@ -97,7 +97,7 @@ index 000000000000..faee68c95b20
+ <destination ipv6="yes"/> + <destination ipv6="yes"/>
+</icmptype> +</icmptype>
diff --git a/po/POTFILES.in b/po/POTFILES.in diff --git a/po/POTFILES.in b/po/POTFILES.in
index 249cff8d0d2f..3bb71fd3d332 100644 index 1c990542ac4d..adeebdee3f55 100644
--- a/po/POTFILES.in --- a/po/POTFILES.in
+++ b/po/POTFILES.in +++ b/po/POTFILES.in
@@ -15,6 +15,10 @@ config/icmptypes/host-redirect.xml @@ -15,6 +15,10 @@ config/icmptypes/host-redirect.xml
@ -112,7 +112,7 @@ index 249cff8d0d2f..3bb71fd3d332 100644
config/icmptypes/neighbour-solicitation.xml config/icmptypes/neighbour-solicitation.xml
config/icmptypes/network-prohibited.xml config/icmptypes/network-prohibited.xml
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index d238451ebd5d..67fb6457e86c 100644 index 6ad4b9168403..3df3fa3c3742 100644
--- a/src/firewall/core/nftables.py --- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py +++ b/src/firewall/core/nftables.py
@@ -140,6 +140,10 @@ ICMP_TYPES_FRAGMENTS = { @@ -140,6 +140,10 @@ ICMP_TYPES_FRAGMENTS = {
@ -127,5 +127,5 @@ index d238451ebd5d..67fb6457e86c 100644
"neighbour-solicitation": _icmp_types_fragments("icmpv6", "nd-neighbor-solicit"), "neighbour-solicitation": _icmp_types_fragments("icmpv6", "nd-neighbor-solicit"),
"no-route": _icmp_types_fragments("icmpv6", "destination-unreachable", 0), "no-route": _icmp_types_fragments("icmpv6", "destination-unreachable", 0),
-- --
2.43.0 2.43.5

View File

@ -1,95 +0,0 @@
From 82b49bd47d0073f2c2bc4bd296c1a52e4d4d3732 Mon Sep 17 00:00:00 2001
From: Eric Garver <egarver@redhat.com>
Date: Mon, 20 Dec 2021 13:56:55 -0500
Subject: [PATCH 07/10] RHEL only: default to CleanupModulesOnExit=yes
Resolves: rhbz1980206
---
config/firewalld.conf | 4 ++--
doc/xml/firewalld.conf.xml | 4 ++--
src/firewall/config/__init__.py.in | 2 +-
src/firewall/core/fw.py | 2 ++
src/tests/dbus/firewalld.conf.at | 4 ++--
5 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 3abbc9c998c1..c387f87c28be 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -15,8 +15,8 @@ CleanupOnExit=yes
# If set to yes or true the firewall related kernel modules will be
# unloaded on exit or stop of firewalld. This might attempt to unload
# modules not originally loaded by firewalld.
-# Default: no
-CleanupModulesOnExit=no
+# Default: yes
+CleanupModulesOnExit=yes
# Lockdown
# If set to enabled, firewall changes with the D-Bus interface will be limited
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 3ae531bcd94a..c94073dbf84f 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -93,8 +93,8 @@
<listitem>
<para>
Setting this option to yes or true unloads all firewall-related
- kernel modules when firewalld is stopped. The default value is no
- or false.
+ kernel modules when firewalld is stopped. The default value is yes
+ or true.
</para>
</listitem>
</varlistentry>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 5d6d769fbf15..285e2f034b6b 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -125,7 +125,7 @@ FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
FALLBACK_ZONE = "public"
FALLBACK_MINIMAL_MARK = 100
FALLBACK_CLEANUP_ON_EXIT = True
-FALLBACK_CLEANUP_MODULES_ON_EXIT = False
+FALLBACK_CLEANUP_MODULES_ON_EXIT = True
FALLBACK_LOCKDOWN = False
FALLBACK_IPV6_RPFILTER = True
FALLBACK_INDIVIDUAL_CALLS = False
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 4171697bdb94..5cef18b5f889 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -238,6 +238,8 @@ class Firewall(object):
value = self._firewalld_conf.get("CleanupModulesOnExit")
if value is not None and value.lower() in [ "yes", "true" ]:
self.cleanup_modules_on_exit = True
+ if value is not None and value.lower() in [ "no", "false" ]:
+ self.cleanup_modules_on_exit = False
log.debug1("CleanupModulesOnExit is set to '%s'",
self.cleanup_modules_on_exit)
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 9a04a3bd491c..68832bca33bc 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -17,7 +17,7 @@ dnl Verify defaults over dbus. Should be inline with default firewalld.conf.
DBUS_GETALL([config], [config], 0, [dnl
string "AllowZoneDrifting" : variant string "no"
string "AutomaticHelpers" : variant string "no"
-string "CleanupModulesOnExit" : variant string "no"
+string "CleanupModulesOnExit" : variant string "yes"
string "CleanupOnExit" : variant string "no"
string "DefaultZone" : variant string "public"
string "FirewallBackend" : variant string "nftables"
@@ -46,7 +46,7 @@ _helper([IPv6_rpfilter], [string:"yes"], [variant string "yes"])
_helper([IndividualCalls], [string:"yes"], [variant string "yes"])
_helper([FirewallBackend], [string:"iptables"], [variant string "iptables"])
_helper([FlushAllOnReload], [string:"no"], [variant string "no"])
-_helper([CleanupModulesOnExit], [string:"yes"], [variant string "yes"])
+_helper([CleanupModulesOnExit], [string:"no"], [variant string "no"])
_helper([CleanupOnExit], [string:"yes"], [variant string "yes"])
_helper([RFC3964_IPv4], [string:"no"], [variant string "no"])
_helper([AllowZoneDrifting], [string:"yes"], [variant string "yes"])
--
2.39.1

View File

@ -1,7 +1,7 @@
From 5266735bf4827178ddd9a12edc37b1b0a93e0d3a Mon Sep 17 00:00:00 2001 From 22b100b8ac9aeeacae851e2b9f11e4dc1741cd85 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com> From: Thomas Haller <thaller@redhat.com>
Date: Tue, 12 Dec 2023 14:58:07 +0100 Date: Tue, 12 Dec 2023 14:58:07 +0100
Subject: [PATCH 22/26] v2.1.0: fix(rich): validate service name of rich rule Subject: [PATCH 07/22] v2.1.0: fix(rich): validate service name of rich rule
Previously, validation of valid service names was not done. Previously, validation of valid service names was not done.
That meant: That meant:
@ -21,30 +21,20 @@ Now:
https://issues.redhat.com/browse/RHEL-5790 https://issues.redhat.com/browse/RHEL-5790
(cherry picked from commit fbcdddd3e38c31a7b8325bf02764b84344c216b0) (cherry picked from commit fbcdddd3e38c31a7b8325bf02764b84344c216b0)
--- ---
src/firewall/core/io/policy.py | 11 +++++++++++ src/firewall/core/io/policy.py | 8 ++++++++
src/tests/features/rich_rules.at | 8 +++++++- src/tests/features/rich_rules.at | 7 ++++++-
2 files changed, 18 insertions(+), 1 deletion(-) 2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py
index 3b951545e975..514a20251ef4 100644 index 7d383abb0a2d..f9a1114d7969 100644
--- a/src/firewall/core/io/policy.py --- a/src/firewall/core/io/policy.py
+++ b/src/firewall/core/io/policy.py +++ b/src/firewall/core/io/policy.py
@@ -304,6 +304,8 @@ def common_endElement(obj, name): @@ -471,6 +471,14 @@ def common_check_config(obj, config, item, all_config, all_io_objects):
obj._limit_ok = None log.debug1("{} (unsupported)".format(ex))
else:
def common_check_config(obj, config, item, all_config): raise ex
+ obj_type = "Policy" if isinstance(obj, Policy) else "Zone" + elif isinstance(obj_rich.element, rich.Rich_Service):
+ + if obj_rich.element.name not in all_io_objects["services"]:
if item == "services" and obj.fw_config:
existing_services = obj.fw_config.get_services()
for service in config:
@@ -360,6 +362,15 @@ def common_check_config(obj, config, item, all_config):
raise FirewallError(errors.INVALID_ICMPTYPE,
"rich rule family '%s' conflicts with icmp type '%s'" % \
(obj_rich.family, obj_rich.element.name))
+ elif obj.fw_config and isinstance(obj_rich.element, rich.Rich_Service):
+ existing_services = obj.fw_config.get_services()
+ if obj_rich.element.name not in existing_services:
+ raise FirewallError( + raise FirewallError(
+ errors.INVALID_SERVICE, + errors.INVALID_SERVICE,
+ "{} '{}': '{}' not among existing services".format( + "{} '{}': '{}' not among existing services".format(
@ -55,28 +45,27 @@ index 3b951545e975..514a20251ef4 100644
def common_writer(obj, handler): def common_writer(obj, handler):
# short # short
diff --git a/src/tests/features/rich_rules.at b/src/tests/features/rich_rules.at diff --git a/src/tests/features/rich_rules.at b/src/tests/features/rich_rules.at
index bb5e4b72a516..de98bf0ce268 100644 index aadc76da57b4..f7d1a1d0abf4 100644
--- a/src/tests/features/rich_rules.at --- a/src/tests/features/rich_rules.at
+++ b/src/tests/features/rich_rules.at +++ b/src/tests/features/rich_rules.at
@@ -46,6 +46,11 @@ FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priorit @@ -46,6 +46,10 @@ FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priorit
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=0 source address=10.10.10.13 drop'], 0, ignore) FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=0 source address=10.10.10.13 drop'], 0, ignore)
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=-1 source address=10.10.10.14 accept'], 0, ignore) FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=-1 source address=10.10.10.14 accept'], 0, ignore)
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=1 source address=10.10.10.15 accept'], 0, ignore) FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=1 source address=10.10.10.15 accept'], 0, ignore)
+ +
+dnl Invalid service name is rejected. +dnl Invalid service name is rejected.
+FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="bogusservice" accept'], 101, ignore, ignore) +FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="bogusservice" accept'], 101, ignore, ignore)
+FWD_CHECK([--policy foobar --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="bogusservice" accept'], 101, ignore, ignore)
+ +
FWD_RELOAD FWD_RELOAD
NFT_LIST_RULES([inet], [filter_IN_policy_foobar_pre], 0, [dnl NFT_LIST_RULES([inet], [filter_IN_policy_foobar_pre], 0, [dnl
table inet firewalld { table inet firewalld {
@@ -289,4 +294,5 @@ IP6TABLES_LIST_RULES([filter], [IN_foobar_post], 0, [dnl @@ -319,4 +323,5 @@ IP6TABLES_LIST_RULES([filter], [IN_foobar_post], 0, [dnl
ACCEPT all ::/0 ::/0 ACCEPT 0 -- ::/0 ::/0
]) ])
-FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d']) -FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d'])
+FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d' dnl +FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d' dnl
+ -e "/ERROR: INVALID_SERVICE: Policy 'foobar': 'bogusservice' not among existing services/d"]) + -e "/ERROR: INVALID_SERVICE: Policy 'foobar': 'bogusservice' not among existing services/d"])
-- --
2.43.0 2.43.5

View File

@ -1,141 +0,0 @@
From ae057df0222e6e1dd1556436fad93b669da8f653 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 30 Nov 2021 14:54:20 -0500
Subject: [PATCH 08/10] v1.1.0: fix(ipset): reduce cost of entry overlap
detection
This increases peak memory usage to reduce the duration it takes to
apply the set entries. Building the list of IPv4Network objects up front
means we don't have to build them multiple times inside the for loop.
Fixes: #881
(cherry picked from commit 7f5b736378c0133f46470c42e0c1fb3b95087de5)
---
src/firewall/client.py | 10 ++++------
src/firewall/core/fw_ipset.py | 9 +++------
src/firewall/core/ipset.py | 27 ++++++++++++++++++++++-----
src/firewall/server/config_ipset.py | 10 ++++------
4 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/src/firewall/client.py b/src/firewall/client.py
index 3715ffd29316..fdc88ac7946b 100644
--- a/src/firewall/client.py
+++ b/src/firewall/client.py
@@ -34,7 +34,8 @@ from firewall.core.base import DEFAULT_ZONE_TARGET, DEFAULT_POLICY_TARGET, DEFAU
from firewall.dbus_utils import dbus_to_python
from firewall.functions import b2u
from firewall.core.rich import Rich_Rule
-from firewall.core.ipset import normalize_ipset_entry, check_entry_overlaps_existing
+from firewall.core.ipset import normalize_ipset_entry, check_entry_overlaps_existing, \
+ check_for_overlapping_entries
from firewall import errors
from firewall.errors import FirewallError
@@ -1617,11 +1618,8 @@ class FirewallClientIPSetSettings(object):
if "timeout" in self.settings[4] and \
self.settings[4]["timeout"] != "0":
raise FirewallError(errors.IPSET_WITH_TIMEOUT)
- _entries = set()
- for _entry in dbus_to_python(entries, list):
- check_entry_overlaps_existing(_entry, _entries)
- _entries.add(normalize_ipset_entry(_entry))
- self.settings[5] = list(_entries)
+ check_for_overlapping_entries(entries)
+ self.settings[5] = entries
@handle_exceptions
def addEntry(self, entry):
if "timeout" in self.settings[4] and \
diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py
index 711c86a062be..d4bf99eaadcc 100644
--- a/src/firewall/core/fw_ipset.py
+++ b/src/firewall/core/fw_ipset.py
@@ -25,7 +25,8 @@ __all__ = [ "FirewallIPSet" ]
from firewall.core.logger import log
from firewall.core.ipset import remove_default_create_options as rm_def_cr_opts, \
- normalize_ipset_entry, check_entry_overlaps_existing
+ normalize_ipset_entry, check_entry_overlaps_existing, \
+ check_for_overlapping_entries
from firewall.core.io.ipset import IPSet
from firewall import errors
from firewall.errors import FirewallError
@@ -242,11 +243,7 @@ class FirewallIPSet(object):
def set_entries(self, name, entries):
obj = self.get_ipset(name, applied=True)
- _entries = set()
- for _entry in entries:
- check_entry_overlaps_existing(_entry, _entries)
- _entries.add(normalize_ipset_entry(_entry))
- entries = list(_entries)
+ check_for_overlapping_entries(entries)
for entry in entries:
IPSet.check_entry(entry, obj.options, obj.type)
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
index d6defa395241..66ea4335536d 100644
--- a/src/firewall/core/ipset.py
+++ b/src/firewall/core/ipset.py
@@ -309,9 +309,26 @@ def check_entry_overlaps_existing(entry, entries):
if len(entry.split(",")) > 1:
return
+ try:
+ entry_network = ipaddress.ip_network(entry, strict=False)
+ except ValueError:
+ # could not parse the new IP address, maybe a MAC
+ return
+
for itr in entries:
- try:
- if ipaddress.ip_network(itr, strict=False).overlaps(ipaddress.ip_network(entry, strict=False)):
- raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps with existing entry '{}'".format(itr, entry))
- except ValueError:
- pass
+ if entry_network.overlaps(ipaddress.ip_network(itr, strict=False)):
+ raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps with existing entry '{}'".format(entry, itr))
+
+def check_for_overlapping_entries(entries):
+ """ Check if any entry overlaps any entry in the list of entries """
+ try:
+ entries = [ipaddress.ip_network(x, strict=False) for x in entries]
+ except ValueError:
+ # at least one entry can not be parsed
+ return
+
+ while entries:
+ entry = entries.pop()
+ for itr in entries:
+ if entry.overlaps(itr):
+ raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps entry '{}'".format(entry, itr))
diff --git a/src/firewall/server/config_ipset.py b/src/firewall/server/config_ipset.py
index f33c2a02926f..499ffcb9227a 100644
--- a/src/firewall/server/config_ipset.py
+++ b/src/firewall/server/config_ipset.py
@@ -34,7 +34,8 @@ from firewall.dbus_utils import dbus_to_python, \
dbus_introspection_add_properties
from firewall.core.io.ipset import IPSet
from firewall.core.ipset import IPSET_TYPES, normalize_ipset_entry, \
- check_entry_overlaps_existing
+ check_entry_overlaps_existing, \
+ check_for_overlapping_entries
from firewall.core.logger import log
from firewall.server.decorators import handle_exceptions, \
dbus_handle_exceptions, dbus_service_method
@@ -407,11 +408,8 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
in_signature='as')
@dbus_handle_exceptions
def setEntries(self, entries, sender=None):
- _entries = set()
- for _entry in dbus_to_python(entries, list):
- check_entry_overlaps_existing(_entry, _entries)
- _entries.add(normalize_ipset_entry(_entry))
- entries = list(_entries)
+ entries = dbus_to_python(entries, list)
+ check_for_overlapping_entries(entries)
log.debug1("%s.setEntries('[%s]')", self._log_prefix,
",".join(entries))
self.parent.accessCheck(sender)
--
2.39.1

View File

@ -0,0 +1,66 @@
From 11ee9b9ed8da78bfc11edffc2c9386efa41be1cf Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 18 Dec 2023 18:22:38 -0500
Subject: [PATCH 08/22] v2.1.0: improvement(nftables): do not track rule
handles for policy table
It's not necessary. This table is transient and we simply delete the
entire table when we're done with it.
(cherry picked from commit 119dff1d86f841cd2f33ddbab278bc9257dae7b0)
---
src/firewall/core/nftables.py | 24 +++++++-----------------
1 file changed, 7 insertions(+), 17 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 3df3fa3c3742..690a5dc067ab 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -386,6 +386,11 @@ class nftables(object):
if verb not in output["nftables"][index]:
continue
+ # don't bother tracking handles for the policy table as we simply
+ # delete the entire table.
+ if TABLE_NAME_POLICY == output["nftables"][index][verb]["rule"]["table"]:
+ continue
+
self.rule_to_handle[rule_key] = output["nftables"][index][verb]["rule"]["handle"]
def set_rule(self, rule, log_denied):
@@ -408,18 +413,8 @@ class nftables(object):
"name": table}}}]
def build_flush_rules(self):
- # Policy is stashed in a separate table that we're _not_ going to
- # flush. As such, we retain the policy rule handles and ref counts.
- saved_rule_to_handle = {}
- saved_rule_ref_count = {}
- for rule in self._build_set_policy_rules_ct_rules(True):
- policy_key = self._get_rule_key(rule)
- if policy_key in self.rule_to_handle:
- saved_rule_to_handle[policy_key] = self.rule_to_handle[policy_key]
- saved_rule_ref_count[policy_key] = self.rule_ref_count[policy_key]
-
- self.rule_to_handle = saved_rule_to_handle
- self.rule_ref_count = saved_rule_ref_count
+ self.rule_to_handle = {}
+ self.rule_ref_count = {}
self.rich_rule_priority_counts = {}
self.policy_priority_counts = {}
self.zone_source_index_cache = {}
@@ -475,11 +470,6 @@ class nftables(object):
rules += self._build_set_policy_rules_ct_rules(True)
elif policy == "ACCEPT":
- for rule in self._build_set_policy_rules_ct_rules(False):
- policy_key = self._get_rule_key(rule)
- if policy_key in self.rule_to_handle:
- rules.append(rule)
-
rules += self._build_delete_table_rules(TABLE_NAME_POLICY)
else:
raise FirewallError(UNKNOWN_ERROR, "not implemented")
--
2.43.5

View File

@ -1,56 +0,0 @@
From 885d308c1457e9ea0d839d852dd98a1c134b448c Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 30 Nov 2021 14:50:17 -0500
Subject: [PATCH 09/10] v1.1.0: test(ipset): huge set of entries benchmark
Coverage: #881
(cherry picked from commit 114936c71ab1b12a5598d06805b7e9e13f7ee190)
---
src/tests/regression/gh881.at | 25 +++++++++++++++++++++++++
src/tests/regression/regression.at | 1 +
2 files changed, 26 insertions(+)
create mode 100644 src/tests/regression/gh881.at
diff --git a/src/tests/regression/gh881.at b/src/tests/regression/gh881.at
new file mode 100644
index 000000000000..c7326805b555
--- /dev/null
+++ b/src/tests/regression/gh881.at
@@ -0,0 +1,25 @@
+FWD_START_TEST([ipset entry overlap detect perf])
+AT_KEYWORDS(ipset gh881)
+
+dnl build a large ipset
+dnl
+AT_DATA([./deny_cidr], [])
+NS_CHECK([sh -c '
+for I in $(seq 10); do
+ for J in $(seq 250); do
+ echo "10.${I}.${J}.0/24" >> ./deny_cidr
+ done
+done
+'])
+
+dnl verify non-overlapping does not error
+dnl
+FWD_CHECK([--permanent --new-ipset=deny_set --type=hash:net --option=family=inet --option=hashsize=16384 --option=maxelem=20000], 0, [ignore])
+NS_CHECK([time timeout 300 firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 0, [ignore], [ignore])
+
+dnl verify overlap detection actually detects an overlap
+dnl
+NS_CHECK([echo "10.1.0.0/16" >> ./deny_cidr])
+NS_CHECK([time timeout 300 firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+
+FWD_END_TEST()
diff --git a/src/tests/regression/regression.at b/src/tests/regression/regression.at
index 104f784cbe93..143298d3235f 100644
--- a/src/tests/regression/regression.at
+++ b/src/tests/regression/regression.at
@@ -50,3 +50,4 @@ m4_include([regression/gh874.at])
m4_include([regression/service_includes_for_builtin.at])
m4_include([regression/rhbz2181406.at])
m4_include([regression/ipset_scale.at])
+m4_include([regression/gh881.at])
--
2.39.1

View File

@ -0,0 +1,203 @@
From c53dabcb9ca5c6d9ab2b076d961127a67afe8f8f Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Fri, 11 Aug 2023 18:16:20 +0200
Subject: [PATCH 09/22] v2.1.0: improvement(fw): make set_policy("DROP") more
flexible
We will add a reload-policy via
ReloadPolicy=OUTPUT:{ACCEPT,REJECT,DROP},INPUT:{ACCEPT,REJECT,DROP},FORWARD:{ACCEPT,REJECT,DROP}
Extend set_policy() so that the "DROP" policy can be overridden.
(cherry picked from commit e3bb468ff469373d193398b471a59f7ab7d29f27)
---
src/firewall/core/ebtables.py | 2 +-
src/firewall/core/fw.py | 27 +++++++++++++---
src/firewall/core/ipXtables.py | 11 +++++--
src/firewall/core/nftables.py | 56 ++++++++++++++++++++++++----------
4 files changed, 72 insertions(+), 24 deletions(-)
diff --git a/src/firewall/core/ebtables.py b/src/firewall/core/ebtables.py
index c1c0b8587a5c..f059975724a5 100644
--- a/src/firewall/core/ebtables.py
+++ b/src/firewall/core/ebtables.py
@@ -237,7 +237,7 @@ class ebtables(object):
rules.append(["-t", table, flag])
return rules
- def build_set_policy_rules(self, policy):
+ def build_set_policy_rules(self, policy, policy_details):
rules = []
_policy = "DROP" if policy == "PANIC" else policy
for table in BUILT_IN_CHAINS.keys():
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index f1bc124b9443..ccec875f3c3c 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -983,7 +983,18 @@ class Firewall(object):
if use_transaction is None:
transaction.execute(True)
- def set_policy(self, policy, use_transaction=None):
+ def _set_policy_build_rules(self, backend, policy, policy_details=None):
+ assert policy in ("ACCEPT", "DROP", "PANIC")
+ if policy_details is None:
+ dp = "ACCEPT" if policy == "ACCEPT" else "DROP"
+ policy_details = {
+ "INPUT": dp,
+ "OUTPUT": dp,
+ "FORWARD": dp,
+ }
+ return backend.build_set_policy_rules(policy, policy_details)
+
+ def set_policy(self, policy, policy_details=None, use_transaction=None):
if use_transaction is None:
transaction = FirewallTransaction(self)
else:
@@ -992,7 +1003,7 @@ class Firewall(object):
log.debug1("Setting policy to '%s'", policy)
for backend in self.enabled_backends():
- rules = backend.build_set_policy_rules(policy)
+ rules = self._set_policy_build_rules(backend, policy, policy_details)
transaction.add_rules(backend, rules)
if use_transaction is None:
@@ -1224,13 +1235,19 @@ class Firewall(object):
# for the old backend that was set to DROP above.
if not self._panic and old_firewall_backend != self._firewall_backend:
if old_firewall_backend == "nftables":
- for rule in self.nftables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.nftables_backend, "ACCEPT"
+ ):
self.nftables_backend.set_rule(rule, self._log_denied)
else:
- for rule in self.ip4tables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.ip4tables_backend, "ACCEPT"
+ ):
self.ip4tables_backend.set_rule(rule, self._log_denied)
if self.ip6tables_enabled:
- for rule in self.ip6tables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.ip6tables_backend, "ACCEPT"
+ ):
self.ip6tables_backend.set_rule(rule, self._log_denied)
if start_exception:
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index e05a2bd4d7ed..1a0cea7a3b4e 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -578,7 +578,7 @@ class ip4tables(object):
rules.append(["-t", table, flag])
return rules
- def build_set_policy_rules(self, policy):
+ def build_set_policy_rules(self, policy, policy_details):
rules = []
_policy = "DROP" if policy == "PANIC" else policy
for table in BUILT_IN_CHAINS.keys():
@@ -587,7 +587,14 @@ class ip4tables(object):
if table == "nat":
continue
for chain in BUILT_IN_CHAINS[table]:
- rules.append(["-t", table, "-P", chain, _policy])
+ if table == "filter":
+ p = policy_details[chain]
+ if p == "REJECT":
+ rules.append(["-t", table, "-A", chain, "-j", "REJECT"])
+ p = "DROP"
+ else:
+ p = _policy
+ rules.append(["-t", table, "-P", chain, p])
return rules
def supported_icmp_types(self, ipv=None):
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 690a5dc067ab..e9816147ef8e 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -421,20 +421,17 @@ class nftables(object):
return self._build_delete_table_rules(TABLE_NAME)
- def _build_set_policy_rules_ct_rules(self, enable):
+ def _build_set_policy_rules_ct_rule(self, enable, hook):
add_del = { True: "add", False: "delete" }[enable]
- rules = []
- for hook in ["input", "forward", "output"]:
- rules.append({add_del: {"rule": {"family": "inet",
- "table": TABLE_NAME_POLICY,
- "chain": "%s_%s" % ("filter", hook),
- "expr": [{"match": {"left": {"ct": {"key": "state"}},
- "op": "in",
- "right": {"set": ["established", "related"]}}},
- {"accept": None}]}}})
- return rules
-
- def build_set_policy_rules(self, policy):
+ return {add_del: {"rule": {"family": "inet",
+ "table": TABLE_NAME_POLICY,
+ "chain": "%s_%s" % ("filter", hook),
+ "expr": [{"match": {"left": {"ct": {"key": "state"}},
+ "op": "in",
+ "right": {"set": ["established", "related"]}}},
+ {"accept": None}]}}}
+
+ def build_set_policy_rules(self, policy, policy_details):
# Policy is not exposed to the user. It's only to make sure we DROP
# packets while reloading and for panic mode. As such, using hooks with
# a higher priority than our base chains is sufficient.
@@ -459,16 +456,43 @@ class nftables(object):
# To drop everything except existing connections we use
# "filter" because it occurs _after_ conntrack.
- for hook in ["input", "forward", "output"]:
+ for hook in ("INPUT", "FORWARD", "OUTPUT"):
+ d_policy = policy_details[hook]
+ assert d_policy in ("ACCEPT", "REJECT", "DROP")
+ hook = hook.lower()
+ chain_name = f"filter_{hook}"
+
rules.append({"add": {"chain": {"family": "inet",
"table": TABLE_NAME_POLICY,
- "name": "%s_%s" % ("filter", hook),
+ "name": chain_name,
"type": "filter",
"hook": hook,
"prio": 0 + NFT_HOOK_OFFSET - 1,
"policy": "drop"}}})
- rules += self._build_set_policy_rules_ct_rules(True)
+ rules.append(self._build_set_policy_rules_ct_rule(True, hook))
+
+ if d_policy == "ACCEPT":
+ expr_fragment = {"accept": None}
+ elif d_policy == "DROP":
+ expr_fragment = {"drop": None}
+ else:
+ expr_fragment = {
+ "reject": {"type": "icmpx", "expr": "admin-prohibited"}
+ }
+
+ rules.append(
+ {
+ "add": {
+ "rule": {
+ "family": "inet",
+ "table": TABLE_NAME_POLICY,
+ "chain": chain_name,
+ "expr": [expr_fragment],
+ }
+ }
+ }
+ )
elif policy == "ACCEPT":
rules += self._build_delete_table_rules(TABLE_NAME_POLICY)
else:
--
2.43.5

View File

@ -1,150 +0,0 @@
From d8d6d313acd50aa1c87c42fb7a7334b01c516227 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 25 Jan 2022 09:29:32 -0500
Subject: [PATCH 10/10] v1.1.0: fix(ipset): further reduce cost of entry
overlap detection
This makes the complexity linear by sorting the networks ahead of time.
Fixes: #881
Fixes: rhbz2043289
(cherry picked from commit 36c170db265265e838a089858be4b20dbbd582eb)
---
src/firewall/core/ipset.py | 59 ++++++++++++++++++++++++++++++++---
src/tests/regression/gh881.at | 42 ++++++++++++++++++++++---
2 files changed, 92 insertions(+), 9 deletions(-)
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
index 66ea4335536d..b160d8345669 100644
--- a/src/firewall/core/ipset.py
+++ b/src/firewall/core/ipset.py
@@ -327,8 +327,57 @@ def check_for_overlapping_entries(entries):
# at least one entry can not be parsed
return
- while entries:
- entry = entries.pop()
- for itr in entries:
- if entry.overlaps(itr):
- raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps entry '{}'".format(entry, itr))
+ # We can take advantage of some facts of IPv4Network/IPv6Network and
+ # how Python sorts the networks to quickly detect overlaps.
+ #
+ # Facts:
+ #
+ # 1. IPv{4,6}Network are normalized to remove host bits, e.g.
+ # 10.1.1.0/16 will become 10.1.0.0/16.
+ #
+ # 2. IPv{4,6}Network objects are sorted by:
+ # a. IP address (network bits)
+ # then
+ # b. netmask (significant bits count)
+ #
+ # Because of the above we have these properties:
+ #
+ # 1. big networks (netA) are sorted before smaller networks (netB)
+ # that overlap the big network (netA)
+ # - e.g. 10.1.128.0/17 (netA) sorts before 10.1.129.0/24 (netB)
+ # 2. same value addresses (network bits) are grouped together even
+ # if the number of network bits vary. e.g. /16 vs /24
+ # - recall that address are normalized to remove host bits
+ # - e.g. 10.1.128.0/17 (netA) sorts before 10.1.128.0/24 (netC)
+ # 3. non-overlapping networks (netD, netE) are always sorted before or
+ # after networks that overlap (netB, netC) the current one (netA)
+ # - e.g. 10.1.128.0/17 (netA) sorts before 10.2.128.0/16 (netD)
+ # - e.g. 10.1.128.0/17 (netA) sorts after 9.1.128.0/17 (netE)
+ # - e.g. 9.1.128.0/17 (netE) sorts before 10.1.129.0/24 (netB)
+ #
+ # With this we know the sorted list looks like:
+ #
+ # list: [ netE, netA, netB, netC, netD ]
+ #
+ # netE = non-overlapping network
+ # netA = big network
+ # netB = smaller network that overlaps netA (subnet)
+ # netC = smaller network that overlaps netA (subnet)
+ # netD = non-overlapping network
+ #
+ # If networks netB and netC exist in the list, they overlap and are
+ # adjacent to netA.
+ #
+ # Checking for overlaps on a sorted list is thus:
+ #
+ # 1. compare adjacent elements in the list for overlaps
+ #
+ # Recall that we only need to detect a single overlap. We do not need to
+ # detect them all.
+ #
+ entries.sort()
+ prev_network = entries.pop(0)
+ for current_network in entries:
+ if prev_network.overlaps(current_network):
+ raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps entry '{}'".format(prev_network, current_network))
+ prev_network = current_network
diff --git a/src/tests/regression/gh881.at b/src/tests/regression/gh881.at
index c7326805b555..a5cf7e4eb912 100644
--- a/src/tests/regression/gh881.at
+++ b/src/tests/regression/gh881.at
@@ -5,21 +5,55 @@ dnl build a large ipset
dnl
AT_DATA([./deny_cidr], [])
NS_CHECK([sh -c '
-for I in $(seq 10); do
+for I in $(seq 250); do
for J in $(seq 250); do
echo "10.${I}.${J}.0/24" >> ./deny_cidr
done
done
'])
+NS_CHECK([echo "10.254.0.0/16" >> ./deny_cidr])
dnl verify non-overlapping does not error
dnl
FWD_CHECK([--permanent --new-ipset=deny_set --type=hash:net --option=family=inet --option=hashsize=16384 --option=maxelem=20000], 0, [ignore])
-NS_CHECK([time timeout 300 firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 0, [ignore], [ignore])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 0, [ignore], [ignore])
+
+dnl still no overlap
+dnl
+AT_DATA([./deny_cidr], [
+9.0.0.0/8
+11.1.0.0/16
+])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 0, [ignore], [ignore])
dnl verify overlap detection actually detects an overlap
dnl
-NS_CHECK([echo "10.1.0.0/16" >> ./deny_cidr])
-NS_CHECK([time timeout 300 firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+AT_DATA([./deny_cidr], [
+10.1.0.0/16
+10.2.0.0/16
+10.250.0.0/16
+])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+
+AT_DATA([./deny_cidr], [
+10.253.0.0/16
+10.253.128.0/17
+])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+
+AT_DATA([./deny_cidr], [
+10.1.1.1/32
+])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+
+AT_DATA([./deny_cidr], [
+10.0.0.0/8
+10.0.0.0/25
+])
+NS_CHECK([time firewall-cmd --permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 136, [ignore], [ignore])
+
+dnl empty file, no additions, but previous ones will remain
+AT_DATA([./deny_cidr], [])
+FWD_CHECK([--permanent --ipset=deny_set --add-entries-from-file=./deny_cidr], 0, [ignore], [ignore])
FWD_END_TEST()
--
2.39.1

View File

@ -0,0 +1,303 @@
From 67c8a0010ba6244c40e48a93560eb66d91a2ca09 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Mon, 14 Aug 2023 14:53:55 +0200
Subject: [PATCH 10/22] v2.1.0: feat(fw): add ReloadPolicy option in
firewalld.conf
One interesting aspect is that during `firewall-cmd --reload`, the code
first sets the policy to "DROP", before reloading "firewalld.conf". That
means, changing the value only takes effect after the next reload. But
that seems expected as we set the policy before starting to reload.
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2149039
(cherry picked from commit 0019371a8f42d376ac9cce79cc5e1e7d2049f021)
---
config/firewalld.conf | 8 +++
doc/xml/firewalld.conf.xml | 16 ++++++
src/firewall/config/__init__.py.in | 1 +
src/firewall/core/fw.py | 13 ++++-
src/firewall/core/io/firewalld_conf.py | 56 ++++++++++++++++++++-
src/tests/features/features.at | 1 +
src/tests/features/reloadpolicy.at | 12 +++++
src/tests/unit/test_firewalld_conf.py | 68 ++++++++++++++++++++++++++
8 files changed, 172 insertions(+), 3 deletions(-)
create mode 100644 src/tests/features/reloadpolicy.at
create mode 100644 src/tests/unit/test_firewalld_conf.py
diff --git a/config/firewalld.conf b/config/firewalld.conf
index f8caf11c8a86..7a0be1ff1b76 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -66,6 +66,14 @@ FirewallBackend=nftables
# Default: yes
FlushAllOnReload=yes
+# ReloadPolicy
+# Policy during reload. By default all traffic except for established
+# connections is dropped while the rules are updated. Set to "DROP", "REJECT"
+# or "ACCEPT". Alternatively, specify it per table, like
+# "OUTPUT:ACCEPT,INPUT:DROP,FORWARD:REJECT".
+# Default: ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
+ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
+
# RFC3964_IPv4
# As per RFC 3964, filter IPv6 traffic with 6to4 destination addresses that
# correspond to IPv4 addresses that should not be routed over the public
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index e4312acc8e1c..022569ccf502 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -195,6 +195,22 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>ReloadPolicy</option></term>
+ <listitem>
+ <para>
+ The policy during reload. By default, all traffic except
+ established connections is dropped while reloading the
+ firewall rules. This can be overridden for INPUT, FORWARD
+ and OUTPUT. The accepted values are "DROP", "REJECT" and
+ "ACCEPT", which then applies to all tables. Alternatively,
+ the policy can be specified per table, like
+ "INPUT:REJECT,FORWARD:DROP,OUTPUT:ACCEPT".
+ Defaults to "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP".
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>RFC3964_IPv4</option></term>
<listitem>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index d982384a0382..da1e31e10e58 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -133,5 +133,6 @@ FALLBACK_LOG_DENIED = "off"
FALLBACK_AUTOMATIC_HELPERS = "no"
FALLBACK_FIREWALL_BACKEND = "nftables"
FALLBACK_FLUSH_ALL_ON_RELOAD = True
+FALLBACK_RELOAD_POLICY = "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP"
FALLBACK_RFC3964_IPV4 = True
FALLBACK_ALLOW_ZONE_DRIFTING = False
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index ccec875f3c3c..ac13be122b66 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -1000,7 +1000,13 @@ class Firewall(object):
else:
transaction = use_transaction
- log.debug1("Setting policy to '%s'", policy)
+ log.debug1(
+ "Setting policy to '%s'%s",
+ policy,
+ f" (ReloadPolicy={firewalld_conf._unparse_reload_policy(policy_details)})"
+ if policy == "DROP"
+ else "",
+ )
for backend in self.enabled_backends():
rules = self._set_policy_build_rules(backend, policy, policy_details)
@@ -1146,7 +1152,10 @@ class Firewall(object):
_ipset_objs.append(self.ipset.get_ipset(_name))
if not _panic:
- self.set_policy("DROP")
+ reload_policy = firewalld_conf._parse_reload_policy(
+ self._firewalld_conf.get("ReloadPolicy")
+ )
+ self.set_policy("DROP", policy_details=reload_policy)
self.flush()
self.cleanup()
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index b907c5b1e60b..d2879b319d1f 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -31,7 +31,7 @@ valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]
+ "AllowZoneDrifting", "ReloadPolicy" ]
class firewalld_conf(object):
def __init__(self, filename):
@@ -77,6 +77,7 @@ class firewalld_conf(object):
self.set("AutomaticHelpers", config.FALLBACK_AUTOMATIC_HELPERS)
self.set("FirewallBackend", config.FALLBACK_FIREWALL_BACKEND)
self.set("FlushAllOnReload", "yes" if config.FALLBACK_FLUSH_ALL_ON_RELOAD else "no")
+ self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
@@ -208,6 +209,17 @@ class firewalld_conf(object):
config.FALLBACK_FLUSH_ALL_ON_RELOAD)
self.set("FlushAllOnReload", str(config.FALLBACK_FLUSH_ALL_ON_RELOAD))
+ value = self.get("ReloadPolicy")
+ try:
+ value = self._parse_reload_policy(value)
+ except ValueError:
+ log.warning(
+ "ReloadPolicy '%s' is not valid, using default value '%s'",
+ value,
+ config.FALLBACK_RELOAD_POLICY,
+ )
+ self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
+
value = self.get("RFC3964_IPv4")
if not value or value.lower() not in [ "yes", "true", "no", "false" ]:
if value is not None:
@@ -330,3 +342,45 @@ class firewalld_conf(object):
raise IOError("Failed to create '%s': %s" % (self.filename, msg))
else:
os.chmod(self.filename, 0o600)
+
+ @staticmethod
+ def _parse_reload_policy(value):
+ valid = True
+ result = {
+ "INPUT": "DROP",
+ "FORWARD": "DROP",
+ "OUTPUT": "DROP",
+ }
+ if value:
+ value = value.strip()
+ v = value.upper()
+ if v in ("ACCEPT", "REJECT", "DROP"):
+ for k in result:
+ result[k] = v
+ else:
+ for a in value.replace(";", ",").split(","):
+ a = a.strip()
+ if not a:
+ continue
+ a2 = a.replace("=", ":").split(":", 2)
+ if len(a2) != 2:
+ valid = False
+ continue
+ k = a2[0].strip().upper()
+ if k not in result:
+ valid = False
+ continue
+ v = a2[1].strip().upper()
+ if v not in ("ACCEPT", "REJECT", "DROP"):
+ valid = False
+ continue
+ result[k] = v
+
+ if not valid:
+ raise ValueError("Invalid ReloadPolicy")
+
+ return result
+
+ @staticmethod
+ def _unparse_reload_policy(value):
+ return ",".join(f"{k}:{v}" for k, v in value.items())
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index f59baea1cd70..065cb2872e88 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -20,3 +20,4 @@ m4_include([features/startup_failsafe.at])
m4_include([features/ipset.at])
m4_include([features/reset_defaults.at])
m4_include([features/iptables_no_flush_on_shutdown.at])
+m4_include([features/reloadpolicy.at])
diff --git a/src/tests/features/reloadpolicy.at b/src/tests/features/reloadpolicy.at
new file mode 100644
index 000000000000..fea1aa26aab4
--- /dev/null
+++ b/src/tests/features/reloadpolicy.at
@@ -0,0 +1,12 @@
+FWD_START_TEST([check ReloadPolicy])
+AT_KEYWORDS(reloadpolicy rhbz2149039)
+
+AT_CHECK([sed -i 's/^ReloadPolicy=.*/ReloadPolicy=INPUT:REJECT,FORWARD:ACCEPT/' ./firewalld.conf])
+dnl call RELOAD twice, to see more action about the ReloadPolicy.
+FWD_RELOAD()
+FWD_RELOAD()
+
+AT_CHECK([sed -i 's/^ReloadPolicy=.*/ReloadPolicy=REJECT/' ./firewalld.conf])
+FWD_RELOAD()
+
+FWD_END_TEST()
diff --git a/src/tests/unit/test_firewalld_conf.py b/src/tests/unit/test_firewalld_conf.py
new file mode 100644
index 000000000000..0ce1fd279f91
--- /dev/null
+++ b/src/tests/unit/test_firewalld_conf.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import firewall.core.io.firewalld_conf
+import firewall.config
+
+
+def test_reload_policy():
+ def t(value, expected_valid=True, **kw):
+
+ expected = {
+ "INPUT": "DROP",
+ "FORWARD": "DROP",
+ "OUTPUT": "DROP",
+ }
+ for k, v in kw.items():
+ assert k in expected
+ expected[k] = v
+
+ try:
+ parsed = (
+ firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ value
+ )
+ )
+ except ValueError:
+ assert not expected_valid
+ return
+
+ assert parsed == expected
+ assert expected_valid
+
+ unparsed = (
+ firewall.core.io.firewalld_conf.firewalld_conf._unparse_reload_policy(
+ parsed
+ )
+ )
+ parsed2 = firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ unparsed
+ )
+ assert parsed2 == parsed
+
+ t(None)
+ t("")
+ t(" ")
+ t(" input: ACCept ", INPUT="ACCEPT")
+ t(
+ "forward:DROP, forward : REJEct; input: ACCept ",
+ INPUT="ACCEPT",
+ FORWARD="REJECT",
+ )
+ t(" accept ", INPUT="ACCEPT", FORWARD="ACCEPT", OUTPUT="ACCEPT")
+ t("REJECT", INPUT="REJECT", FORWARD="REJECT", OUTPUT="REJECT")
+ t("forward=REJECT", FORWARD="REJECT")
+ t("forward=REJECT , input=accept", FORWARD="REJECT", INPUT="ACCEPT")
+ t("forward=REJECT , xinput=accept", expected_valid=False)
+ t("forward=REJECT, ACCEPT", expected_valid=False)
+
+ def _norm(reload_policy):
+ parsed = firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ reload_policy
+ )
+ return firewall.core.io.firewalld_conf.firewalld_conf._unparse_reload_policy(
+ parsed
+ )
+
+ assert firewall.config.FALLBACK_RELOAD_POLICY == _norm(
+ firewall.config.FALLBACK_RELOAD_POLICY
+ )
--
2.43.5

View File

@ -1,32 +0,0 @@
From e9e1edef3af8bd1a6b7c27fdd2d580e2f1571440 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Rigault?= <rigault.francois@gmail.com>
Date: Sun, 28 Aug 2022 10:25:33 +0200
Subject: [PATCH 11/17] v1.1.0: fix(ipset): exception on overlap checking empty
set
In the case of --remove-entries-from-file, check_for_overlapping_entries
can be called with no entry in input, which fails with an exception.
Fixes: rhbz2121985
(cherry picked from commit 1ea554e6263ed21aa9ae6e5f0abb629d53b4a7bc)
---
src/firewall/core/ipset.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
index b160d8345669..d8e0a1ab1e56 100644
--- a/src/firewall/core/ipset.py
+++ b/src/firewall/core/ipset.py
@@ -327,6 +327,9 @@ def check_for_overlapping_entries(entries):
# at least one entry can not be parsed
return
+ if len(entries) == 0:
+ return
+
# We can take advantage of some facts of IPv4Network/IPv6Network and
# how Python sorts the networks to quickly detect overlaps.
#
--
2.39.3

View File

@ -0,0 +1,27 @@
From 55e40954a8c596fabe03371e9a508d3518273ac1 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:27:54 -0400
Subject: [PATCH 11/22] v2.2.0: test(functions): add macro CHECK_NFTABLES_FIB
(cherry picked from commit 0aeebef07bc57b1f56b107632cdfdd809384398c)
---
src/tests/functions.at | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index f454ca980046..65a4ce078e05 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -748,3 +748,9 @@ m4_define([CHECK_NM_CAPABILITY_OVS], [
m4_define([IF_BACKEND_IS_DEFAULT], [
m4_if(nftables, FIREWALL_BACKEND, [$1], [])
])
+
+m4_define([CHECK_NFTABLES_FIB], [
+ m4_if(nftables, FIREWALL_BACKEND, [
+ IF_HOST_SUPPORTS_NFT_FIB([], [AT_SKIP_IF([:])])
+ ])
+])
--
2.43.5

View File

@ -1,48 +0,0 @@
From a7b4212df4e1aa05d8dcb8fd4cf5e353a84d3481 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 29 Aug 2022 08:37:50 -0400
Subject: [PATCH 12/17] v1.1.0: test(ipset): verify --remove-entries-from-file
Specifically if it results in an empty set.
Coverage: rhbz2121985
(cherry picked from commit edea40189e10d3f7777e69746592fb5e2e0e36ea)
---
src/tests/regression/gh1011.at | 15 +++++++++++++++
src/tests/regression/regression.at | 1 +
2 files changed, 16 insertions(+)
create mode 100644 src/tests/regression/gh1011.at
diff --git a/src/tests/regression/gh1011.at b/src/tests/regression/gh1011.at
new file mode 100644
index 000000000000..037ab70648eb
--- /dev/null
+++ b/src/tests/regression/gh1011.at
@@ -0,0 +1,15 @@
+FWD_START_TEST([remove entries results in empty])
+AT_KEYWORDS(ipset gh1011 rhbz2121985)
+
+FWD_CHECK([--permanent --new-ipset foobar --type hash:net], 0, [ignore])
+AT_DATA([./empty], [dnl
+10.10.10.0/24
+])
+FWD_CHECK([--permanent --ipset foobar --add-entry 10.10.10.0/24], 0, [ignore])
+FWD_CHECK([--permanent --ipset foobar --remove-entries-from-file ./empty], 0, [ignore])
+
+FWD_RELOAD()
+FWD_CHECK([--ipset foobar --add-entry 10.10.10.0/24], 0, [ignore])
+FWD_CHECK([--ipset foobar --remove-entries-from-file ./empty], 0, [ignore])
+
+FWD_END_TEST()
diff --git a/src/tests/regression/regression.at b/src/tests/regression/regression.at
index 143298d3235f..889c66dd175d 100644
--- a/src/tests/regression/regression.at
+++ b/src/tests/regression/regression.at
@@ -51,3 +51,4 @@ m4_include([regression/service_includes_for_builtin.at])
m4_include([regression/rhbz2181406.at])
m4_include([regression/ipset_scale.at])
m4_include([regression/gh881.at])
+m4_include([regression/gh1011.at])
--
2.39.3

View File

@ -0,0 +1,31 @@
From d368d579c78652a68273897d5f8b5099d251a9b5 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:21:06 -0400
Subject: [PATCH 12/22] v2.2.0: test(functions): add macro
CHECK_NFTABLES_FIB_IN_FORWARD
(cherry picked from commit b9cf7b75c7d94efa98545a3b7ad5020b1896b22a)
---
src/tests/functions.at | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index 65a4ce078e05..b2372dd4075b 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -754,3 +754,12 @@ m4_define([CHECK_NFTABLES_FIB], [
IF_HOST_SUPPORTS_NFT_FIB([], [AT_SKIP_IF([:])])
])
])
+
+m4_define([CHECK_NFTABLES_FIB_IN_FORWARD], [
+ m4_if(nftables, FIREWALL_BACKEND, [
+ NS_CHECK([nft add table inet firewalld_check])
+ NS_CHECK([nft add chain inet firewalld_check foobar { type filter hook forward priority 0 \; }])
+ AT_SKIP_IF([! NS_CMD([nft add rule inet firewalld_check foobar meta nfproto ipv6 fib saddr . mark . iif oif missing drop >/dev/null 2>&1])])
+ NS_CHECK([nft delete table inet firewalld_check])
+ ])
+])
--
2.43.5

View File

@ -1,138 +0,0 @@
From 90412a5fae831dcb1a8c9d9f4a798efabcc46567 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Tue, 11 Jul 2023 15:26:56 +0200
Subject: [PATCH 13/17] v1.2.0: fix(ipset): fix configuring IP range for ipsets
with nftables
Setting an IP range with nftables did not work:
firewall-cmd --permanent --delete-ipset=testipset || :
firewall-cmd --permanent --delete-zone=testzone || :
ENTRY=1.1.1.1-1.1.1.10
firewall-cmd --permanent --new-ipset=testipset --type=hash:ip
firewall-cmd --permanent --ipset=testipset --add-entry="$ENTRY"
firewall-cmd --permanent --info-ipset=testipset
firewall-cmd --permanent --new-zone=testzone
firewall-cmd --permanent --zone=testzone --add-rich-rule='rule family="ipv4" source ipset="testipset" service name="ssh" accept'
firewall-cmd --reload &
This would generate the following JSON request:
{
"add": {
"element": {
"family": "inet",
"table": "firewalld",
"name": "testipset",
"elem": [
"1.1.1.1-1.1.1.10"
]
}
}
}
libnftables will try to resolve "1.1.1.1-1.1.1.10" via getaddrinfo(). Calling
getaddrinfo() to resolve names is bound to fail, and it blocks the process for
a very long time. libnftables should not block the calling process ([1]).
We need to generate the correct JSON request, which is
{
"add": {
"element": {
"family": "inet",
"table": "firewalld",
"name": "testipset",
"elem": [
{
"range": [
"1.1.1.1",
"1.1.1.10"
]
}
]
}
}
}
This is an ugly fix, because the parsing of ipset entries is duplicated
and inconsistent. A better solution for that shall follow.
[1] https://marc.info/?l=netfilter-devel&m=168901121103612
https://bugzilla.redhat.com/show_bug.cgi?id=2028748
Fixes: 1582c5dd736a ('feat: nftables: convert to libnftables JSON interface')
(cherry picked from commit 4db89e316f2d60f3cf856a7025a96a61e40b1e5a)
---
src/firewall/core/nftables.py | 27 +++++++++++++++------------
src/tests/cli/firewall-cmd.at | 4 ++--
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 19a649aaaa71..2764bcf93645 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1850,19 +1850,22 @@ class nftables(object):
fragment.append({"range": [port_str[:index], port_str[index+1:]]})
elif type_format[i] in ["ip", "net"]:
- try:
- index = entry_tokens[i].index("/")
- except ValueError:
- addr = entry_tokens[i]
- if "family" in obj.options and obj.options["family"] == "inet6":
- addr = normalizeIP6(addr)
- fragment.append(addr)
+ if '-' in entry_tokens[i]:
+ fragment.append({"range": entry_tokens[i].split('-') })
else:
- addr = entry_tokens[i][:index]
- if "family" in obj.options and obj.options["family"] == "inet6":
- addr = normalizeIP6(addr)
- fragment.append({"prefix": {"addr": addr,
- "len": int(entry_tokens[i][index+1:])}})
+ try:
+ index = entry_tokens[i].index("/")
+ except ValueError:
+ addr = entry_tokens[i]
+ if "family" in obj.options and obj.options["family"] == "inet6":
+ addr = normalizeIP6(addr)
+ fragment.append(addr)
+ else:
+ addr = entry_tokens[i][:index]
+ if "family" in obj.options and obj.options["family"] == "inet6":
+ addr = normalizeIP6(addr)
+ fragment.append({"prefix": {"addr": addr,
+ "len": int(entry_tokens[i][index+1:])}})
else:
fragment.append(entry_tokens[i])
return [{"concat": fragment}] if len(type_format) > 1 else fragment
diff --git a/src/tests/cli/firewall-cmd.at b/src/tests/cli/firewall-cmd.at
index 47bdd81f5194..c4ab3108d37c 100644
--- a/src/tests/cli/firewall-cmd.at
+++ b/src/tests/cli/firewall-cmd.at
@@ -908,7 +908,7 @@ FWD_START_TEST([ipset])
dnl multi dimensional sets
FWD_CHECK([--permanent --new-ipset=foobar --type=hash:ip,port], 0, ignore)
- FWD_CHECK([--permanent --ipset=foobar --add-entry=10.10.10.10,1234], 0, ignore)
+ FWD_CHECK([--permanent --ipset=foobar --add-entry=10.10.10.10-10.10.10.12,1234], 0, ignore)
FWD_CHECK([--permanent --ipset=foobar --add-entry=10.10.10.10,2000-2100], 0, ignore)
FWD_RELOAD
NFT_LIST_SET([foobar], 0, [dnl
@@ -916,7 +916,7 @@ FWD_START_TEST([ipset])
set foobar {
type ipv4_addr . inet_proto . inet_service
flags interval
- elements = { 10.10.10.10 . tcp . 1234,
+ elements = { 10.10.10.10-10.10.10.12 . tcp . 1234,
10.10.10.10 . tcp . 2000-2100 }
}
}
--
2.39.3

View File

@ -0,0 +1,51 @@
From c1620d5ad4c151382373a138ab0c36dd7561a4bb Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:29:50 -0400
Subject: [PATCH 13/22] v2.2.0: test(rpfilter): use CHECK macros
(cherry picked from commit 352f3fc7fc00b675178de1eff8f0197607741de7)
---
src/tests/features/rpfilter.at | 27 +++++++++++----------------
1 file changed, 11 insertions(+), 16 deletions(-)
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 01fb81ea75ef..ccc8a6cf5e80 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -1,22 +1,17 @@
-FWD_START_TEST([rpfilter])
+FWD_START_TEST([rpfilter - strict])
AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
-IF_HOST_SUPPORTS_NFT_FIB([
- NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
- table inet firewalld {
- chain filter_PREROUTING {
- icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
- meta nfproto ipv6 fib saddr . mark . iif oif missing drop
- }
- }
- ])
-], [
- NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
- table inet firewalld {
- chain filter_PREROUTING {
- }
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
+ meta nfproto ipv6 fib saddr . mark . iif oif missing drop
}
- ])
+ }
])
IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
--
2.43.5

View File

@ -1,45 +0,0 @@
From 08f76e2aa6d7ca35cfb626f20ace1f9036cda3a0 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 14 Aug 2023 09:13:29 -0400
Subject: [PATCH 14/17] v1.2.0: chore(nftables): add delete table helper
This is to workaround an nftables issue where using the "delete" verb on
a table that does not exist will throw ENOENT. We can't use the newer
"destroy" verb because it's too new to rely upon.
A simple hack is to always add the table before deleting it. The "add"
is ignored if the table already exists.
(cherry picked from commit 8be561d26931832f000526cc41293700faa6c877)
---
src/firewall/core/nftables.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 2764bcf93645..1959bdce73be 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -396,6 +396,20 @@ class nftables(object):
# Tables always exist in nftables
return [table] if table else IPTABLES_TO_NFT_HOOK.keys()
+ def _build_delete_table_rules(self, table):
+ # To avoid nftables returning ENOENT we always add the table before
+ # deleting to guarantee it will exist.
+ #
+ # In the future, this add+delete should be replaced with "destroy", but
+ # that verb is too new to rely upon.
+ rules = []
+ for family in ["inet", "ip", "ip6"]:
+ rules.append({"add": {"table": {"family": family,
+ "name": table}}})
+ rules.append({"delete": {"table": {"family": family,
+ "name": table}}})
+ return rules
+
def build_flush_rules(self):
# Policy is stashed in a separate table that we're _not_ going to
# flush. As such, we retain the policy rule handles and ref counts.
--
2.39.3

View File

@ -0,0 +1,41 @@
From 0ba1eed533e4cd1dd77771ba7c16dc0edcea841e Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 13 May 2024 13:53:55 -0400
Subject: [PATCH 14/22] v2.2.0: test(IPv6_rpfilter): verify valid values
Including the deprecated "yes" value.
(cherry picked from commit 1e91792157d36355669b4f02a82c1ee603a9467d)
---
src/tests/features/rpfilter.at | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index ccc8a6cf5e80..755d9dfd33cc 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -22,4 +22,20 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
PREROUTING_ZONES 0 -- ::/0 ::/0
])
-FWD_END_TEST
+FWD_END_TEST()
+
+FWD_START_TEST([rpfilter - config values])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+
+dnl Verify other/deprecated configuration values are accepted.
+dnl
+m4_foreach([VALUE], [[no], [yes], [false], [true]], [
+ AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=VALUE/' ./firewalld.conf])
+ FWD_RELOAD()
+])
+dnl And a bogus one.
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=bogus/' ./firewalld.conf])
+FWD_RELOAD()
+
+FWD_END_TEST([-e "/^WARNING: IPv6_rpfilter 'bogus' is not valid/d"])
--
2.43.5

View File

@ -1,38 +0,0 @@
From 0704ea3fef79cc1532f913ac1598e297016e1905 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Thu, 10 Aug 2023 08:43:03 -0400
Subject: [PATCH 15/17] v1.2.0: fix(nftables): always flush main table on start
On start created_tables will not contain the main "firewalld" table so a
flush command is not issued. We should always attempt to flush. If
CleanupOnExit=no, then not flushing causes duplicate rules on restart.
Fixes: rhbz2222044
(cherry picked from commit 6a155ea7195f2c720625e2452afa41544b4b4227)
---
src/firewall/core/nftables.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 1959bdce73be..e3e06d75f663 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -427,13 +427,11 @@ class nftables(object):
self.policy_priority_counts = {}
self.zone_source_index_cache = {}
- rules = []
for family in ["inet", "ip", "ip6"]:
if TABLE_NAME in self.created_tables[family]:
- rules.append({"delete": {"table": {"family": family,
- "name": TABLE_NAME}}})
self.created_tables[family].remove(TABLE_NAME)
- return rules
+
+ return self._build_delete_table_rules(TABLE_NAME)
def _build_set_policy_rules_ct_rules(self, enable):
add_del = { True: "add", False: "delete" }[enable]
--
2.39.3

View File

@ -0,0 +1,336 @@
From 7973ddf8d9f972f0292c8c865da9e0ebaefd77cb Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Thu, 16 May 2024 09:26:43 -0400
Subject: [PATCH 15/22] v2.2.0: chore(IPv6_rpfilter): prepare for new config
values
This is just prep work for supporting other configuration values. This
commits supports using "strict" as a value which is synonymous with
"yes" and is the current default behavior.
(cherry picked from commit cd959f21a5ceb41057b76f817b0456c281408ae0)
---
config/firewalld.conf | 15 ++++++++++-----
doc/xml/firewalld.conf.xml | 12 +++++++++---
doc/xml/firewalld.dbus.xml | 8 ++++++--
src/firewall/config/__init__.py.in | 3 ++-
src/firewall/core/fw.py | 19 ++++++++-----------
src/firewall/core/io/firewalld_conf.py | 14 ++++++++------
src/firewall/server/config.py | 22 ++++++++++++++++++----
src/firewall/server/firewalld.py | 2 +-
src/tests/dbus/firewalld.conf.at | 4 ++++
src/tests/features/rpfilter.at | 2 +-
10 files changed, 67 insertions(+), 34 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 7a0be1ff1b76..48e2a5a6527a 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -26,14 +26,19 @@ CleanupModulesOnExit=no
Lockdown=no
# IPv6_rpfilter
-# Performs a reverse path filter test on a packet for IPv6. If a reply to the
-# packet would be sent via the same interface that the packet arrived on, the
-# packet will match and be accepted, otherwise dropped.
+# Performs reverse path filtering (RPF) on IPv6 packets as per RFC 3704.
+# Possible values:
+# - strict: Performs "strict" filtering as per RFC 3704. This check
+# verifies that the in ingress interface is the same interface
+# that would be used to send a packet reply to the source. That
+# is, ingress == egress.
+# - no: RPF is completely disabled.
+#
# The rp_filter for IPv4 is controlled using sysctl.
# Note: This feature has a performance impact. See man page FIREWALLD.CONF(5)
# for details.
-# Default: yes
-IPv6_rpfilter=yes
+# Default: strict
+IPv6_rpfilter=strict
# IndividualCalls
# Do not use combined -restore calls, but individual calls. This increases the
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 022569ccf502..be6972aa0d8a 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -121,9 +121,15 @@
<term><option>IPv6_rpfilter</option></term>
<listitem>
<para>
- If this option is enabled (it is by default), reverse path filter test on a packet for IPv6 is performed.
- If a reply to the packet would be sent via the same interface that the packet arrived on, the packet will match and be accepted, otherwise dropped.
- For IPv4 the rp_filter is controlled using sysctl.
+ Performs reverse path filtering (RPF) on IPv6 packets as per RFC 3704.
+ Possible values:
+ - strict: Performs "strict" filtering as per RFC 3704. This check
+ verifies that the in ingress interface is the same interface
+ that would be used to send a packet reply to the source. That
+ is, ingress == egress.
+ - no: RPF is completely disabled.
+
+ The rp_filter for IPv4 is controlled using sysctl.
</para>
<para>
<emphasis role="bold">Note</emphasis>: This feature has a performance
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index a3196ea4af38..f04cf5ae757b 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2858,8 +2858,12 @@
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Properties.IPv6_rpfilter">
- <term><parameter>IPv6_rpfilter</parameter> - s - (rw)</term>
- <listitem><para>Indicates whether the reverse path filter test on a packet for IPv6 is enabled. If a reply to the packet would be sent via the same interface that the packet arrived on, the packet will match and be accepted, otherwise dropped.</para></listitem>
+ <term><parameter>IPv6_rpfilter</parameter> - b - (rw)</term>
+ <listitem><para>Deprecated. See <link linkend="FirewallD1.config.Properties.IPv6_rpfilter2">org.fedoraproject.FirewallD1.config.Properties.IPv6_rpfilter2</link>.</para></listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.config.Properties.IPv6_rpfilter2">
+ <term><parameter>IPv6_rpfilter2</parameter> - s - (rw)</term>
+ <listitem><para>Indicates whether the reverse path filter (RFE 3704) test on a packet for IPv6 is enabled.</para></listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Properties.IndividualCalls">
<term><parameter>IndividualCalls</parameter> - s - (ro)</term>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index da1e31e10e58..68e9bddce5a8 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,6 +120,7 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
@@ -127,7 +128,7 @@ FALLBACK_MINIMAL_MARK = 100
FALLBACK_CLEANUP_ON_EXIT = True
FALLBACK_CLEANUP_MODULES_ON_EXIT = False
FALLBACK_LOCKDOWN = False
-FALLBACK_IPV6_RPFILTER = True
+FALLBACK_IPV6_RPFILTER = "strict"
FALLBACK_INDIVIDUAL_CALLS = False
FALLBACK_LOG_DENIED = "off"
FALLBACK_AUTOMATIC_HELPERS = "no"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index ac13be122b66..b2e150077958 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -98,7 +98,7 @@ class Firewall(object):
self.ebtables_enabled, self._state, self._panic,
self._default_zone, self._module_refcount, self._marks,
self.cleanup_on_exit, self.cleanup_modules_on_exit,
- self.ipv6_rpfilter_enabled, self.ipset_enabled,
+ self._ipv6_rpfilter, self.ipset_enabled,
self._individual_calls, self._log_denied)
def __init_vars(self):
@@ -112,7 +112,7 @@ class Firewall(object):
# fallback settings will be overloaded by firewalld.conf
self.cleanup_on_exit = config.FALLBACK_CLEANUP_ON_EXIT
self.cleanup_modules_on_exit = config.FALLBACK_CLEANUP_MODULES_ON_EXIT
- self.ipv6_rpfilter_enabled = config.FALLBACK_IPV6_RPFILTER
+ self._ipv6_rpfilter = config.FALLBACK_IPV6_RPFILTER
self._individual_calls = config.FALLBACK_INDIVIDUAL_CALLS
self._log_denied = config.FALLBACK_LOG_DENIED
self._firewall_backend = config.FALLBACK_FIREWALL_BACKEND
@@ -329,14 +329,11 @@ class Firewall(object):
if self._firewalld_conf.get("IPv6_rpfilter"):
value = self._firewalld_conf.get("IPv6_rpfilter")
if value is not None:
- if value.lower() in [ "no", "false" ]:
- self.ipv6_rpfilter_enabled = False
- if value.lower() in [ "yes", "true" ]:
- self.ipv6_rpfilter_enabled = True
- if self.ipv6_rpfilter_enabled:
- log.debug1("IPv6 rpfilter is enabled")
- else:
- log.debug1("IPV6 rpfilter is disabled")
+ if value.lower() in ["no", "false"]:
+ self._ipv6_rpfilter = "no"
+ elif value.lower() in ["yes", "true", "strict"]:
+ self._ipv6_rpfilter = "strict"
+ log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
value = self._firewalld_conf.get("IndividualCalls")
@@ -933,7 +930,7 @@ class Firewall(object):
if self.is_ipv_enabled("ipv6"):
ipv6_backend = self.get_backend_by_ipv("ipv6")
if "raw" in ipv6_backend.get_available_tables():
- if self.ipv6_rpfilter_enabled:
+ if self._ipv6_rpfilter != "no":
rules = ipv6_backend.build_rpfilter_rules(self._log_denied)
transaction.add_rules(ipv6_backend, rules)
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index d2879b319d1f..9ad64883b656 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -71,7 +71,7 @@ class firewalld_conf(object):
self.set("CleanupOnExit", "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no")
self.set("CleanupModulesOnExit", "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no")
self.set("Lockdown", "yes" if config.FALLBACK_LOCKDOWN else "no")
- self.set("IPv6_rpfilter","yes" if config.FALLBACK_IPV6_RPFILTER else "no")
+ self.set("IPv6_rpfilter", config.FALLBACK_IPV6_RPFILTER)
self.set("IndividualCalls", "yes" if config.FALLBACK_INDIVIDUAL_CALLS else "no")
self.set("LogDenied", config.FALLBACK_LOG_DENIED)
self.set("AutomaticHelpers", config.FALLBACK_AUTOMATIC_HELPERS)
@@ -160,12 +160,14 @@ class firewalld_conf(object):
# check ipv6_rpfilter
value = self.get("IPv6_rpfilter")
- if not value or value.lower() not in [ "yes", "true", "no", "false" ]:
+ if not value or value.lower() not in config.IPV6_RPFILTER_VALUES:
if value is not None:
- log.warning("IPv6_rpfilter '%s' is not valid, using default "
- "value %s", value if value else '',
- config.FALLBACK_IPV6_RPFILTER)
- self.set("IPv6_rpfilter","yes" if config.FALLBACK_IPV6_RPFILTER else "no")
+ log.warning(
+ "IPv6_rpfilter '%s' is not valid, using default " "value %s",
+ value if value else "",
+ config.FALLBACK_IPV6_RPFILTER,
+ )
+ self.set("IPv6_rpfilter", config.FALLBACK_IPV6_RPFILTER)
# check individual calls
value = self.get("IndividualCalls")
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index dfbbb889b520..b805e497bb05 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -100,6 +100,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupOnExit": "readwrite",
"CleanupModulesOnExit": "readwrite",
"IPv6_rpfilter": "readwrite",
+ "IPv6_rpfilter2": "readwrite",
"Lockdown": "readwrite",
"MinimalMark": "readwrite",
"IndividualCalls": "readwrite",
@@ -564,7 +565,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2" ]:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
"Property '%s' does not exist" % prop)
@@ -594,8 +595,13 @@ class FirewallDConfig(DbusServiceObject):
value = "yes" if config.FALLBACK_LOCKDOWN else "no"
return dbus.String(value)
elif prop == "IPv6_rpfilter":
+ if value is None or value != "no":
+ return dbus.String("yes")
+ else:
+ return dbus.String("no")
+ elif prop == "IPv6_rpfilter2":
if value is None:
- value = "yes" if config.FALLBACK_IPV6_RPFILTER else "no"
+ value = config.FALLBACK_IPV6_RPFILTER
return dbus.String(value)
elif prop == "IndividualCalls":
if value is None:
@@ -640,6 +646,8 @@ class FirewallDConfig(DbusServiceObject):
return dbus.String(self._get_property(prop))
elif prop == "IPv6_rpfilter":
return dbus.String(self._get_property(prop))
+ elif prop == "IPv6_rpfilter2":
+ return dbus.String(self._get_property(prop))
elif prop == "IndividualCalls":
return dbus.String(self._get_property(prop))
elif prop == "LogDenied":
@@ -693,7 +701,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2" ]:
ret[x] = self._get_property(x)
elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
@@ -722,7 +730,7 @@ class FirewallDConfig(DbusServiceObject):
"IPv6_rpfilter", "IndividualCalls",
"LogDenied",
"FirewallBackend", "FlushAllOnReload",
- "RFC3964_IPv4"]:
+ "RFC3964_IPv4", "IPv6_rpfilter2"]:
if property_name in [ "CleanupOnExit", "CleanupModulesOnExit",
"Lockdown", "IPv6_rpfilter",
"IndividualCalls", "FlushAllOnReload",
@@ -742,6 +750,12 @@ class FirewallDConfig(DbusServiceObject):
raise FirewallError(errors.INVALID_VALUE,
"'%s' for %s" % \
(new_value, property_name))
+ elif property_name == "IPv6_rpfilter2":
+ if new_value not in config.IPV6_RPFILTER_VALUES:
+ raise FirewallError(
+ errors.INVALID_VALUE,
+ "'%s' for %s" % (new_value, property_name),
+ )
else:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
diff --git a/src/firewall/server/firewalld.py b/src/firewall/server/firewalld.py
index e43ddb959f41..8b9593a22fd8 100644
--- a/src/firewall/server/firewalld.py
+++ b/src/firewall/server/firewalld.py
@@ -165,7 +165,7 @@ class FirewallD(DbusServiceObject):
return dbus.Boolean(self.fw.is_ipv_enabled("ipv6"))
elif prop == "IPv6_rpfilter":
- return dbus.Boolean(self.fw.ipv6_rpfilter_enabled)
+ return dbus.Boolean(False if self.fw._ipv6_rpfilter == "no" else True)
elif prop == "IPv6ICMPTypes":
return dbus.Array(self.fw.ipv6_supported_icmp_types, "s")
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 10b16cd9e06f..61c220f3e59c 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -3,8 +3,10 @@ AT_KEYWORDS(dbus)
IF_HOST_SUPPORTS_NFT_FIB([
EXPECTED_IPV6_RPFILTER_VALUE=yes
+ EXPECTED_IPV6_RPFILTER2_VALUE=strict
], [
EXPECTED_IPV6_RPFILTER_VALUE=no
+ EXPECTED_IPV6_RPFILTER2_VALUE=no
])
IF_HOST_SUPPORTS_NFT_RULE_INDEX([
@@ -23,6 +25,7 @@ string "DefaultZone" : variant string "public"
string "FirewallBackend" : variant string "nftables"
string "FlushAllOnReload" : variant string "yes"
string "IPv6_rpfilter" : variant string m4_escape(["${EXPECTED_IPV6_RPFILTER_VALUE}"])
+string "IPv6_rpfilter2" : variant string m4_escape(["${EXPECTED_IPV6_RPFILTER2_VALUE}"])
string "IndividualCalls" : variant string m4_escape(["${EXPECTED_INDIVIDUAL_CALLS_VALUE}"])
string "Lockdown" : variant string "no"
string "LogDenied" : variant string "off"
@@ -43,6 +46,7 @@ _helper([AutomaticHelpers], [string:"yes"], [variant string "no"])
_helper([Lockdown], [string:"yes"], [variant string "yes"])
_helper([LogDenied], [string:"all"], [variant string "all"])
_helper([IPv6_rpfilter], [string:"yes"], [variant string "yes"])
+_helper([IPv6_rpfilter2], [string:"no"], [variant string "no"])
_helper([IndividualCalls], [string:"yes"], [variant string "yes"])
_helper([FirewallBackend], [string:"iptables"], [variant string "iptables"])
_helper([FlushAllOnReload], [string:"no"], [variant string "no"])
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 755d9dfd33cc..58a4b4500330 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -2,7 +2,7 @@ FWD_START_TEST([rpfilter - strict])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
-AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=yes/' ./firewalld.conf])
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=strict/' ./firewalld.conf])
FWD_RELOAD()
NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
--
2.43.5

View File

@ -1,82 +0,0 @@
From 8c79246dbc5b8945c22b313ad51be698f2b61316 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 9 Aug 2023 14:39:08 -0400
Subject: [PATCH 16/17] v1.2.0: test(CleanUpOnExit): verify restart does not
duplicate rules
Coverage: rhbz2222044
(cherry picked from commit c66e752a00c05a5afa58904850d244f50528059e)
---
src/tests/regression/regression.at | 1 +
src/tests/regression/rhbz2222044.at | 50 +++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
create mode 100644 src/tests/regression/rhbz2222044.at
diff --git a/src/tests/regression/regression.at b/src/tests/regression/regression.at
index 889c66dd175d..bc9aeb1a8624 100644
--- a/src/tests/regression/regression.at
+++ b/src/tests/regression/regression.at
@@ -52,3 +52,4 @@ m4_include([regression/rhbz2181406.at])
m4_include([regression/ipset_scale.at])
m4_include([regression/gh881.at])
m4_include([regression/gh1011.at])
+m4_include([regression/rhbz2222044.at])
diff --git a/src/tests/regression/rhbz2222044.at b/src/tests/regression/rhbz2222044.at
new file mode 100644
index 000000000000..9f3b1615b2f9
--- /dev/null
+++ b/src/tests/regression/rhbz2222044.at
@@ -0,0 +1,50 @@
+FWD_START_TEST([duplicate rules after restart])
+AT_KEYWORDS(rhbz2222044)
+AT_SKIP_IF([! NS_CMD([command -v wc >/dev/null 2>&1])])
+
+dnl rules have not changed so rule count should not change
+m4_define([check_rule_count], [
+m4_if(nftables, FIREWALL_BACKEND, [
+NS_CHECK([nft list table inet firewalld | wc -l], 0, [dnl
+237
+])
+NS_CHECK([nft list table ip firewalld | wc -l], 0, [dnl
+105
+])
+NS_CHECK([nft list table ip6 firewalld | wc -l], 0, [dnl
+105
+])
+], [ dnl iptables
+NS_CHECK([iptables-save | wc -l], 0, [dnl
+256
+])
+])])
+
+dnl --------------------------
+dnl --------------------------
+
+AT_CHECK([sed -i 's/^CleanupOnExit.*/CleanupOnExit=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+check_rule_count()
+FWD_RESTART()
+check_rule_count()
+
+check_rule_count()
+FWD_RELOAD()
+check_rule_count()
+
+dnl Now do it again, but with CleanupOnExit=no
+AT_CHECK([sed -i 's/^CleanupOnExit.*/CleanupOnExit=no/' ./firewalld.conf])
+FWD_RELOAD()
+
+check_rule_count()
+FWD_RESTART()
+check_rule_count()
+
+check_rule_count()
+FWD_RELOAD()
+check_rule_count()
+
+m4_undefine([check_rule_count])
+FWD_END_TEST()
--
2.39.3

View File

@ -0,0 +1,166 @@
From 41828e0723b1ad195a33c535918a2fb6fabaf88f Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 6 May 2024 10:22:09 -0400
Subject: [PATCH 16/22] v2.2.0: feat(IPv6_rpfilter): support loose rpfilter
Support "loose" mode as per RFC 3704. This guarantees only that there is
a patch back to the source, but does NOT guarantee that ingress ==
egress.
(cherry picked from commit 669524a10658761f614e1f199970844db7259960)
---
config/firewalld.conf | 4 ++++
doc/xml/firewalld.conf.xml | 4 ++++
src/firewall/config/__init__.py.in | 2 +-
src/firewall/core/fw.py | 2 ++
src/firewall/core/ipXtables.py | 16 ++++++++++------
src/firewall/core/nftables.py | 8 +++++++-
src/tests/features/rpfilter.at | 26 ++++++++++++++++++++++++++
7 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 48e2a5a6527a..c35caf9f152b 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -32,6 +32,10 @@ Lockdown=no
# verifies that the in ingress interface is the same interface
# that would be used to send a packet reply to the source. That
# is, ingress == egress.
+# - loose: Performs "loose" filtering as per RFC 3704. This check only
+# verifies that there is a route back to the source through any
+# interface; even if it's not the same one on which the packet
+# arrived.
# - no: RPF is completely disabled.
#
# The rp_filter for IPv4 is controlled using sysctl.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index be6972aa0d8a..279a149ab2a1 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -127,6 +127,10 @@
verifies that the in ingress interface is the same interface
that would be used to send a packet reply to the source. That
is, ingress == egress.
+ - loose: Performs "loose" filtering as per RFC 3704. This check only
+ verifies that there is a route back to the source through any
+ interface; even if it's not the same one on which the packet
+ arrived.
- no: RPF is completely disabled.
The rp_filter for IPv4 is controlled using sysctl.
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 68e9bddce5a8..6bfe96be35a6 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,7 +120,7 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
-IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict"]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index b2e150077958..d9724e9c8534 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -333,6 +333,8 @@ class Firewall(object):
self._ipv6_rpfilter = "no"
elif value.lower() in ["yes", "true", "strict"]:
self._ipv6_rpfilter = "strict"
+ elif value.lower() in ["loose"]:
+ self._ipv6_rpfilter = "loose"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index 1a0cea7a3b4e..339840c37ba4 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -1456,13 +1456,17 @@ class ip6tables(ip4tables):
def build_rpfilter_rules(self, log_denied=False):
rules = []
- rules.append([ "-I", "PREROUTING", "-t", "mangle",
- "-m", "rpfilter", "--invert", "--validmark",
- "-j", "DROP" ])
+ rpfilter_fragment = ["-m", "rpfilter", "--invert", "--validmark"]
+ if self._fw._ipv6_rpfilter == "loose":
+ rpfilter_fragment += ["--loose"]
+
+ rules.append([ "-I", "PREROUTING", "-t", "mangle" ]
+ + rpfilter_fragment +
+ [ "-j", "DROP" ])
if log_denied != "off":
- rules.append([ "-I", "PREROUTING", "-t", "mangle",
- "-m", "rpfilter", "--invert", "--validmark",
- "-j", "LOG",
+ rules.append([ "-I", "PREROUTING", "-t", "mangle" ]
+ + rpfilter_fragment +
+ [ "-j", "LOG",
"--log-prefix", "rpfilter_DROP: " ])
rules.append([ "-I", "PREROUTING", "-t", "mangle",
"-p", "ipv6-icmp",
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index e9816147ef8e..9827e84042ef 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1614,10 +1614,16 @@ class nftables(object):
def build_rpfilter_rules(self, log_denied=False):
rules = []
+
+ if self._fw._ipv6_rpfilter == "loose":
+ fib_flags = ["saddr", "mark"]
+ else:
+ fib_flags = ["saddr", "mark", "iif"]
+
expr_fragments = [{"match": {"left": {"meta": {"key": "nfproto"}},
"op": "==",
"right": "ipv6"}},
- {"match": {"left": {"fib": {"flags": ["saddr", "iif", "mark"],
+ {"match": {"left": {"fib": {"flags": fib_flags,
"result": "oif"}},
"op": "==",
"right": False}}]
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 58a4b4500330..23cd9e0e8d7f 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -24,6 +24,32 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - loose])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=loose/' ./firewalld.conf])
+FWD_RELOAD()
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
+ meta nfproto ipv6 fib saddr . mark oif missing drop
+ }
+ }
+])
+
+IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
+ ACCEPT 58 -- ::/0 ::/0 ipv6-icmptype 134
+ ACCEPT 58 -- ::/0 ::/0 ipv6-icmptype 135
+ DROP 0 -- ::/0 ::/0 rpfilter loose validmark invert
+ PREROUTING_direct 0 -- ::/0 ::/0
+ PREROUTING_ZONES 0 -- ::/0 ::/0
+])
+
+FWD_END_TEST()
+
FWD_START_TEST([rpfilter - config values])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -1,32 +0,0 @@
From 2ca79f8ebbadcf39f9b378b7fd296fcef13a4c54 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 14 Aug 2023 09:21:17 -0400
Subject: [PATCH 17/17] v1.2.0: chore(nftables): policy: use delete table
helper
Use the new table delete helper when deleting the policy table.
(cherry picked from commit a291a5d2f03711c2c6b0079128626204229ad79e)
---
src/firewall/core/nftables.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index e3e06d75f663..2a13b2678a94 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -489,9 +489,9 @@ class nftables(object):
if policy_key in self.rule_to_handle:
rules.append(rule)
+ rules += self._build_delete_table_rules(TABLE_NAME_POLICY)
+
if TABLE_NAME_POLICY in self.created_tables["inet"]:
- rules.append({"delete": {"table": {"family": "inet",
- "name": TABLE_NAME_POLICY}}})
self.created_tables["inet"].remove(TABLE_NAME_POLICY)
else:
FirewallError(UNKNOWN_ERROR, "not implemented")
--
2.39.3

View File

@ -0,0 +1,229 @@
From a965defecfad03530f3374271fb4889ff895ab1c Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 12:16:44 -0400
Subject: [PATCH 17/22] v2.2.0: feat(IPv6_rpfilter): support loose-forward
rpfilter
This adds a new config value for IPv6_rpfilter, loose-forward. In this
mode the rpfilter occurs only for forwarded packets using the "loose"
algorithm from RFC 3704. This is a significant performance improvement
for end-stations that have a single default route because the RPF check
is completely absent on INPUT.
This new value is NOT compatible with the iptables backend. This is
enforced by configuration checks.
(cherry picked from commit fb692d2e560253c97c7fdd1e9fdbfcc8c61d0eba)
---
config/firewalld.conf | 2 ++
doc/xml/firewalld.conf.xml | 2 ++
src/firewall/config/__init__.py.in | 3 ++-
src/firewall/core/fw.py | 2 ++
src/firewall/core/io/firewalld_conf.py | 15 +++++++++++
src/firewall/core/nftables.py | 30 ++++++++++++++-------
src/tests/features/rpfilter.at | 36 ++++++++++++++++++++++++++
7 files changed, 79 insertions(+), 11 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index c35caf9f152b..5d0777d54b15 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -36,6 +36,8 @@ Lockdown=no
# verifies that there is a route back to the source through any
# interface; even if it's not the same one on which the packet
# arrived.
+# - loose-forward: This is almost identical to "loose", but does not perform
+# RPF for packets targeted to the host (INPUT).
# - no: RPF is completely disabled.
#
# The rp_filter for IPv4 is controlled using sysctl.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 279a149ab2a1..1303758347e2 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -131,6 +131,8 @@
verifies that there is a route back to the source through any
interface; even if it's not the same one on which the packet
arrived.
+ - loose-forward: This is almost identical to "loose", but does not perform
+ RPF for packets targeted to the host (INPUT).
- no: RPF is completely disabled.
The rp_filter for IPv4 is controlled using sysctl.
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 6bfe96be35a6..f2c4c9f5afa1 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,7 +120,8 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
-IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose"]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose",
+ "loose-forward"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index d9724e9c8534..8c20a4a606e2 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -335,6 +335,8 @@ class Firewall(object):
self._ipv6_rpfilter = "strict"
elif value.lower() in ["loose"]:
self._ipv6_rpfilter = "loose"
+ elif value.lower() in ["loose-forward"]:
+ self._ipv6_rpfilter = "loose-forward"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 9ad64883b656..159715df7ede 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -26,6 +26,7 @@ import shutil
from firewall import config
from firewall.core.logger import log
+from firewall import errors
valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
@@ -81,6 +82,18 @@ class firewalld_conf(object):
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ def sanity_check(self):
+ if (
+ self.get("FirewallBackend") == "iptables"
+ and self.get("IPv6_rpfilter") == "loose-forward"
+ ):
+ raise errors.FirewallError(
+ errors.INVALID_VALUE,
+ "IPv6_rpfilter=loose-forward is incompatible "
+ "with FirewallBackend=iptables. This is a limitation "
+ "of the iptables backend.",
+ )
+
# load self.filename
def read(self):
self.clear()
@@ -238,6 +251,8 @@ class firewalld_conf(object):
config.FALLBACK_ALLOW_ZONE_DRIFTING)
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ self.sanity_check()
+
# save to self.filename if there are key/value changes
def write(self):
if len(self._config) < 1:
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 9827e84042ef..da2d8eb8ec29 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1614,9 +1614,13 @@ class nftables(object):
def build_rpfilter_rules(self, log_denied=False):
rules = []
+ rpfilter_chain = "filter_PREROUTING"
if self._fw._ipv6_rpfilter == "loose":
fib_flags = ["saddr", "mark"]
+ elif self._fw._ipv6_rpfilter == "loose-forward":
+ fib_flags = ["saddr", "mark"]
+ rpfilter_chain = "filter_FORWARD"
else:
fib_flags = ["saddr", "mark", "iif"]
@@ -1633,17 +1637,19 @@ class nftables(object):
rules.append({"insert": {"rule": {"family": "inet",
"table": TABLE_NAME,
- "chain": "filter_PREROUTING",
+ "chain": rpfilter_chain,
"expr": expr_fragments}}})
# RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
- rules.append({"insert": {"rule": {"family": "inet",
- "table": TABLE_NAME,
- "chain": "filter_PREROUTING",
- "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6",
- "field": "type"}},
- "op": "==",
- "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}},
- {"accept": None}]}}})
+ if self._fw._ipv6_rpfilter != "loose-forward":
+ # this rule doesn't make sense for forwarded packets
+ rules.append({"insert": {"rule": {"family": "inet",
+ "table": TABLE_NAME,
+ "chain": rpfilter_chain,
+ "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6",
+ "field": "type"}},
+ "op": "==",
+ "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}},
+ {"accept": None}]}}})
return rules
def build_rfc3964_ipv4_rules(self):
@@ -1674,7 +1680,11 @@ class nftables(object):
"chain": "filter_OUTPUT",
"index": 1,
"expr": expr_fragments}}})
- forward_index = 4 if self._fw.get_log_denied() != "off" else 3
+ forward_index = 3
+ if self._fw.get_log_denied() != "off":
+ forward_index += 1
+ if self._fw._ipv6_rpfilter == "loose-forward":
+ forward_index += 1
rules.append({"add": {"rule": {"family": "inet",
"table": TABLE_NAME,
"chain": "filter_FORWARD",
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 23cd9e0e8d7f..5c25ed7e16f0 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -50,6 +50,42 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - loose-forward])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+CHECK_NFTABLES_FIB_IN_FORWARD()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=loose-forward/' ./firewalld.conf])
+m4_if(iptables, FIREWALL_BACKEND, [
+FWD_RELOAD(114, [ignore], [ignore])
+], [
+FWD_RELOAD()
+])
+
+NFT_LIST_RULES([inet], [filter_FORWARD], 0, [dnl
+ table inet firewalld {
+ chain filter_FORWARD {
+ meta nfproto ipv6 fib saddr . mark oif missing drop
+ ct state established,related accept
+ ct status dnat accept
+ iifname "lo" accept
+ ct state invalid drop
+ ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 addr-unreachable
+ jump filter_FORWARD_ZONES
+ reject with icmpx admin-prohibited
+ }
+ }
+])
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ }
+ }
+])
+
+FWD_END_TEST([-e "/^ERROR: INVALID_VALUE:/d"])
+
FWD_START_TEST([rpfilter - config values])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -1,242 +0,0 @@
From 0715e07a68d50d33797a724d24157a96afee3de6 Mon Sep 17 00:00:00 2001
From: Derek Dai <daiderek@gmail.com>
Date: Tue, 10 Nov 2020 20:37:36 +0800
Subject: [PATCH 18/26] v1.0.0: feat(rich): support using ipset in destination
Fixes: #706
Closes: #711
(cherry picked from commit 286d00031f431f3c3d0f94028975a409e78be8c8)
---
doc/xml/firewalld.richlanguage.xml | 2 +-
src/firewall/core/io/policy.py | 21 ++++++++++----
src/firewall/core/io/zone.py | 4 +--
src/firewall/core/ipXtables.py | 25 +++++++++++------
src/firewall/core/nftables.py | 7 ++++-
src/firewall/core/rich.py | 44 ++++++++++++++++++++++--------
6 files changed, 74 insertions(+), 29 deletions(-)
diff --git a/doc/xml/firewalld.richlanguage.xml b/doc/xml/firewalld.richlanguage.xml
index e336bfd0b464..19bd038fc1fd 100644
--- a/doc/xml/firewalld.richlanguage.xml
+++ b/doc/xml/firewalld.richlanguage.xml
@@ -129,7 +129,7 @@ source [not] address="address[/mask]"|mac="mac-address"|ipset="ipset"
<title>Destination</title>
<para>
<programlisting>
-destination [not] address="address[/mask]"
+destination [not] address="address[/mask]"|ipset="ipset"
</programlisting>
With the destination address the target can be limited to the destination address. The destination address is using the same syntax as the source address.
</para>
diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py
index c543aa1b42a6..3b951545e975 100644
--- a/src/firewall/core/io/policy.py
+++ b/src/firewall/core/io/policy.py
@@ -186,11 +186,18 @@ def common_startElement(obj, name, attrs):
str(obj._rule))
return True
invert = False
+ address = None
+ if "address" in attrs:
+ address = attrs["address"]
+ ipset = None
+ if "ipset" in attrs:
+ ipset = attrs["ipset"]
if "invert" in attrs and \
attrs["invert"].lower() in [ "yes", "true" ]:
invert = True
- obj._rule.destination = rich.Rich_Destination(attrs["address"],
- invert)
+ obj._rule.destination = rich.Rich_Destination(address,
+ ipset,
+ invert)
elif name in [ "accept", "reject", "drop", "mark" ]:
if not obj._rule:
@@ -447,7 +454,11 @@ def common_writer(obj, handler):
# destination
if rule.destination:
- attrs = { "address": rule.destination.addr }
+ attrs = { }
+ if rule.destination.addr:
+ attrs["address"] = rule.destination.addr
+ if rule.destination.ipset:
+ attrs["ipset"] = rule.destination.ipset
if rule.destination.invert:
attrs["invert"] = "True"
handler.ignorableWhitespace(" ")
@@ -607,7 +618,7 @@ class Policy(IO_Object):
"forward-port": [ "port", "protocol" ],
"rule": None,
"source": None,
- "destination": [ "address" ],
+ "destination": None,
"protocol": [ "value" ],
"source-port": [ "port", "protocol" ],
"log": None,
@@ -625,7 +636,7 @@ class Policy(IO_Object):
"forward-port": [ "to-port", "to-addr" ],
"rule": [ "family", "priority" ],
"source": [ "address", "mac", "invert", "family", "ipset" ],
- "destination": [ "invert" ],
+ "destination": [ "address", "invert", "ipset" ],
"log": [ "prefix", "level" ],
"reject": [ "type" ],
}
diff --git a/src/firewall/core/io/zone.py b/src/firewall/core/io/zone.py
index 4291ec9cba00..0c419ee0f2bd 100644
--- a/src/firewall/core/io/zone.py
+++ b/src/firewall/core/io/zone.py
@@ -73,7 +73,7 @@ class Zone(IO_Object):
"interface": [ "name" ],
"rule": None,
"source": None,
- "destination": [ "address" ],
+ "destination": None,
"protocol": [ "value" ],
"source-port": [ "port", "protocol" ],
"log": None,
@@ -91,7 +91,7 @@ class Zone(IO_Object):
"forward-port": [ "to-port", "to-addr" ],
"rule": [ "family", "priority" ],
"source": [ "address", "mac", "invert", "family", "ipset" ],
- "destination": [ "invert" ],
+ "destination": [ "address", "invert", "ipset" ],
"log": [ "prefix", "level" ],
"reject": [ "type" ],
}
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index cf6c6e03e7ad..401377104ce1 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -1093,15 +1093,22 @@ class ip4tables(object):
return []
rule_fragment = []
- if rich_dest.invert:
- rule_fragment.append("!")
- if check_single_address("ipv6", rich_dest.addr):
- rule_fragment += [ "-d", normalizeIP6(rich_dest.addr) ]
- elif check_address("ipv6", rich_dest.addr):
- addr_split = rich_dest.addr.split("/")
- rule_fragment += [ "-d", normalizeIP6(addr_split[0]) + "/" + addr_split[1] ]
- else:
- rule_fragment += [ "-d", rich_dest.addr ]
+ if rich_dest.addr:
+ if rich_dest.invert:
+ rule_fragment.append("!")
+ if check_single_address("ipv6", rich_dest.addr):
+ rule_fragment += [ "-d", normalizeIP6(rich_dest.addr) ]
+ elif check_address("ipv6", rich_dest.addr):
+ addr_split = rich_dest.addr.split("/")
+ rule_fragment += [ "-d", normalizeIP6(addr_split[0]) + "/" + addr_split[1] ]
+ else:
+ rule_fragment += [ "-d", rich_dest.addr ]
+ elif rich_dest.ipset:
+ rule_fragment += [ "-m", "set" ]
+ if rich_dest.invert:
+ rule_fragment.append("!")
+ flags = self._fw.zone._ipset_match_flags(rich_dest.ipset, "dst")
+ rule_fragment += [ "--match-set", rich_dest.ipset, flags ]
return rule_fragment
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 2a13b2678a94..d238451ebd5d 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1253,7 +1253,12 @@ class nftables(object):
def _rich_rule_destination_fragment(self, rich_dest):
if not rich_dest:
return {}
- return self._rule_addr_fragment("daddr", rich_dest.addr, invert=rich_dest.invert)
+ if rich_dest.addr:
+ address = rich_dest.addr
+ elif rich_dest.ipset:
+ address = "ipset:" + rich_dest.ipset
+
+ return self._rule_addr_fragment("daddr", address, invert=rich_dest.invert)
def _rich_rule_source_fragment(self, rich_source):
if not rich_source:
diff --git a/src/firewall/core/rich.py b/src/firewall/core/rich.py
index 03bc194c2b28..6a03eeca5d8a 100644
--- a/src/firewall/core/rich.py
+++ b/src/firewall/core/rich.py
@@ -63,13 +63,27 @@ class Rich_Source(object):
"no address, mac and ipset")
class Rich_Destination(object):
- def __init__(self, addr, invert=False):
+ def __init__(self, addr, ipset, invert=False):
self.addr = addr
+ if self.addr == "":
+ self.addr = None
+ self.ipset = ipset
+ if self.ipset == "":
+ self.ipset = None
self.invert = invert
+ if self.addr is None and self.ipset is None:
+ raise FirewallError(errors.INVALID_RULE,
+ "no address and ipset")
def __str__(self):
- return 'destination %saddress="%s"' % ("not " if self.invert else "",
- self.addr)
+ ret = 'destination%s ' % (" NOT" if self.invert else "")
+ if self.addr is not None:
+ return ret + 'address="%s"' % self.addr
+ elif self.ipset is not None:
+ return ret + 'ipset="%s"' % self.ipset
+ else:
+ raise FirewallError(errors.INVALID_RULE,
+ "no address and ipset")
class Rich_Service(object):
def __init__(self, name):
@@ -404,12 +418,12 @@ class Rich_Rule(object):
attrs.clear()
index = index -1 # return token to input
elif in_element == 'destination':
- if attr_name in ['address', 'invert']:
+ if attr_name in ['address', 'ipset', 'invert']:
attrs[attr_name] = attr_value
elif element in ['not', 'NOT']:
attrs['invert'] = True
else:
- self.destination = Rich_Destination(attrs.get('address'), attrs.get('invert'))
+ self.destination = Rich_Destination(attrs.get('address'), attrs.get('ipset'), attrs.get('invert', False))
in_elements.pop() # destination
attrs.clear()
index = index -1 # return token to input
@@ -587,12 +601,20 @@ class Rich_Rule(object):
# destination
if self.destination is not None:
- if self.family is None:
- raise FirewallError(errors.INVALID_FAMILY)
- if self.destination.addr is None or \
- not functions.check_address(self.family,
- self.destination.addr):
- raise FirewallError(errors.INVALID_ADDR, str(self.destination.addr))
+ if self.destination.addr is not None:
+ if self.family is None:
+ raise FirewallError(errors.INVALID_FAMILY)
+ if self.destination.ipset is not None:
+ raise FirewallError(errors.INVALID_DESTINATION, "address and ipset")
+ if not functions.check_address(self.family, self.destination.addr):
+ raise FirewallError(errors.INVALID_ADDR, str(self.destination.addr))
+
+ elif self.destination.ipset is not None:
+ if not check_ipset_name(self.destination.ipset):
+ raise FirewallError(errors.INVALID_IPSET, str(self.destination.ipset))
+
+ else:
+ raise FirewallError(errors.INVALID_RULE, "invalid destination")
# service
if type(self.element) == Rich_Service:
--
2.43.0

View File

@ -0,0 +1,183 @@
From 7629e1c24b73d8c1e56d275c07076bc3bf99caad Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 17 May 2024 10:05:04 -0400
Subject: [PATCH 18/22] v2.2.0: feat(IPv6_rpfilter): support strict-forward
rpfilter
(cherry picked from commit 98e5cbb862885e00981611a79f7a3186da5d1050)
---
config/firewalld.conf | 2 ++
doc/xml/firewalld.conf.xml | 5 +++-
src/firewall/config/__init__.py.in | 2 +-
src/firewall/core/fw.py | 2 ++
src/firewall/core/io/firewalld_conf.py | 8 +++---
src/firewall/core/nftables.py | 7 +++--
src/tests/features/rpfilter.at | 36 ++++++++++++++++++++++++++
7 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 5d0777d54b15..cf95af3eea8e 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -36,6 +36,8 @@ Lockdown=no
# verifies that there is a route back to the source through any
# interface; even if it's not the same one on which the packet
# arrived.
+# - strict-forward: This is almost identical to "strict", but does not perform
+# RPF for packets targeted to the host (INPUT).
# - loose-forward: This is almost identical to "loose", but does not perform
# RPF for packets targeted to the host (INPUT).
# - no: RPF is completely disabled.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 1303758347e2..4b9bfdad15be 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -131,6 +131,8 @@
verifies that there is a route back to the source through any
interface; even if it's not the same one on which the packet
arrived.
+ - strict-forward: This is almost identical to "loose", but does not perform
+ RPF for packets targeted to the host (INPUT).
- loose-forward: This is almost identical to "loose", but does not perform
RPF for packets targeted to the host (INPUT).
- no: RPF is completely disabled.
@@ -144,7 +146,8 @@
the established connections fast path. As such it can have a
significant performance impact if there is a lot of traffic. It's
enabled by default for security, but can be disabled if performance is
- a concern.
+ a concern. Alternatively one of the variants that only does RPF on
+ forwarded packets may be used.
</para>
</listitem>
</varlistentry>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index f2c4c9f5afa1..2eda337e8180 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -121,7 +121,7 @@ LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose",
- "loose-forward"]
+ "loose-forward", "strict-forward"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 8c20a4a606e2..f91ff53fc37a 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -337,6 +337,8 @@ class Firewall(object):
self._ipv6_rpfilter = "loose"
elif value.lower() in ["loose-forward"]:
self._ipv6_rpfilter = "loose-forward"
+ elif value.lower() in ["strict-forward"]:
+ self._ipv6_rpfilter = "strict-forward"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 159715df7ede..20072e26cfcd 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -83,13 +83,13 @@ class firewalld_conf(object):
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
def sanity_check(self):
- if (
- self.get("FirewallBackend") == "iptables"
- and self.get("IPv6_rpfilter") == "loose-forward"
+ if self.get("FirewallBackend") == "iptables" and self.get("IPv6_rpfilter") in (
+ "loose-forward",
+ "strict-forward",
):
raise errors.FirewallError(
errors.INVALID_VALUE,
- "IPv6_rpfilter=loose-forward is incompatible "
+ f"IPv6_rpfilter={self.get('IPv6_rpfilter')} is incompatible "
"with FirewallBackend=iptables. This is a limitation "
"of the iptables backend.",
)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index da2d8eb8ec29..5a49b34e3a4f 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1621,6 +1621,9 @@ class nftables(object):
elif self._fw._ipv6_rpfilter == "loose-forward":
fib_flags = ["saddr", "mark"]
rpfilter_chain = "filter_FORWARD"
+ elif self._fw._ipv6_rpfilter == "strict-forward":
+ fib_flags = ["saddr", "mark", "iif"]
+ rpfilter_chain = "filter_FORWARD"
else:
fib_flags = ["saddr", "mark", "iif"]
@@ -1640,7 +1643,7 @@ class nftables(object):
"chain": rpfilter_chain,
"expr": expr_fragments}}})
# RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
- if self._fw._ipv6_rpfilter != "loose-forward":
+ if self._fw._ipv6_rpfilter not in ("loose-forward", "strict-forward"):
# this rule doesn't make sense for forwarded packets
rules.append({"insert": {"rule": {"family": "inet",
"table": TABLE_NAME,
@@ -1683,7 +1686,7 @@ class nftables(object):
forward_index = 3
if self._fw.get_log_denied() != "off":
forward_index += 1
- if self._fw._ipv6_rpfilter == "loose-forward":
+ if self._fw._ipv6_rpfilter in ("loose-forward", "strict-forward"):
forward_index += 1
rules.append({"add": {"rule": {"family": "inet",
"table": TABLE_NAME,
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 5c25ed7e16f0..9ad50c993ba0 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -50,6 +50,42 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - strict-forward])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+CHECK_NFTABLES_FIB_IN_FORWARD()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=strict-forward/' ./firewalld.conf])
+m4_if(iptables, FIREWALL_BACKEND, [
+FWD_RELOAD(114, [ignore], [ignore])
+], [
+FWD_RELOAD()
+])
+
+NFT_LIST_RULES([inet], [filter_FORWARD], 0, [dnl
+ table inet firewalld {
+ chain filter_FORWARD {
+ meta nfproto ipv6 fib saddr . mark . iif oif missing drop
+ ct state established,related accept
+ ct status dnat accept
+ iifname "lo" accept
+ ct state invalid drop
+ ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 addr-unreachable
+ jump filter_FORWARD_ZONES
+ reject with icmpx admin-prohibited
+ }
+ }
+])
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ }
+ }
+])
+
+FWD_END_TEST([-e "/^ERROR: INVALID_VALUE:/d"])
+
FWD_START_TEST([rpfilter - loose-forward])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -1,60 +0,0 @@
From cf8a55d1fe769a9e4632fbccf5ae4738ab661421 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Thu, 12 Nov 2020 17:11:58 -0500
Subject: [PATCH 19/26] v1.0.0: test(rich): destination ipset
(cherry picked from commit f274bfd0f7bc0e466c42b732e03002e11e99ed88)
---
src/tests/features/features.at | 1 +
src/tests/features/rich_destination_ipset.at | 30 ++++++++++++++++++++
2 files changed, 31 insertions(+)
create mode 100644 src/tests/features/rich_destination_ipset.at
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index 2340853aeca7..381bf6dba0e4 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -13,3 +13,4 @@ m4_include([features/rich_rules.at])
m4_include([features/icmp_blocks.at])
m4_include([features/rpfilter.at])
m4_include([features/zone_combine.at])
+m4_include([features/rich_destination_ipset.at])
diff --git a/src/tests/features/rich_destination_ipset.at b/src/tests/features/rich_destination_ipset.at
new file mode 100644
index 000000000000..c07809141851
--- /dev/null
+++ b/src/tests/features/rich_destination_ipset.at
@@ -0,0 +1,30 @@
+FWD_START_TEST([rich destination ipset])
+AT_KEYWORDS(rich ipset)
+
+FWD_CHECK([--permanent --new-ipset=foobar --type=hash:ip], 0, [ignore])
+FWD_RELOAD
+
+FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
+FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
+NFT_LIST_RULES([inet], [filter_IN_public_allow], 0, [dnl
+ table inet firewalld {
+ chain filter_IN_public_allow {
+ tcp dport 22 ct state new,untracked accept
+ ip6 daddr fe80::/64 udp dport 546 ct state new,untracked accept
+ ip daddr @foobar accept
+ }
+ }
+])
+IPTABLES_LIST_RULES([filter], [IN_public_allow], 0, [dnl
+ ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 ctstate NEW,UNTRACKED
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set foobar dst
+])
+
+dnl negative tests
+FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
+FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
+FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
+FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
+
+FWD_END_TEST([-e '/ERROR: INVALID_RULE: bad attribute/d'dnl
+ -e '/ERROR: INVALID_DESTINATION: address and ipset/d'])
--
2.43.0

View File

@ -0,0 +1,31 @@
From e031ce8e41d2fc23735be91f7070127f8812b490 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 21 Jun 2024 16:17:44 -0400
Subject: [PATCH 19/22] v2.2.0: test(functions): start firewalld with file
logging
This is a test environment. Trying to log to syslog does not make sense.
The default, mixed, sends to both file and syslog, but with different
log level settings.
(cherry picked from commit 5e0c28d5f7f71a216485169610d8c72a3a3518d1)
---
src/tests/functions.at | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index b2372dd4075b..d1c89ed5b982 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -9,7 +9,7 @@ m4_define([FWD_STOP_FIREWALLD], [
])
m4_define([FWD_START_FIREWALLD], [
- FIREWALLD_ARGS="--nofork --nopid --log-file ./firewalld.log --system-config ./"
+ FIREWALLD_ARGS="--nofork --nopid --log-file ./firewalld.log --log-target file --system-config ./"
dnl if testsuite ran with debug flag, add debug output
${at_debug_p} && FIREWALLD_ARGS="--debug=9 ${FIREWALLD_ARGS}"
if test "x${FIREWALLD_DEFAULT_CONFIG}" != x ; then
--
2.43.5

View File

@ -1,63 +0,0 @@
From 63100ca625942e6be2c68422e7a48bc68f8d01c5 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 13 Nov 2020 13:32:22 -0500
Subject: [PATCH 20/26] v1.0.0: test(rich): destination ipset: verify policy
support
(cherry picked from commit fdd120572cd45a6ea2515bc906b89482de6560ea)
---
src/tests/features/rich_destination_ipset.at | 23 ++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/tests/features/rich_destination_ipset.at b/src/tests/features/rich_destination_ipset.at
index c07809141851..3286755d2252 100644
--- a/src/tests/features/rich_destination_ipset.at
+++ b/src/tests/features/rich_destination_ipset.at
@@ -1,9 +1,14 @@
FWD_START_TEST([rich destination ipset])
AT_KEYWORDS(rich ipset)
+FWD_CHECK([--permanent --new-policy=mypolicy], 0, [ignore])
+FWD_CHECK([--permanent --policy=mypolicy --add-ingress-zone ANY], 0, [ignore])
+FWD_CHECK([--permanent --policy=mypolicy --add-egress-zone HOST], 0, [ignore])
+
FWD_CHECK([--permanent --new-ipset=foobar --type=hash:ip], 0, [ignore])
FWD_RELOAD
+dnl zone
FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
NFT_LIST_RULES([inet], [filter_IN_public_allow], 0, [dnl
@@ -20,11 +25,29 @@ IPTABLES_LIST_RULES([filter], [IN_public_allow], 0, [dnl
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set foobar dst
])
+dnl policy
+FWD_CHECK([--permanent --policy mypolicy --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
+FWD_CHECK([ --policy mypolicy --add-rich-rule='rule family=ipv4 destination ipset=foobar accept'], 0, [ignore])
+NFT_LIST_RULES([inet], [filter_IN_policy_mypolicy_allow], 0, [dnl
+ table inet firewalld {
+ chain filter_IN_policy_mypolicy_allow {
+ ip daddr @foobar accept
+ }
+ }
+])
+IPTABLES_LIST_RULES([filter], [IN_mypolicy_allow], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set foobar dst
+])
+
dnl negative tests
FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
FWD_CHECK([--permanent --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
FWD_CHECK([ --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
+FWD_CHECK([--permanent --policy mypolicy --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
+FWD_CHECK([ --policy mypolicy --add-rich-rule='rule family=ipv4 destination bogus=foobar accept'], 122, [ignore], [ignore])
+FWD_CHECK([--permanent --policy mypolicy --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
+FWD_CHECK([ --policy mypolicy --add-rich-rule='rule family=ipv4 destination address=10.0.0.1 ipset=foobar accept'], 121, [ignore], [ignore])
FWD_END_TEST([-e '/ERROR: INVALID_RULE: bad attribute/d'dnl
-e '/ERROR: INVALID_DESTINATION: address and ipset/d'])
--
2.43.0

View File

@ -0,0 +1,417 @@
From ce75ad2920f457d5b2ce578ead5d8c64f0daa490 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 21 Jun 2024 17:38:29 -0400
Subject: [PATCH 20/22] v2.2.0: feat(nftables): table ownership
This allows using the nftables table flags "owner" and "persist". When
these are in use ONLY firewalld will be able to change the firewalld
table/rules. All other processes will be blocked. This enhances
firewalld robustness by guaranteeing that firewalld's rules can not be
modified without it's knowledge, e.g. by other entities.
This also allows firewalld and nftables services to coexist. An "nft
flush ruleset" will NOT flush the "firewalld" table.
Fixes: RHEL-17002
(cherry picked from commit becd083fc2905921651af73cb15ce8c9aba9203b)
---
config/firewalld.conf | 8 +++
doc/xml/firewalld.conf.xml | 13 ++++
doc/xml/firewalld.dbus.xml | 11 ++++
src/firewall/config/__init__.py.in | 1 +
src/firewall/core/fw.py | 22 +++++++
src/firewall/core/io/firewalld_conf.py | 6 +-
src/firewall/core/nftables.py | 83 ++++++++++++++++++++++----
src/firewall/server/config.py | 16 +++--
src/tests/dbus/firewalld.conf.at | 2 +
src/tests/regression/rhbz2222044.at | 5 ++
10 files changed, 150 insertions(+), 17 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index cf95af3eea8e..28345a13d54e 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -93,3 +93,11 @@ ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
# internet.
# Defaults to "yes".
RFC3964_IPv4=yes
+
+# NftablesTableOwner
+# If set to yes, the generated nftables rule set will be owned exclusively by
+# firewalld. This prevents other entities from mistakenly (or maliciously)
+# modifying firewalld's rule set. If you intentionally modify firewalld's
+# rules, then you will have to set this to "no".
+# Defaults to "yes".
+NftablesTableOwner=yes
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 4b9bfdad15be..cf7134c04012 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -247,6 +247,19 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>NftablesTableOwner</option></term>
+ <listitem>
+ <para>
+ If set to yes, the generated nftables rule set will be owned exclusively by
+ firewalld. This prevents other entities from mistakenly (or maliciously)
+ modifying firewalld's rule set. If you intentionally modify firewalld's
+ rules, then you will have to set this to "no".
+ Defaults to "yes".
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index f04cf5ae757b..00d7e8fbe5b1 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2905,6 +2905,17 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry id="FirewallD1.config.Properties.NftablesTableOwner">
+ <term>NftablesTableOwner - s - (rw)</term>
+ <listitem>
+ <para>
+ If set to yes, the generated nftables rule set will be owned exclusively by
+ firewalld. This prevents other entities from mistakenly (or maliciously)
+ modifying firewalld's rule set. If you intentionally modify firewalld's
+ rules, then you will have to set this to "no".
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect3>
</refsect2>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 2eda337e8180..16291fcf795a 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -138,3 +138,4 @@ FALLBACK_FLUSH_ALL_ON_RELOAD = True
FALLBACK_RELOAD_POLICY = "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP"
FALLBACK_RFC3964_IPV4 = True
FALLBACK_ALLOW_ZONE_DRIFTING = False
+FALLBACK_NFTABLES_TABLE_OWNER = True
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index f91ff53fc37a..557b6e527dbd 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -119,6 +119,7 @@ class Firewall(object):
self._flush_all_on_reload = config.FALLBACK_FLUSH_ALL_ON_RELOAD
self._rfc3964_ipv4 = config.FALLBACK_RFC3964_IPV4
self._allow_zone_drifting = config.FALLBACK_ALLOW_ZONE_DRIFTING
+ self._nftables_table_owner = config.FALLBACK_NFTABLES_TABLE_OWNER
if self._offline:
self.ip4tables_enabled = False
@@ -290,6 +291,17 @@ class Firewall(object):
log.debug1("ebtables-restore is not supporting the --noflush "
"option, will therefore not be used")
+ self.nftables_backend.probe_support()
+
+ if (
+ self._nftables_table_owner
+ and not self.nftables_backend.supports_table_owner
+ ):
+ log.info1(
+ "Configuration has NftablesTableOwner=True, but it's "
+ "not supported by nftables. Table ownership will be disabled."
+ )
+
def _start_load_firewalld_conf(self):
# load firewalld config
log.debug1("Loading firewalld config file '%s'", config.FIREWALLD_CONF)
@@ -378,6 +390,16 @@ class Firewall(object):
log.debug1("RFC3964_IPv4 is set to '%s'",
self._rfc3964_ipv4)
+ if self._firewalld_conf.get("NftablesTableOwner"):
+ value = self._firewalld_conf.get("NftablesTableOwner")
+ if value.lower() in ["no", "false"]:
+ self._nftables_table_owner = False
+ else:
+ self._nftables_table_owner = True
+ log.debug1(
+ "NftablesTableOwner is set to '%s'", self._nftables_table_owner
+ )
+
self.config.set_firewalld_conf(copy.deepcopy(self._firewalld_conf))
def _start_load_lockdown_whitelist(self):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 20072e26cfcd..d5b47666c0f0 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -32,7 +32,7 @@ valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "ReloadPolicy" ]
+ "AllowZoneDrifting", "ReloadPolicy", "NftablesTableOwner" ]
class firewalld_conf(object):
def __init__(self, filename):
@@ -81,6 +81,10 @@ class firewalld_conf(object):
self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ self.set(
+ "NftablesTableOwner",
+ "yes" if config.FALLBACK_NFTABLES_TABLE_OWNER else "no",
+ )
def sanity_check(self):
if self.get("FirewallBackend") == "iptables" and self.get("IPv6_rpfilter") in (
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 5a49b34e3a4f..8115bcb9d7f4 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -36,6 +36,7 @@ from nftables.nftables import Nftables
TABLE_NAME = "firewalld"
TABLE_NAME_POLICY = TABLE_NAME + "_" + "policy_drop"
+TABLE_NAME_PROBE = TABLE_NAME + "_" + "probe"
POLICY_CHAIN_PREFIX = "policy_"
# Map iptables (table, chain) to hooks and priorities.
@@ -169,6 +170,7 @@ class nftables(object):
def __init__(self, fw):
self._fw = fw
self.restore_command_exists = True
+ self.supports_table_owner = False
self.available_tables = []
self.rule_to_handle = {}
self.rule_ref_count = {}
@@ -180,6 +182,57 @@ class nftables(object):
self.nftables.set_echo_output(True)
self.nftables.set_handle_output(True)
+ def _probe_support_table_owner(self):
+ try:
+ rules = {
+ "nftables": [
+ {"metainfo": {"json_schema_version": 1}},
+ {
+ "add": {
+ "table": {
+ "family": "inet",
+ "name": TABLE_NAME_PROBE,
+ "flags": ["owner", "persist"],
+ }
+ }
+ },
+ ]
+ }
+
+ rc, output, _ = self.nftables.json_cmd(rules)
+ if rc:
+ raise ValueError("nftables probe table owner failed")
+
+ # old nftables versions would ignore table flags in JSON, so we
+ # must parse back and verify the flags are set.
+ rules = {
+ "nftables": [
+ {"metainfo": {"json_schema_version": 1}},
+ {"list": {"table": {"family": "inet", "name": TABLE_NAME_PROBE}}},
+ ]
+ }
+ self.nftables.set_echo_output(False)
+ rc, output, _ = self.nftables.json_cmd(rules)
+ self.nftables.set_echo_output(True)
+ flags = output["nftables"][1]["table"]["flags"]
+
+ self.set_rule(
+ {"delete": {"table": {"family": "inet", "name": TABLE_NAME_PROBE}}},
+ self._fw.get_log_denied(),
+ )
+
+ if "owner" not in flags or "persist" not in flags:
+ raise ValueError("nftables probe table owner failed")
+
+ log.debug2("nftables: probe_support(): owner flag is supported.")
+ self.supports_table_owner = True
+ except:
+ log.debug2("nftables: probe_support(): owner flag is NOT supported.")
+ self.supports_table_owner = False
+
+ def probe_support(self):
+ self._probe_support_table_owner()
+
def _run_replace_zone_source(self, rule, zone_source_index_cache):
for verb in ["add", "insert", "delete"]:
if verb in rule:
@@ -401,16 +454,27 @@ class nftables(object):
# Tables always exist in nftables
return [table] if table else IPTABLES_TO_NFT_HOOK.keys()
+ def _build_add_table_rules(self, table):
+ rule = {"add": {"table": {"family": "inet", "name": table}}}
+
+ if (
+ table == TABLE_NAME
+ and self._fw._nftables_table_owner
+ and self.supports_table_owner
+ ):
+ rule["add"]["table"]["flags"] = ["owner", "persist"]
+
+ return [rule]
+
def _build_delete_table_rules(self, table):
# To avoid nftables returning ENOENT we always add the table before
# deleting to guarantee it will exist.
#
# In the future, this add+delete should be replaced with "destroy", but
# that verb is too new to rely upon.
- return [{"add": {"table": {"family": "inet",
- "name": table}}},
- {"delete": {"table": {"family": "inet",
- "name": table}}}]
+ return self._build_add_table_rules(table) + [
+ {"delete": {"table": {"family": "inet", "name": table}}},
+ ]
def build_flush_rules(self):
self.rule_to_handle = {}
@@ -437,8 +501,7 @@ class nftables(object):
# a higher priority than our base chains is sufficient.
rules = []
if policy == "PANIC":
- rules.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME_POLICY}}})
+ rules.extend(self._build_add_table_rules(TABLE_NAME_POLICY))
# Use "raw" priority for panic mode. This occurs before
# conntrack, mangle, nat, etc
@@ -451,8 +514,7 @@ class nftables(object):
"prio": -300 + NFT_HOOK_OFFSET - 1,
"policy": "drop"}}})
elif policy == "DROP":
- rules.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME_POLICY}}})
+ rules.extend(self._build_add_table_rules(TABLE_NAME_POLICY))
# To drop everything except existing connections we use
# "filter" because it occurs _after_ conntrack.
@@ -511,10 +573,7 @@ class nftables(object):
return list(supported)
def build_default_tables(self):
- default_tables = []
- default_tables.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME}}})
- return default_tables
+ return self._build_add_table_rules(TABLE_NAME)
def build_default_rules(self, log_denied="off"):
default_rules = []
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index b805e497bb05..c07a1e6a2503 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -110,6 +110,7 @@ class FirewallDConfig(DbusServiceObject):
"FlushAllOnReload": "readwrite",
"RFC3964_IPv4": "readwrite",
"AllowZoneDrifting": "readwrite",
+ "NftablesTableOwner": "readwrite",
}
)
@@ -565,7 +566,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "IPv6_rpfilter2" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2", "NftablesTableOwner" ]:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
"Property '%s' does not exist" % prop)
@@ -631,6 +632,10 @@ class FirewallDConfig(DbusServiceObject):
if value is None:
value = "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no"
return dbus.String(value)
+ elif prop == "NftablesTableOwner":
+ if value is None:
+ value = "yes" if config.FALLBACK_NFTABLES_TABLE_OWNER else "no"
+ return dbus.String(value)
@dbus_handle_exceptions
def _get_dbus_property(self, prop):
@@ -662,6 +667,8 @@ class FirewallDConfig(DbusServiceObject):
return dbus.String(self._get_property(prop))
elif prop == "AllowZoneDrifting":
return dbus.String(self._get_property(prop))
+ elif prop == "NftablesTableOwner":
+ return dbus.String(self._get_property(prop))
else:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
@@ -701,7 +708,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "IPv6_rpfilter2" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2", "NftablesTableOwner" ]:
ret[x] = self._get_property(x)
elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
@@ -730,11 +737,12 @@ class FirewallDConfig(DbusServiceObject):
"IPv6_rpfilter", "IndividualCalls",
"LogDenied",
"FirewallBackend", "FlushAllOnReload",
- "RFC3964_IPv4", "IPv6_rpfilter2"]:
+ "RFC3964_IPv4", "IPv6_rpfilter2",
+ "NftablesTableOwner" ]:
if property_name in [ "CleanupOnExit", "CleanupModulesOnExit",
"Lockdown", "IPv6_rpfilter",
"IndividualCalls", "FlushAllOnReload",
- "RFC3964_IPv4"]:
+ "RFC3964_IPv4", "NftablesTableOwner" ]:
if new_value.lower() not in [ "yes", "no",
"true", "false" ]:
raise FirewallError(errors.INVALID_VALUE,
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 61c220f3e59c..2ead41baa00a 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -30,6 +30,7 @@ string "IndividualCalls" : variant string m4_escape(["${EXPECTED_INDIVIDUAL_CALL
string "Lockdown" : variant string "no"
string "LogDenied" : variant string "off"
string "MinimalMark" : variant int32 100
+string "NftablesTableOwner" : variant string "yes"
string "RFC3964_IPv4" : variant string "yes"
])
@@ -54,6 +55,7 @@ _helper([CleanupModulesOnExit], [string:"yes"], [variant string "yes"])
_helper([CleanupOnExit], [string:"no"], [variant string "no"])
_helper([RFC3964_IPv4], [string:"no"], [variant string "no"])
_helper([AllowZoneDrifting], [string:"yes"], [variant string "no"])
+_helper([NftablesTableOwner], [string:"no"], [variant string "no"])
dnl Note: DefaultZone is RO
m4_undefine([_helper])
diff --git a/src/tests/regression/rhbz2222044.at b/src/tests/regression/rhbz2222044.at
index 7e3454509188..2d0333865076 100644
--- a/src/tests/regression/rhbz2222044.at
+++ b/src/tests/regression/rhbz2222044.at
@@ -2,6 +2,11 @@ FWD_START_TEST([duplicate rules after restart])
AT_KEYWORDS(rhbz2222044)
AT_SKIP_IF([! NS_CMD([command -v wc >/dev/null 2>&1])])
+dnl Disable for this test because CI do not support table owner. It's very new
+dnl in nftables.
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=no/' ./firewalld.conf])
+FWD_RELOAD()
+
dnl rules have not changed so rule count should not change
m4_define([check_rule_count], [
m4_if(nftables, FIREWALL_BACKEND, [
--
2.43.5

View File

@ -0,0 +1,69 @@
From bf91ea35e7faf66484bdae7d0b3260c4717ee39a Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 18 Jun 2024 16:20:06 -0400
Subject: [PATCH 21/22] v2.2.0: test(nftables): table ownership
Coverage: RHEL-17002
(cherry picked from commit e7728b843c2ec3a61dbe436575c977e2ad9c8674)
---
src/tests/features/features.at | 1 +
src/tests/features/nftables_table_owner.at | 38 ++++++++++++++++++++++
2 files changed, 39 insertions(+)
create mode 100644 src/tests/features/nftables_table_owner.at
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index 065cb2872e88..83ad9d122189 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -21,3 +21,4 @@ m4_include([features/ipset.at])
m4_include([features/reset_defaults.at])
m4_include([features/iptables_no_flush_on_shutdown.at])
m4_include([features/reloadpolicy.at])
+m4_include([features/nftables_table_owner.at])
diff --git a/src/tests/features/nftables_table_owner.at b/src/tests/features/nftables_table_owner.at
new file mode 100644
index 000000000000..abc946da0ad7
--- /dev/null
+++ b/src/tests/features/nftables_table_owner.at
@@ -0,0 +1,38 @@
+m4_if(nftables, FIREWALL_BACKEND, [
+FWD_START_TEST([nftables table owner])
+AT_KEYWORDS(RHEL-17002)
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+AT_SKIP_IF([grep "Configuration has NftablesTableOwner=True, but it's not supported by nftables." ./firewalld.log])
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld { # progname firewalld
+ flags owner,persist
+])])
+
+dnl Test the transitions from On to Off
+dnl
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=no/' ./firewalld.conf])
+FWD_RELOAD()
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld {
+ chain mangle_PREROUTING {
+])])
+
+dnl Test the transitions from Off to On
+dnl
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld { # progname firewalld
+ flags owner,persist
+])])
+
+FWD_END_TEST()
+])
--
2.43.5

View File

@ -0,0 +1,29 @@
From 8d87974a34c055e0daee3a83f11c539fc98fd6bf Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 26 Jun 2024 15:31:38 -0400
Subject: [PATCH 22/22] v2.2.0: chore(service): remove Conflicts with nftables
Now that firewalld uses the table owner flag it can coexist with the
nftables service.
(cherry picked from commit 9c6cb982981ad0aee5b85773823614ca7fd69073)
---
config/firewalld.service.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/firewalld.service.in b/config/firewalld.service.in
index afbe0ac5def7..b757a08f28dc 100644
--- a/config/firewalld.service.in
+++ b/config/firewalld.service.in
@@ -4,7 +4,7 @@ Before=network-pre.target
Wants=network-pre.target
After=dbus.service
After=polkit.service
-Conflicts=iptables.service ip6tables.service ebtables.service ipset.service nftables.service
+Conflicts=iptables.service ip6tables.service ebtables.service ipset.service
Documentation=man:firewalld(1)
[Service]
--
2.43.5

View File

@ -1,27 +0,0 @@
From 39e8946ba75fc3ce36c3ff72e3af1fb2ae0d95ec Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Mon, 5 Feb 2024 13:24:25 +0100
Subject: [PATCH 23/26] v2.2.0: fix(rich): fix range check for large rule limit
Fixes: 555ae1307a3e ('New rich language usable in zones')
(cherry picked from commit e790c64ebb5760e8d8f8afd1b978baab891d5933)
---
src/firewall/core/rich.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/firewall/core/rich.py b/src/firewall/core/rich.py
index 6a03eeca5d8a..b150a0dca402 100644
--- a/src/firewall/core/rich.py
+++ b/src/firewall/core/rich.py
@@ -264,7 +264,7 @@ class Rich_Limit(object):
elif duration == "d":
mult = 24*60*60
- if 10000 * mult / rate == 0:
+ if 10000 * mult // rate == 0:
raise FirewallError(errors.INVALID_LIMIT,
"%s too fast" % self.value)
--
2.43.0

View File

@ -1,63 +0,0 @@
From 028529e33ed45507bcb1f3eb2722de3344eea091 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Mon, 5 Feb 2024 13:09:02 +0100
Subject: [PATCH 24/26] v2.2.0: improvement(policy): extract helper function
for writing limit rule element
Soon the Rich_Limit will also get a burst attribute. Then _handler_add_rich_limit()
will become more complicated. We wouldn't want to duplicated that code.
(cherry picked from commit f662606891569f09553c73023a2f70086d137512)
---
src/firewall/core/io/policy.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py
index 514a20251ef4..66535e0d0368 100644
--- a/src/firewall/core/io/policy.py
+++ b/src/firewall/core/io/policy.py
@@ -372,6 +372,11 @@ def common_check_config(obj, config, item, all_config):
),
)
+
+def _handler_add_rich_limit(handler, limit):
+ handler.simpleElement("limit", {"value": limit.value})
+
+
def common_writer(obj, handler):
# short
if obj.short and obj.short != "":
@@ -533,8 +538,7 @@ def common_writer(obj, handler):
handler.ignorableWhitespace(" ")
handler.startElement("log", attrs)
handler.ignorableWhitespace("\n ")
- handler.simpleElement("limit",
- { "value": rule.log.limit.value })
+ _handler_add_rich_limit(handler, rule.log.limit)
handler.ignorableWhitespace("\n ")
handler.endElement("log")
else:
@@ -549,8 +553,7 @@ def common_writer(obj, handler):
handler.ignorableWhitespace(" ")
handler.startElement("audit", { })
handler.ignorableWhitespace("\n ")
- handler.simpleElement("limit",
- { "value": rule.audit.limit.value })
+ _handler_add_rich_limit(handler, rule.audit.limit)
handler.ignorableWhitespace("\n ")
handler.endElement("audit")
else:
@@ -579,8 +582,7 @@ def common_writer(obj, handler):
handler.ignorableWhitespace(" ")
handler.startElement(action, attrs)
handler.ignorableWhitespace("\n ")
- handler.simpleElement("limit",
- { "value": rule.action.limit.value })
+ _handler_add_rich_limit(handler, rule.action.limit)
handler.ignorableWhitespace("\n ")
handler.endElement(action)
else:
--
2.43.0

View File

@ -1,189 +0,0 @@
From 2844fedea7b0c65d864f9960b513150c4468adb2 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Wed, 13 Dec 2023 19:42:37 +0100
Subject: [PATCH 25/26] v2.2.0: improvement(rich): add Rich_Limit.value_parse()
and normalize value
Instead of duplicating the parsing, add a Rich_Limit.value_parse()
function that can be used to "understand" the value string.
Note that already previously, Rich_Limit.__init__() would normalize the
value (e.g. modify "/minute" to "/m"). Go one step further with this.
Now parse and stringify the value, so that it is normalized. Invalid
values are left unnormalized, and Rich_Limit.__init__() still does not
fail the operation (like before). For that we have check().
This normalization matters. For example, the parser is (rightfully)
graceful and will accept 'limit value="1 /m"'. If we add two rules
that are identical, except for the white space, we want that the
normalize string is identical. That's useful, because the normalized
string of a rule is used as identity for the rule.
(cherry picked from commit 8d2f9502db98b349cabf76bb9c0a303fe6e3512a)
---
src/firewall-config.in | 6 +--
src/firewall/core/nftables.py | 9 ++---
src/firewall/core/rich.py | 76 ++++++++++++++++++++++-------------
3 files changed, 53 insertions(+), 38 deletions(-)
diff --git a/src/firewall-config.in b/src/firewall-config.in
index f91e945ca7de..e4fbb029ac6d 100755
--- a/src/firewall-config.in
+++ b/src/firewall-config.in
@@ -3245,7 +3245,7 @@ class FirewallConfig(object):
if old_obj.action.limit:
self.richRuleDialogActionLimitCheck.set_active(True)
- (rate, duration) = old_obj.action.limit.value.split("/")
+ (rate, duration) = old_obj.action.limit.value_parse()
self.richRuleDialogActionLimitRateEntry.set_text(rate)
combobox_select_text( \
self.richRuleDialogActionLimitDurationCombobox,
@@ -3288,7 +3288,7 @@ class FirewallConfig(object):
loglevel[log_level])
if old_obj.log.limit:
self.richRuleDialogLogLimitCheck.set_active(True)
- (rate, duration) = old_obj.log.limit.value.split("/")
+ (rate, duration) = old_obj.log.limit.value_parse()
self.richRuleDialogLogLimitRateEntry.set_text(rate)
combobox_select_text( \
self.richRuleDialogLogLimitDurationCombobox,
@@ -3299,7 +3299,7 @@ class FirewallConfig(object):
self.richRuleDialogAuditCheck.set_active(True)
if old_obj.audit.limit:
self.richRuleDialogAuditLimitCheck.set_active(True)
- (rate, duration) = old_obj.audit.limit.value.split("/")
+ (rate, duration) = old_obj.audit.limit.value_parse()
self.richRuleDialogAuditLimitRateEntry.set_text(rate)
combobox_select_text( \
self.richRuleDialogAuditLimitDurationCombobox,
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 67fb6457e86c..f24095ce729c 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1071,13 +1071,10 @@ class nftables(object):
"d" : "day",
}
- try:
- i = limit.value.index("/")
- except ValueError:
- raise FirewallError(INVALID_RULE, "Expected '/' in limit")
+ rate, duration = limit.value_parse()
- return {"limit": {"rate": int(limit.value[0:i]),
- "per": rich_to_nft[limit.value[i+1]]}}
+ return {"limit": {"rate": rate,
+ "per": rich_to_nft[duration]}}
def _rich_rule_chain_suffix(self, rich_rule):
if type(rich_rule.element) in [Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock]:
diff --git a/src/firewall/core/rich.py b/src/firewall/core/rich.py
index b150a0dca402..a77f2b4aa495 100644
--- a/src/firewall/core/rich.py
+++ b/src/firewall/core/rich.py
@@ -230,54 +230,72 @@ class Rich_Mark(object):
# value is uint32
raise FirewallError(errors.INVALID_MARK, x)
+DURATION_TO_MULT = {
+ "s": 1,
+ "m": 60,
+ "h": 60 * 60,
+ "d": 24 * 60 * 60,
+}
+
class Rich_Limit(object):
def __init__(self, value):
self.value = value
- if "/" in self.value:
- splits = self.value.split("/")
- if len(splits) == 2 and \
- splits[1] in [ "second", "minute", "hour", "day" ]:
- self.value = "%s/%s" % (splits[0], splits[1][:1])
def check(self):
+ self.value_parse()
+
+ @property
+ def value(self):
+ return self._value
+
+ @value.setter
+ def value(self, value):
+ if value is None:
+ self._value = None
+ return
+ try:
+ rate, duration = self._value_parse(value)
+ except FirewallError:
+ # The value is invalid. We cannot normalize it.
+ v = value
+ else:
+ v = f"{rate}/{duration}"
+ if getattr(self, "_value", None) != v:
+ self._value = v
+
+ @staticmethod
+ def _value_parse(value):
splits = None
- if "/" in self.value:
- splits = self.value.split("/")
+ if "/" in value:
+ splits = value.split("/")
if not splits or len(splits) != 2:
- raise FirewallError(errors.INVALID_LIMIT, self.value)
+ raise FirewallError(errors.INVALID_LIMIT, value)
(rate, duration) = splits
try:
rate = int(rate)
except:
- raise FirewallError(errors.INVALID_LIMIT, self.value)
+ raise FirewallError(errors.INVALID_LIMIT, value)
- if rate < 1 or duration not in [ "s", "m", "h", "d" ]:
- raise FirewallError(errors.INVALID_LIMIT, self.value)
+ if duration in ["second", "minute", "hour", "day"]:
+ duration = duration[:1]
- mult = 1
- if duration == "s":
- mult = 1
- elif duration == "m":
- mult = 60
- elif duration == "h":
- mult = 60*60
- elif duration == "d":
- mult = 24*60*60
+ if rate < 1 or duration not in ["s", "m", "h", "d"]:
+ raise FirewallError(errors.INVALID_LIMIT, value)
- if 10000 * mult // rate == 0:
- raise FirewallError(errors.INVALID_LIMIT,
- "%s too fast" % self.value)
+ if 10000 * DURATION_TO_MULT[duration] // rate == 0:
+ raise FirewallError(errors.INVALID_LIMIT, "%s too fast" % (value,))
if rate == 1 and duration == "d":
# iptables (v1.4.21) doesn't accept 1/d
- raise FirewallError(errors.INVALID_LIMIT,
- "%s too slow" % self.value)
+ raise FirewallError(errors.INVALID_LIMIT, "%s too slow" % (value,))
- def __str__(self):
- return 'limit value="%s"' % (self.value)
+ return rate, duration
- def command(self):
- return ''
+ def value_parse(self):
+ return self._value_parse(self._value)
+
+ def __str__(self):
+ return f'limit value="{self._value}"'
class Rich_Rule(object):
priority_min = -32768
--
2.43.0

View File

@ -1,238 +0,0 @@
From 45ebffc5521db62064f365f4a9100b4ab40dce90 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Wed, 13 Dec 2023 20:35:51 +0100
Subject: [PATCH 26/26] v2.2.0: improvement(rich): support "burst" attribute to
limit in rich rules
For iptables, this is `-m limit --limit rate/suffix --limit-burst burst`.
For nftables, this is `limit rate [over] packet_number / TIME_UNIT [burst packet_number packets]`
Not implemented in `src/firewall-config.in`.
https://issues.redhat.com/browse/RHEL-9316
(cherry picked from commit 58dfdcafabaaad639bfcf389ebbd6b2c242965a4)
---
src/firewall/core/io/policy.py | 9 +++--
src/firewall/core/io/zone.py | 1 +
src/firewall/core/ipXtables.py | 9 +++--
src/firewall/core/nftables.py | 12 +++++--
src/firewall/core/rich.py | 63 ++++++++++++++++++++++++++++++----
src/tests/cli/firewall-cmd.at | 4 +--
6 files changed, 82 insertions(+), 16 deletions(-)
diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py
index 66535e0d0368..c732324c441b 100644
--- a/src/firewall/core/io/policy.py
+++ b/src/firewall/core/io/policy.py
@@ -278,7 +278,7 @@ def common_startElement(obj, name, attrs):
obj._rule_error = True
return True
value = attrs["value"]
- obj._limit_ok.limit = rich.Rich_Limit(value)
+ obj._limit_ok.limit = rich.Rich_Limit(value, attrs.get("burst"))
else:
return False
@@ -374,7 +374,11 @@ def common_check_config(obj, config, item, all_config):
def _handler_add_rich_limit(handler, limit):
- handler.simpleElement("limit", {"value": limit.value})
+ d = {"value": limit.value}
+ burst = limit.burst
+ if burst is not None:
+ d["burst"] = burst
+ handler.simpleElement("limit", d)
def common_writer(obj, handler):
@@ -652,6 +656,7 @@ class Policy(IO_Object):
"destination": [ "address", "invert", "ipset" ],
"log": [ "prefix", "level" ],
"reject": [ "type" ],
+ "limit": ["burst"],
}
def __init__(self):
diff --git a/src/firewall/core/io/zone.py b/src/firewall/core/io/zone.py
index 0c419ee0f2bd..753036e4fb55 100644
--- a/src/firewall/core/io/zone.py
+++ b/src/firewall/core/io/zone.py
@@ -94,6 +94,7 @@ class Zone(IO_Object):
"destination": [ "address", "invert", "ipset" ],
"log": [ "prefix", "level" ],
"reject": [ "type" ],
+ "limit": ["burst"],
}
@staticmethod
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index 401377104ce1..0f9a1518380e 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -967,9 +967,12 @@ class ip4tables(object):
return rules
def _rule_limit(self, limit):
- if limit:
- return [ "-m", "limit", "--limit", limit.value ]
- return []
+ if not limit:
+ return []
+ s = ["-m", "limit", "--limit", limit.value]
+ if limit.burst is not None:
+ s += ["--limit-burst", limit.burst]
+ return s
def _rich_rule_chain_suffix(self, rich_rule):
if type(rich_rule.element) in [Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock]:
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index f24095ce729c..834176c09cbc 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1073,8 +1073,16 @@ class nftables(object):
rate, duration = limit.value_parse()
- return {"limit": {"rate": rate,
- "per": rich_to_nft[duration]}}
+ d = {
+ "rate": rate,
+ "per": rich_to_nft[duration],
+ }
+
+ burst = limit.burst_parse()
+ if burst is not None:
+ d["burst"] = burst
+
+ return {"limit": d}
def _rich_rule_chain_suffix(self, rich_rule):
if type(rich_rule.element) in [Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock]:
diff --git a/src/firewall/core/rich.py b/src/firewall/core/rich.py
index a77f2b4aa495..c561709af0e2 100644
--- a/src/firewall/core/rich.py
+++ b/src/firewall/core/rich.py
@@ -238,11 +238,13 @@ DURATION_TO_MULT = {
}
class Rich_Limit(object):
- def __init__(self, value):
+ def __init__(self, value, burst=None):
self.value = value
+ self.burst = burst
def check(self):
self.value_parse()
+ self.burst_parse()
@property
def value(self):
@@ -263,6 +265,24 @@ class Rich_Limit(object):
if getattr(self, "_value", None) != v:
self._value = v
+ @property
+ def burst(self):
+ return self._burst
+
+ @burst.setter
+ def burst(self, burst):
+ if burst is None:
+ self._burst = None
+ return
+ try:
+ b = self._burst_parse(burst)
+ except FirewallError:
+ b = burst
+ else:
+ b = str(burst)
+ if getattr(self, "_burst", None) != b:
+ self._burst = b
+
@staticmethod
def _value_parse(value):
splits = None
@@ -294,8 +314,28 @@ class Rich_Limit(object):
def value_parse(self):
return self._value_parse(self._value)
+ @staticmethod
+ def _burst_parse(burst):
+ if burst is None:
+ return None
+ try:
+ b = int(burst)
+ except:
+ raise FirewallError(errors.INVALID_LIMIT, burst)
+
+ if b < 1 or b > 10_000_000:
+ raise FirewallError(errors.INVALID_LIMIT, burst)
+
+ return b
+
+ def burst_parse(self):
+ return self._burst_parse(self._burst)
+
def __str__(self):
- return f'limit value="{self._value}"'
+ s = f'limit value="{self._value}"'
+ if self._burst is not None:
+ s += f" burst={self._burst}"
+ return s
class Rich_Rule(object):
priority_min = -32768
@@ -368,7 +408,7 @@ class Rich_Rule(object):
'invert', 'value',
'port', 'protocol', 'to-port', 'to-addr',
'name', 'prefix', 'level', 'type',
- 'set']:
+ 'set', 'burst']:
raise FirewallError(errors.INVALID_RULE, "bad attribute '%s'" % attr_name)
else: # element
if element in ['rule', 'source', 'destination', 'protocol',
@@ -554,11 +594,20 @@ class Rich_Rule(object):
attrs.clear()
index = index -1 # return token to input
elif in_element == 'limit':
- if attr_name == 'value':
- attrs['limit'] = Rich_Limit(attr_value)
- in_elements.pop() # limit
+ if attr_name in ["value", "burst"]:
+ attrs[f"limit.{attr_name}"] = attr_value
else:
- raise FirewallError(errors.INVALID_RULE, "invalid 'limit' element")
+ if "limit.value" not in attrs:
+ raise FirewallError(
+ errors.INVALID_RULE, "invalid 'limit' element"
+ )
+ attrs["limit"] = Rich_Limit(
+ attrs["limit.value"], attrs.get("limit.burst")
+ )
+ attrs.pop("limit.value", None)
+ attrs.pop("limit.burst", None)
+ in_elements.pop() # limit
+ index = index - 1 # return token to input
index = index + 1
diff --git a/src/tests/cli/firewall-cmd.at b/src/tests/cli/firewall-cmd.at
index c4ab3108d37c..6c69f0ccebd4 100644
--- a/src/tests/cli/firewall-cmd.at
+++ b/src/tests/cli/firewall-cmd.at
@@ -1356,8 +1356,8 @@ FWD_START_TEST([rich rules good])
rich_rule_test([rule protocol value="ah" reject])
rich_rule_test([rule protocol value="esp" accept])
rich_rule_test([rule protocol value="sctp" log])
- rich_rule_test([rule family="ipv4" source address="192.168.0.0/24" service name="tftp" log prefix="tftp: " level="info" limit value="1/m" accept])
- rich_rule_test([rule family="ipv4" source not address="192.168.0.0/24" service name="dns" log prefix="dns: " level="info" limit value="2/m" drop])
+ rich_rule_test([rule family="ipv4" source address="192.168.0.0/24" service name="tftp" log prefix="tftp: " level="info" limit value="1/m" burst=5 accept])
+ rich_rule_test([rule family="ipv4" source not address="192.168.0.0/24" service name="dns" log prefix="dns: " level="info" limit value="2/m" burst=5 drop])
IF_HOST_SUPPORTS_IPV6_RULES([
rich_rule_test([rule family="ipv6" source address="1:2:3:4:6::" service name="radius" log prefix="dns -- " level="info" limit value="3/m" reject type="icmp6-addr-unreachable" limit value="20/m"])
rich_rule_test([rule family="ipv6" source address="1:2:3:4:6::" port port="4011" protocol="tcp" log prefix="port 4011: " level="info" limit value="4/m" drop])
--
2.43.0

View File

@ -1,34 +0,0 @@
From f61b27ffc91da3d5e634a2d90edd164ac4102086 Mon Sep 17 00:00:00 2001
From: Eric Garver <egarver@redhat.com>
Date: Wed, 26 Jun 2024 11:13:00 -0400
Subject: [PATCH 28/30] v2.0.0: chore(direct): add has_runtime_configuration()
This is originally from cdd015475e83 ("fix(ipset): defer native ipset
creation if nftables").
---
src/firewall/core/fw_direct.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/firewall/core/fw_direct.py b/src/firewall/core/fw_direct.py
index 76aeda9f19cb..a35ebce1f276 100644
--- a/src/firewall/core/fw_direct.py
+++ b/src/firewall/core/fw_direct.py
@@ -64,9 +64,14 @@ class FirewallDirect(object):
def set_permanent_config(self, obj):
self._obj = obj
- def has_configuration(self):
+ def has_runtime_configuration(self):
if len(self._chains) + len(self._rules) + len(self._passthroughs) > 0:
return True
+ return False
+
+ def has_configuration(self):
+ if self.has_runtime_configuration():
+ return True
if len(self._obj.get_all_chains()) + \
len(self._obj.get_all_rules()) + \
len(self._obj.get_all_passthroughs()) > 0:
--
2.43.0

File diff suppressed because it is too large Load Diff