From c6bfeff9a2924d54af501b7985aa3f503e6e8b44 Mon Sep 17 00:00:00 2001 From: Eric Garver Date: Mon, 1 Jul 2024 10:50:11 -0400 Subject: [PATCH] feat(IPv6_rpfilter): support strict-forward rpfilter feat(IPv6_rpfilter): support loose rpfilter feat(IPv6_rpfilter): support loose-forward rpfilter Resolves: RHEL-33330 --- ...nctions-add-macro-CHECK_NFTABLES_FIB.patch | 27 ++ ...tions-add-macro-CHECK_NFTABLES_FIB_I.patch | 31 ++ ...2.2.0-test-rpfilter-use-CHECK-macros.patch | 51 +++ ...st-IPv6_rpfilter-verify-valid-values.patch | 41 +++ ...6_rpfilter-prepare-for-new-config-va.patch | 336 ++++++++++++++++++ ...IPv6_rpfilter-support-loose-rpfilter.patch | 166 +++++++++ ..._rpfilter-support-loose-forward-rpfi.patch | 229 ++++++++++++ ..._rpfilter-support-strict-forward-rpf.patch | 183 ++++++++++ firewalld.spec | 15 +- 9 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch create mode 100644 0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch create mode 100644 0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch create mode 100644 0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch create mode 100644 0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch create mode 100644 0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch create mode 100644 0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch create mode 100644 0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch diff --git a/0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch b/0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch new file mode 100644 index 0000000..5f1ecd4 --- /dev/null +++ b/0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch @@ -0,0 +1,27 @@ +From 55e40954a8c596fabe03371e9a508d3518273ac1 Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch b/0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch new file mode 100644 index 0000000..5aa3048 --- /dev/null +++ b/0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch @@ -0,0 +1,31 @@ +From d368d579c78652a68273897d5f8b5099d251a9b5 Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch b/0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch new file mode 100644 index 0000000..cd67df8 --- /dev/null +++ b/0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch @@ -0,0 +1,51 @@ +From c1620d5ad4c151382373a138ab0c36dd7561a4bb Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch b/0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch new file mode 100644 index 0000000..6ab9f2d --- /dev/null +++ b/0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch @@ -0,0 +1,41 @@ +From 0ba1eed533e4cd1dd77771ba7c16dc0edcea841e Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch b/0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch new file mode 100644 index 0000000..6cd768a --- /dev/null +++ b/0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch @@ -0,0 +1,336 @@ +From 7973ddf8d9f972f0292c8c865da9e0ebaefd77cb Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 @@ + + + +- 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. + + + Note: 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 @@ + + + +- IPv6_rpfilter - s - (rw) +- 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. ++ IPv6_rpfilter - b - (rw) ++ Deprecated. See org.fedoraproject.FirewallD1.config.Properties.IPv6_rpfilter2. ++ ++ ++ IPv6_rpfilter2 - s - (rw) ++ Indicates whether the reverse path filter (RFE 3704) test on a packet for IPv6 is enabled. + + + IndividualCalls - s - (ro) +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 + diff --git a/0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch b/0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch new file mode 100644 index 0000000..93e565d --- /dev/null +++ b/0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch @@ -0,0 +1,166 @@ +From 41828e0723b1ad195a33c535918a2fb6fabaf88f Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch b/0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch new file mode 100644 index 0000000..e909c35 --- /dev/null +++ b/0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch @@ -0,0 +1,229 @@ +From a965defecfad03530f3374271fb4889ff895ab1c Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch b/0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch new file mode 100644 index 0000000..d6f2640 --- /dev/null +++ b/0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch @@ -0,0 +1,183 @@ +From 7629e1c24b73d8c1e56d275c07076bc3bf99caad Mon Sep 17 00:00:00 2001 +From: Eric Garver +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. + + + +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 + diff --git a/firewalld.spec b/firewalld.spec index 35a475d..753f62f 100644 --- a/firewalld.spec +++ b/firewalld.spec @@ -1,7 +1,7 @@ Summary: A firewall daemon with D-Bus interface providing a dynamic firewall Name: firewalld Version: 1.3.4 -Release: 5%{?dist} +Release: 6%{?dist} URL: http://www.firewalld.org License: GPLv2+ Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.bz2 @@ -15,6 +15,14 @@ Patch7: 0007-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch Patch8: 0008-v2.1.0-improvement-nftables-do-not-track-rule-handle.patch Patch9: 0009-v2.1.0-improvement-fw-make-set_policy-DROP-more-flex.patch Patch10: 0010-v2.1.0-feat-fw-add-ReloadPolicy-option-in-firewalld..patch +Patch11: 0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch +Patch12: 0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch +Patch13: 0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch +Patch14: 0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch +Patch15: 0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch +Patch16: 0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch +Patch17: 0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch +Patch18: 0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch BuildArch: noarch BuildRequires: autoconf BuildRequires: automake @@ -238,6 +246,11 @@ rm -rf %{buildroot}%{_datadir}/firewalld/testsuite %{_mandir}/man1/firewall-config*.1* %changelog +* Mon Jul 01 2024 Eric Garver - 1.3.4-6 +- feat(IPv6_rpfilter): support loose rpfilter +- feat(IPv6_rpfilter): support loose-forward rpfilter +- feat(IPv6_rpfilter): support strict-forward rpfilter + * Mon Jul 01 2024 Eric Garver - 1.3.4-5 - feat(fw): add ReloadPolicy option in firewalld.conf