diff --git a/SOURCES/0035-v2.2.0-feat-add-iperf-2-3-services.patch b/SOURCES/0035-v2.2.0-feat-add-iperf-2-3-services.patch new file mode 100644 index 0000000..28702ba --- /dev/null +++ b/SOURCES/0035-v2.2.0-feat-add-iperf-2-3-services.patch @@ -0,0 +1,71 @@ +From 0829f9ec9d171894bda6695a87c15aba49f3e6a2 Mon Sep 17 00:00:00 2001 +From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> +Date: Tue, 30 Jan 2024 17:08:30 +0800 +Subject: [PATCH 35/48] v2.2.0: feat: add iperf{2,3} services + +(cherry picked from commit a21b401f6d8dd6eb65adba1878a29d63086b15e7) +--- + config/Makefile.am | 2 ++ + config/services/iperf2.xml | 7 +++++++ + config/services/iperf3.xml | 8 ++++++++ + po/POTFILES.in | 2 ++ + 4 files changed, 19 insertions(+) + create mode 100644 config/services/iperf2.xml + create mode 100644 config/services/iperf3.xml + +diff --git a/config/Makefile.am b/config/Makefile.am +index 711f05afb799..a7a6dc039594 100644 +--- a/config/Makefile.am ++++ b/config/Makefile.am +@@ -188,6 +188,8 @@ CONFIG_FILES = \ + services/ident.xml \ + services/imaps.xml \ + services/imap.xml \ ++ services/iperf2.xml \ ++ services/iperf3.xml \ + services/ipfs.xml \ + services/ipp-client.xml \ + services/ipp.xml \ +diff --git a/config/services/iperf2.xml b/config/services/iperf2.xml +new file mode 100644 +index 000000000000..4475c4e58212 +--- /dev/null ++++ b/config/services/iperf2.xml +@@ -0,0 +1,7 @@ ++ ++ ++ iperf2 ++ iperf2 is a TCP and UDP network bandwidth measurement tool. Enable this option if you want to run an iperf2 server on its default port. ++ ++ ++ +diff --git a/config/services/iperf3.xml b/config/services/iperf3.xml +new file mode 100644 +index 000000000000..4a481b0ecdfb +--- /dev/null ++++ b/config/services/iperf3.xml +@@ -0,0 +1,8 @@ ++ ++ ++ iperf3 ++ iperf3 is a TCP, UDP, and SCTP network bandwidth measurement tool. Enable this option if you want to run an iperf3 server on its default port. ++ ++ ++ ++ +diff --git a/po/POTFILES.in b/po/POTFILES.in +index a03ff0ce1df3..bdd0d3fc939e 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -120,6 +120,8 @@ config/services/http.xml + config/services/ident.xml + config/services/imaps.xml + config/services/imap.xml ++config/services/iperf2.xml ++config/services/iperf3.xml + config/services/ipfs.xml + config/services/ipp-client.xml + config/services/ipp.xml +-- +2.47.3 + diff --git a/SOURCES/0036-v2.4.0-test-functions-add-macro-WAIT_UNTIL.patch b/SOURCES/0036-v2.4.0-test-functions-add-macro-WAIT_UNTIL.patch new file mode 100644 index 0000000..f068e61 --- /dev/null +++ b/SOURCES/0036-v2.4.0-test-functions-add-macro-WAIT_UNTIL.patch @@ -0,0 +1,36 @@ +From 1f72a1c3bdf0dd727d76f205b87223dd29ef0c7c Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Mon, 6 Oct 2025 16:32:59 -0400 +Subject: [PATCH 36/48] v2.4.0: test(functions): add macro WAIT_UNTIL + +(cherry picked from commit 50890c62b00db91f15ba5802055afc293a3fe77a) +--- + src/tests/functions.at | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/src/tests/functions.at b/src/tests/functions.at +index 8b07908c667c..df3fff6ad4b9 100644 +--- a/src/tests/functions.at ++++ b/src/tests/functions.at +@@ -763,3 +763,18 @@ m4_define([CHECK_NFTABLES_FIB_IN_FORWARD], [ + NS_CHECK([nft delete table inet firewalld_check]) + ]) + ]) ++ ++dnl $1 = command to run until zero exit code ++m4_define([WAIT_UNTIL], [ ++ _fail=1 ++ _timeout=120 ++ for I in $(seq ${_timeout}); do ++ { $1 ; } && { _fail=0; break; } ++ sleep 1 ++ done ++ if test ${_fail} -gt 0; then ++ printf "FAIL: Command failed succeed in ${_timeout} seconds:\n" ++ printf " $1\n" ++ AT_FAIL_IF([:]) ++ fi ++]) +-- +2.47.3 + diff --git a/SOURCES/0037-v2.4.0-fix-server-load-firewall-rules-before-claimin.patch b/SOURCES/0037-v2.4.0-fix-server-load-firewall-rules-before-claimin.patch new file mode 100644 index 0000000..6e17b2e --- /dev/null +++ b/SOURCES/0037-v2.4.0-fix-server-load-firewall-rules-before-claimin.patch @@ -0,0 +1,81 @@ +From 3a91e1c6a575572cabbc06460ad94f65234a7d98 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 14 Oct 2025 16:41:49 -0400 +Subject: [PATCH 37/48] v2.4.0: fix(server): load firewall rules before + claiming dbus + +This guarantees that the firewall is loaded and ready before the daemon +registers with dbus. + +(cherry picked from commit 63d2238d055ee193f18acb51f6362b64d11e0886) +--- + src/firewall/server/firewalld.py | 15 +++++++++++---- + src/firewall/server/server.py | 10 +--------- + 2 files changed, 12 insertions(+), 13 deletions(-) + +diff --git a/src/firewall/server/firewalld.py b/src/firewall/server/firewalld.py +index 8b9593a22fd8..fc85d3e0c359 100644 +--- a/src/firewall/server/firewalld.py ++++ b/src/firewall/server/firewalld.py +@@ -25,6 +25,7 @@ from gi.repository import GLib + import copy + import dbus + import dbus.service ++import dbus.mainloop.glib + + from firewall import config + from firewall.core.fw import Firewall +@@ -69,12 +70,18 @@ class FirewallD(DbusServiceObject): + """ Use config.dbus.PK_ACTION_CONFIG as a default """ + + @handle_exceptions +- def __init__(self, *args, **kwargs): +- super(FirewallD, self).__init__(*args, **kwargs) ++ def __init__(self): + self.fw = Firewall() +- self.busname = args[0] +- self.path = args[1] + self.start() ++ ++ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) ++ bus = dbus.SystemBus() ++ name = dbus.service.BusName(config.dbus.DBUS_INTERFACE, bus=bus) ++ super(FirewallD, self).__init__(name, config.dbus.DBUS_PATH) ++ ++ self.busname = name ++ self.path = config.dbus.DBUS_PATH ++ + dbus_introspection_prepare_properties(self, config.dbus.DBUS_INTERFACE) + self.config = FirewallDConfig(self.fw.config, self.busname, + config.dbus.DBUS_PATH_CONFIG) +diff --git a/src/firewall/server/server.py b/src/firewall/server/server.py +index 2921ae9104f1..7f3404793f12 100644 +--- a/src/firewall/server/server.py ++++ b/src/firewall/server/server.py +@@ -31,11 +31,6 @@ import signal + + from gi.repository import GLib + +-import dbus +-import dbus.service +-import dbus.mainloop.glib +- +-from firewall import config + from firewall.core.logger import log + from firewall.server.firewalld import FirewallD + +@@ -83,10 +78,7 @@ def run_server(debug_gc=False): + GLib.timeout_add_seconds(gc_timeout, gc_collect) + + try: +- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +- bus = dbus.SystemBus() +- name = dbus.service.BusName(config.dbus.DBUS_INTERFACE, bus=bus) +- service = FirewallD(name, config.dbus.DBUS_PATH) ++ service = FirewallD() + + mainloop = GLib.MainLoop() + if debug_gc: +-- +2.47.3 + diff --git a/SOURCES/0038-v2.4.0-Revert-fix-systemd-allow-start-code-251-RUNNI.patch b/SOURCES/0038-v2.4.0-Revert-fix-systemd-allow-start-code-251-RUNNI.patch new file mode 100644 index 0000000..c06fa62 --- /dev/null +++ b/SOURCES/0038-v2.4.0-Revert-fix-systemd-allow-start-code-251-RUNNI.patch @@ -0,0 +1,29 @@ +From bbfef80c94f368130e4440d1624d2a2bc1daf28d Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 14 Oct 2025 16:05:41 -0400 +Subject: [PATCH 38/48] v2.4.0: Revert "fix(systemd): allow start code 251 + (RUNNING_BUT_FAILED)" + +This reverts commit d52815e198f05378a3f34633adfedd29165cc64e. + +(cherry picked from commit d2af4c8de086b658b0f1a24be9d3bf55b514b3c3) +--- + config/firewalld.service.in | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/config/firewalld.service.in b/config/firewalld.service.in +index bd8690fd87a6..cd7f772b8581 100644 +--- a/config/firewalld.service.in ++++ b/config/firewalld.service.in +@@ -11,8 +11,6 @@ Documentation=man:firewalld(1) + EnvironmentFile=-/etc/sysconfig/firewalld + ExecStart=@sbindir@/firewalld --nofork --nopid $FIREWALLD_ARGS + ExecStartPost=@bindir@/firewall-cmd --state +-# don't fail ExecStartPost on RUNNING_BUT_FAILED +-SuccessExitStatus=251 + ExecReload=/bin/kill -HUP $MAINPID + StandardOutput=null + StandardError=null +-- +2.47.3 + diff --git a/SOURCES/0039-v2.4.0-Revert-fix-systemd-verify-firewalld-is-respon.patch b/SOURCES/0039-v2.4.0-Revert-fix-systemd-verify-firewalld-is-respon.patch new file mode 100644 index 0000000..e0df397 --- /dev/null +++ b/SOURCES/0039-v2.4.0-Revert-fix-systemd-verify-firewalld-is-respon.patch @@ -0,0 +1,29 @@ +From 82a7c51975297fa185410ce37ae0c73b70d1924d Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 14 Oct 2025 16:05:54 -0400 +Subject: [PATCH 39/48] v2.4.0: Revert "fix(systemd): verify firewalld is + responsive to dbus" + +This reverts commit 4ddfe5672e3a51e1c081b410144155553f256e91. + +Fixes: #1492 +(cherry picked from commit 3e61bcccdb3efc12474eb99538bd52fc4f63f4dd) +--- + config/firewalld.service.in | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/config/firewalld.service.in b/config/firewalld.service.in +index cd7f772b8581..08c9f74dd924 100644 +--- a/config/firewalld.service.in ++++ b/config/firewalld.service.in +@@ -10,7 +10,6 @@ Documentation=man:firewalld(1) + [Service] + EnvironmentFile=-/etc/sysconfig/firewalld + ExecStart=@sbindir@/firewalld --nofork --nopid $FIREWALLD_ARGS +-ExecStartPost=@bindir@/firewall-cmd --state + ExecReload=/bin/kill -HUP $MAINPID + StandardOutput=null + StandardError=null +-- +2.47.3 + diff --git a/SOURCES/0040-v2.4.0-fix-nftables-ipset-add-entries-from-GLib-loop.patch b/SOURCES/0040-v2.4.0-fix-nftables-ipset-add-entries-from-GLib-loop.patch new file mode 100644 index 0000000..4ce834e --- /dev/null +++ b/SOURCES/0040-v2.4.0-fix-nftables-ipset-add-entries-from-GLib-loop.patch @@ -0,0 +1,66 @@ +From 85d9dfb83d49499b21fb3c97e4486bc944a2651e Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 14 Oct 2025 15:59:05 -0400 +Subject: [PATCH 40/48] v2.4.0: fix(nftables): ipset: add entries from GLib + loop when idle + +Sets with a large amount of entries can take a significant time to +apply. Use the GLib mainloop to add them in chunks when the loop is +idle. This allows dbus calls in between the chunks as the dbus +events/calls have higher priority. + +Fixes: #1277 +(cherry picked from commit 3874bafc427139e647829d7662577567343aceb6) +--- + src/firewall/core/nftables.py | 23 +++++++++++++++++++---- + 1 file changed, 19 insertions(+), 4 deletions(-) + +diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py +index 8115bcb9d7f4..254fe7cfbebe 100644 +--- a/src/firewall/core/nftables.py ++++ b/src/firewall/core/nftables.py +@@ -18,6 +18,9 @@ + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # ++ ++from gi.repository import GLib ++ + import copy + import json + import ipaddress +@@ -1958,15 +1961,27 @@ class nftables(object): + rules = [] + rules.extend(self.build_set_create_rules(set_name, type_name, create_options)) + rules.extend(self.build_set_flush_rules(set_name)) ++ self.set_rules(rules, self._fw.get_log_denied()) ++ ++ def _idle_set_add_entries(rules): ++ try: ++ self.set_rules(rules, self._fw.get_log_denied()) ++ except Exception as e: ++ log.error("While restoring ipset entries the following Error occurred:") ++ log.error(e) + +- # avoid large memory usage by chunking the entries ++ # Avoid large memory usage by chunking the entries. Additionally, add ++ # the entries from the GLib main loop when it's idle. This avoids ++ # blocking the main loop for too long. ++ # + chunk = 0 ++ rules = [] + for entry in entries: + rules.extend(self.build_set_add_rules(set_name, entry)) + chunk += 1 + if chunk >= 1000: +- self.set_rules(rules, self._fw.get_log_denied()) +- rules.clear() ++ GLib.idle_add(lambda x: _idle_set_add_entries(x), rules) ++ rules = [] + chunk = 0 + else: +- self.set_rules(rules, self._fw.get_log_denied()) ++ GLib.idle_add(lambda x: _idle_set_add_entries(x), rules) +-- +2.47.3 + diff --git a/SOURCES/0041-v2.4.0-test-ipset-scale-verify-all-the-entries-were-.patch b/SOURCES/0041-v2.4.0-test-ipset-scale-verify-all-the-entries-were-.patch new file mode 100644 index 0000000..a862e49 --- /dev/null +++ b/SOURCES/0041-v2.4.0-test-ipset-scale-verify-all-the-entries-were-.patch @@ -0,0 +1,39 @@ +From c2f4ff8d26d2d7510e32f8ec6bcaa222af95ed9a Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 14 Oct 2025 17:50:22 -0400 +Subject: [PATCH 41/48] v2.4.0: test(ipset): scale: verify all the entries were + added + +(cherry picked from commit ab35fc7a99536efc7dde2242507fad6bed577d68) +--- + src/tests/regression/ipset_scale.at | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/src/tests/regression/ipset_scale.at b/src/tests/regression/ipset_scale.at +index 0aef986434f0..d754e035f187 100644 +--- a/src/tests/regression/ipset_scale.at ++++ b/src/tests/regression/ipset_scale.at +@@ -1,6 +1,8 @@ + FWD_START_TEST([ipset scale]) + AT_KEYWORDS(ipset gh738 scale) + ++AT_SKIP_IF([! NS_CMD([which wc >/dev/null 2>&1])]) ++ + dnl Create a huge ipset + AT_CHECK([touch ./entries], 0, [ignore]) + AT_CHECK([sh -c ' +@@ -22,4 +24,11 @@ ulimit -d $(expr 1024 \* 300) + FWD_RESTART() dnl required because we changed ulimit + FWD_RELOAD() + ++dnl Verify all the entries are added. ++m4_if(nftables, FIREWALL_BACKEND, [ ++ WAIT_UNTIL([test "$(NS_CMD([nft $NFT_NUMERIC_ARGS list set inet firewalld foobar |wc -l]))" -eq 31256]) ++], [ ++ WAIT_UNTIL([test "$(NS_CMD([$IPSET list foobar |wc -l]))" -eq 62508]) ++]) ++ + FWD_END_TEST() +-- +2.47.3 + diff --git a/SOURCES/0042-v2.4.0-fix-systemd-Requires-dbus.patch b/SOURCES/0042-v2.4.0-fix-systemd-Requires-dbus.patch new file mode 100644 index 0000000..b50453d --- /dev/null +++ b/SOURCES/0042-v2.4.0-fix-systemd-Requires-dbus.patch @@ -0,0 +1,28 @@ +From f7c7f94908834824bc777334c5b61e7af4d629b5 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Mon, 13 Oct 2025 15:44:17 -0400 +Subject: [PATCH 42/48] v2.4.0: fix(systemd): Requires dbus + +Use Requires so when dbus is restarted firewalld is also restarted. + +Fixes: RHEL-94927 +(cherry picked from commit b9595ea06e6159735300bb4668a3e7e84966219c) +--- + config/firewalld.service.in | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/config/firewalld.service.in b/config/firewalld.service.in +index 08c9f74dd924..7ace390c29d6 100644 +--- a/config/firewalld.service.in ++++ b/config/firewalld.service.in +@@ -2,6 +2,7 @@ + Description=firewalld - dynamic firewall daemon + Before=network-pre.target + Wants=network-pre.target ++Requires=dbus.service + After=dbus.service + After=polkit.service + Conflicts=iptables.service ip6tables.service ebtables.service ipset.service +-- +2.47.3 + diff --git a/SOURCES/0043-v2.4.0-chore-icmp-add-all-icmptypes-to-ICMP_TYPES-di.patch b/SOURCES/0043-v2.4.0-chore-icmp-add-all-icmptypes-to-ICMP_TYPES-di.patch new file mode 100644 index 0000000..a19d7c9 --- /dev/null +++ b/SOURCES/0043-v2.4.0-chore-icmp-add-all-icmptypes-to-ICMP_TYPES-di.patch @@ -0,0 +1,96 @@ +From 496cadf080a047286dbbb0fef4a06a8d17261587 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 28 Oct 2025 15:59:26 -0400 +Subject: [PATCH 43/48] v2.4.0: chore(icmp): add all icmptypes to ICMP_TYPES + dict + +We can then use this as a source of truth for types/codes. + +(cherry picked from commit b121faab666faddf7181d21691ee6e8a13b30dbe) +--- + src/firewall/core/icmp.py | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/src/firewall/core/icmp.py b/src/firewall/core/icmp.py +index c293098da20f..2928052194ea 100644 +--- a/src/firewall/core/icmp.py ++++ b/src/firewall/core/icmp.py +@@ -25,32 +25,42 @@ __all__ = [ "ICMP_TYPES", "ICMPV6_TYPES", + ICMP_TYPES = { + "echo-reply": "0/0", + "pong": "0/0", ++ "destination-unreachable": "3/0", + "network-unreachable": "3/0", ++ "tos-network-unreachable": "3/0", + "host-unreachable": "3/1", ++ "tos-host-unreachable": "3/1", + "protocol-unreachable": "3/2", + "port-unreachable": "3/3", + "fragmentation-needed": "3/4", + "source-route-failed": "3/5", ++ # RFC-1112 Section 3.2.2.1 defines type 3, code 6-12 + "network-unknown": "3/6", + "host-unknown": "3/7", + "network-prohibited": "3/9", + "host-prohibited": "3/10", + "TOS-network-unreachable": "3/11", + "TOS-host-unreachable": "3/12", ++ # RFC-1812 Section 5.2.7.1 defines type 3, code 13-15 + "communication-prohibited": "3/13", + "host-precedence-violation": "3/14", + "precedence-cutoff": "3/15", + "source-quench": "4/0", + "network-redirect": "5/0", ++ "redirect": "5/0", + "host-redirect": "5/1", ++ "tos-host-redirect": "5/1", + "TOS-network-redirect": "5/2", ++ "tos-network-redirect": "5/2", + "TOS-host-redirect": "5/3", + "echo-request": "8/0", + "ping": "8/0", + "router-advertisement": "9/0", + "router-solicitation": "10/0", ++ "time-exceeded": "11/0", + "ttl-zero-during-transit": "11/0", + "ttl-zero-during-reassembly": "11/1", ++ "parameter-problem": "12/0", + "ip-header-bad": "12/0", + "required-option-missing": "12/1", + "timestamp-request": "13/0", +@@ -60,13 +70,19 @@ ICMP_TYPES = { + } + + ICMPV6_TYPES = { ++ "destination-unreachable": "1/0", + "no-route": "1/0", + "communication-prohibited": "1/1", ++ "beyond-scope": "1/2", + "address-unreachable": "1/3", + "port-unreachable": "1/4", ++ "failed-policy": "1/5", ++ "reject-route": "1/6", + "packet-too-big": "2/0", ++ "time-exceeded": "3/0", + "ttl-zero-during-transit": "3/0", + "ttl-zero-during-reassembly": "3/1", ++ "parameter-problem": "4/0", + "bad-header": "4/0", + "unknown-header-type": "4/1", + "unknown-option": "4/2", +@@ -81,6 +97,12 @@ ICMPV6_TYPES = { + "neighbour-advertisement": "136/0", + "neigbour-advertisement": "136/0", + "redirect": "137/0", ++ # MLD is RFC-2710 ++ "mld-listener-query": "130/0", ++ "mld-listener-report": "131/0", ++ "mld-listener-done": "132/0", ++ # MLDv2 is RFC-9777 ++ "mld2-listener-report": "143/0", + } + + def check_icmp_name(_name): +-- +2.47.3 + diff --git a/SOURCES/0044-v2.4.0-chore-icmp-convert-type-code-map-to-tuple.patch b/SOURCES/0044-v2.4.0-chore-icmp-convert-type-code-map-to-tuple.patch new file mode 100644 index 0000000..9700d80 --- /dev/null +++ b/SOURCES/0044-v2.4.0-chore-icmp-convert-type-code-map-to-tuple.patch @@ -0,0 +1,261 @@ +From 8c01d2e4f45c83c2f75e2c5038571f99a650b862 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Wed, 29 Oct 2025 11:32:35 -0400 +Subject: [PATCH 44/48] v2.4.0: chore(icmp): convert type/code map to tuple + +This will make it easier to use/query. + +(cherry picked from commit 6c33bbcdb60e257ec1102baaff128f5b345c67f8) +--- + src/firewall/core/icmp.py | 156 +++++++++++++++++----------------- + src/firewall/core/io/ipset.py | 26 ++++-- + 2 files changed, 99 insertions(+), 83 deletions(-) + +diff --git a/src/firewall/core/icmp.py b/src/firewall/core/icmp.py +index 2928052194ea..fc828ae59e51 100644 +--- a/src/firewall/core/icmp.py ++++ b/src/firewall/core/icmp.py +@@ -23,86 +23,88 @@ __all__ = [ "ICMP_TYPES", "ICMPV6_TYPES", + "check_icmp_type", "check_icmpv6_type" ] + + ICMP_TYPES = { +- "echo-reply": "0/0", +- "pong": "0/0", +- "destination-unreachable": "3/0", +- "network-unreachable": "3/0", +- "tos-network-unreachable": "3/0", +- "host-unreachable": "3/1", +- "tos-host-unreachable": "3/1", +- "protocol-unreachable": "3/2", +- "port-unreachable": "3/3", +- "fragmentation-needed": "3/4", +- "source-route-failed": "3/5", ++ # "type": (type, code, backend omit code) ++ "echo-reply": (0, 0, True), ++ "pong": (0, 0, True), ++ "destination-unreachable": (3, 0, True), ++ "network-unreachable": (3, 0, False), ++ "tos-network-unreachable": (3, 0, False), ++ "host-unreachable": (3, 1, False), ++ "tos-host-unreachable": (3, 1, False), ++ "protocol-unreachable": (3, 2, False), ++ "port-unreachable": (3, 3, False), ++ "fragmentation-needed": (3, 4, False), ++ "source-route-failed": (3, 5, False), + # RFC-1112 Section 3.2.2.1 defines type 3, code 6-12 +- "network-unknown": "3/6", +- "host-unknown": "3/7", +- "network-prohibited": "3/9", +- "host-prohibited": "3/10", +- "TOS-network-unreachable": "3/11", +- "TOS-host-unreachable": "3/12", ++ "network-unknown": (3, 6, False), ++ "host-unknown": (3, 7, False), ++ "network-prohibited": (3, 9, False), ++ "host-prohibited": (3, 10, False), ++ "TOS-network-unreachable": (3, 11, False), ++ "TOS-host-unreachable": (3, 12, False), + # RFC-1812 Section 5.2.7.1 defines type 3, code 13-15 +- "communication-prohibited": "3/13", +- "host-precedence-violation": "3/14", +- "precedence-cutoff": "3/15", +- "source-quench": "4/0", +- "network-redirect": "5/0", +- "redirect": "5/0", +- "host-redirect": "5/1", +- "tos-host-redirect": "5/1", +- "TOS-network-redirect": "5/2", +- "tos-network-redirect": "5/2", +- "TOS-host-redirect": "5/3", +- "echo-request": "8/0", +- "ping": "8/0", +- "router-advertisement": "9/0", +- "router-solicitation": "10/0", +- "time-exceeded": "11/0", +- "ttl-zero-during-transit": "11/0", +- "ttl-zero-during-reassembly": "11/1", +- "parameter-problem": "12/0", +- "ip-header-bad": "12/0", +- "required-option-missing": "12/1", +- "timestamp-request": "13/0", +- "timestamp-reply": "14/0", +- "address-mask-request": "17/0", +- "address-mask-reply": "18/0", ++ "communication-prohibited": (3, 13, False), ++ "host-precedence-violation": (3, 14, False), ++ "precedence-cutoff": (3, 15, False), ++ "source-quench": (4, 0, True), ++ "network-redirect": (5, 0, False), ++ "redirect": (5, 0, True), ++ "host-redirect": (5, 1, False), ++ "tos-host-redirect": (5, 1, False), ++ "TOS-network-redirect": (5, 2, False), ++ "tos-network-redirect": (5, 2, False), ++ "TOS-host-redirect": (5, 3, False), ++ "echo-request": (8, 0, True), ++ "ping": (8, 0, True), ++ "router-advertisement": (9, 0, True), ++ "router-solicitation": (10, 0, True), ++ "time-exceeded": (11, 0, True), ++ "ttl-zero-during-transit": (11, 0, False), ++ "ttl-zero-during-reassembly": (11, 1, False), ++ "parameter-problem": (12, 0, True), ++ "ip-header-bad": (12, 0, False), ++ "required-option-missing": (12, 1, False), ++ "timestamp-request": (13, 0, True), ++ "timestamp-reply": (14, 0, True), ++ "address-mask-request": (17, 0, False), ++ "address-mask-reply": (18, 0, False), + } + + ICMPV6_TYPES = { +- "destination-unreachable": "1/0", +- "no-route": "1/0", +- "communication-prohibited": "1/1", +- "beyond-scope": "1/2", +- "address-unreachable": "1/3", +- "port-unreachable": "1/4", +- "failed-policy": "1/5", +- "reject-route": "1/6", +- "packet-too-big": "2/0", +- "time-exceeded": "3/0", +- "ttl-zero-during-transit": "3/0", +- "ttl-zero-during-reassembly": "3/1", +- "parameter-problem": "4/0", +- "bad-header": "4/0", +- "unknown-header-type": "4/1", +- "unknown-option": "4/2", +- "echo-request": "128/0", +- "ping": "128/0", +- "echo-reply": "129/0", +- "pong": "129/0", +- "router-solicitation": "133/0", +- "router-advertisement": "134/0", +- "neighbour-solicitation": "135/0", +- "neigbour-solicitation": "135/0", +- "neighbour-advertisement": "136/0", +- "neigbour-advertisement": "136/0", +- "redirect": "137/0", ++ # "type": (type, code, backend omit code) ++ "destination-unreachable": (1, 0, True), ++ "no-route": (1, 0, False), ++ "communication-prohibited": (1, 1, False), ++ "beyond-scope": (1, 2, False), ++ "address-unreachable": (1, 3, False), ++ "port-unreachable": (1, 4, False), ++ "failed-policy": (1, 5, False), ++ "reject-route": (1, 6, False), ++ "packet-too-big": (2, 0, True), ++ "time-exceeded": (3, 0, True), ++ "ttl-zero-during-transit": (3, 0, False), ++ "ttl-zero-during-reassembly": (3, 1, False), ++ "parameter-problem": (4, 0, True), ++ "bad-header": (4, 0, False), ++ "unknown-header-type": (4, 1, False), ++ "unknown-option": (4, 2, False), ++ "echo-request": (128, 0, True), ++ "ping": (128, 0, True), ++ "echo-reply": (129, 0, True), ++ "pong": (129, 0, True), ++ "router-solicitation": (133, 0, True), ++ "router-advertisement": (134, 0, True), ++ "neighbour-solicitation": (135, 0, True), ++ "neigbour-solicitation": (135, 0, True), ++ "neighbour-advertisement": (136, 0, True), ++ "neigbour-advertisement": (136, 0, True), ++ "redirect": (137, 0, True), + # MLD is RFC-2710 +- "mld-listener-query": "130/0", +- "mld-listener-report": "131/0", +- "mld-listener-done": "132/0", ++ "mld-listener-query": (130, 0, True), ++ "mld-listener-report": (131, 0, True), ++ "mld-listener-done": (132, 0, True), + # MLDv2 is RFC-9777 +- "mld2-listener-report": "143/0", ++ "mld2-listener-report": (143, 0, True), + } + + def check_icmp_name(_name): +@@ -110,8 +112,8 @@ def check_icmp_name(_name): + return True + return False + +-def check_icmp_type(_type): +- if _type in ICMP_TYPES.values(): ++def check_icmp_type_code(_type, _code): ++ if (_type, _code) in ICMP_TYPES.values(): + return True + return False + +@@ -120,7 +122,7 @@ def check_icmpv6_name(_name): + return True + return False + +-def check_icmpv6_type(_type): +- if _type in ICMPV6_TYPES.values(): ++def check_icmpv6_type_code(_type, _code): ++ if (_type, _code) in ICMPV6_TYPES.values(): + return True + return False +diff --git a/src/firewall/core/io/ipset.py b/src/firewall/core/io/ipset.py +index a2fe16725875..6a612c8380f4 100644 +--- a/src/firewall/core/io/ipset.py ++++ b/src/firewall/core/io/ipset.py +@@ -35,8 +35,8 @@ from firewall.functions import checkIP, checkIP6, checkIPnMask, \ + from firewall.core.io.io_object import IO_Object, \ + IO_Object_ContentHandler, IO_Object_XMLGenerator + from firewall.core.ipset import IPSET_TYPES, IPSET_CREATE_OPTIONS +-from firewall.core.icmp import check_icmp_name, check_icmp_type, \ +- check_icmpv6_name, check_icmpv6_type ++from firewall.core.icmp import check_icmp_name, check_icmp_type_code, \ ++ check_icmpv6_name, check_icmpv6_type_code + from firewall.core.logger import log + from firewall import errors + from firewall.errors import FirewallError +@@ -203,24 +203,38 @@ class IPSet(IO_Object): + errors.INVALID_ENTRY, + "invalid protocol for family '%s' in '%s'" % \ + (family, entry)) +- if not check_icmp_name(splits[1]) and not \ +- check_icmp_type(splits[1]): ++ if not check_icmp_name(splits[1]) and "/" not in splits[1]: + raise FirewallError( + errors.INVALID_ENTRY, + "invalid icmp type '%s' in '%s'" % \ + (splits[1], entry)) ++ else: ++ (_type, _code) = splits[1].split("/") ++ if not check_icmp_type_code(_type, _code): ++ raise FirewallError( ++ errors.INVALID_ENTRY, ++ "invalid icmp type '%s' in '%s'" ++ % (splits[1], entry), ++ ) + elif splits[0] in [ "icmpv6", "ipv6-icmp" ]: + if family != "ipv6": + raise FirewallError( + errors.INVALID_ENTRY, + "invalid protocol for family '%s' in '%s'" % \ + (family, entry)) +- if not check_icmpv6_name(splits[1]) and not \ +- check_icmpv6_type(splits[1]): ++ if not check_icmpv6_name(splits[1]) and "/" not in splits[1]: + raise FirewallError( + errors.INVALID_ENTRY, + "invalid icmpv6 type '%s' in '%s'" % \ + (splits[1], entry)) ++ else: ++ (_type, _code) = splits[1].split("/") ++ if not check_icmpv6_type_code(_type, _code): ++ raise FirewallError( ++ errors.INVALID_ENTRY, ++ "invalid icmpv6 type '%s' in '%s'" ++ % (splits[1], entry), ++ ) + elif splits[0] not in [ "tcp", "sctp", "udp", "udplite" ] \ + and not checkProtocol(splits[0]): + raise FirewallError( +-- +2.47.3 + diff --git a/SOURCES/0045-v2.4.0-chore-nftables-simplify-icmp-match-fragments.patch b/SOURCES/0045-v2.4.0-chore-nftables-simplify-icmp-match-fragments.patch new file mode 100644 index 0000000..99d37a7 --- /dev/null +++ b/SOURCES/0045-v2.4.0-chore-nftables-simplify-icmp-match-fragments.patch @@ -0,0 +1,139 @@ +From 92d92e4f9d58d354b214a678f85a4fe64d510699 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Wed, 29 Oct 2025 11:49:12 -0400 +Subject: [PATCH 45/48] v2.4.0: chore(nftables): simplify icmp match fragments + +We can use ICMP_TYPES/ICMPV6_TYPES to get the codes and make generating +the match code generic. This eliminates ICMP_TYPES_FRAGMENTS. + +(cherry picked from commit d9c36de285fc3df1f1226972f8f9826d07e30921) +--- + src/firewall/core/nftables.py | 89 +++++------------------------------ + 1 file changed, 11 insertions(+), 78 deletions(-) + +diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py +index 254fe7cfbebe..2a48ff6ed7f5 100644 +--- a/src/firewall/core/nftables.py ++++ b/src/firewall/core/nftables.py +@@ -35,6 +35,7 @@ from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \ + Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock, \ + Rich_Tcp_Mss_Clamp, Rich_NFLog + from firewall.core.base import DEFAULT_ZONE_TARGET ++from firewall.core.icmp import ICMP_TYPES, ICMPV6_TYPES + from nftables.nftables import Nftables + + TABLE_NAME = "firewalld" +@@ -94,78 +95,6 @@ def _icmp_types_fragments(protocol, type, code=None): + "right": code}}) + return fragments + +-# Most ICMP types are provided by nft, but for the codes we have to use numeric +-# values. +-# +-ICMP_TYPES_FRAGMENTS = { +- "ipv4": { +- "communication-prohibited": _icmp_types_fragments("icmp", "destination-unreachable", 13), +- "destination-unreachable": _icmp_types_fragments("icmp", "destination-unreachable"), +- "echo-reply": _icmp_types_fragments("icmp", "echo-reply"), +- "echo-request": _icmp_types_fragments("icmp", "echo-request"), +- "fragmentation-needed": _icmp_types_fragments("icmp", "destination-unreachable", 4), +- "host-precedence-violation": _icmp_types_fragments("icmp", "destination-unreachable", 14), +- "host-prohibited": _icmp_types_fragments("icmp", "destination-unreachable", 10), +- "host-redirect": _icmp_types_fragments("icmp", "redirect", 1), +- "host-unknown": _icmp_types_fragments("icmp", "destination-unreachable", 7), +- "host-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 1), +- "ip-header-bad": _icmp_types_fragments("icmp", "parameter-problem", 1), +- "network-prohibited": _icmp_types_fragments("icmp", "destination-unreachable", 8), +- "network-redirect": _icmp_types_fragments("icmp", "redirect", 0), +- "network-unknown": _icmp_types_fragments("icmp", "destination-unreachable", 6), +- "network-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 0), +- "parameter-problem": _icmp_types_fragments("icmp", "parameter-problem"), +- "port-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 3), +- "precedence-cutoff": _icmp_types_fragments("icmp", "destination-unreachable", 15), +- "protocol-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 2), +- "redirect": _icmp_types_fragments("icmp", "redirect"), +- "required-option-missing": _icmp_types_fragments("icmp", "parameter-problem", 1), +- "router-advertisement": _icmp_types_fragments("icmp", "router-advertisement"), +- "router-solicitation": _icmp_types_fragments("icmp", "router-solicitation"), +- "source-quench": _icmp_types_fragments("icmp", "source-quench"), +- "source-route-failed": _icmp_types_fragments("icmp", "destination-unreachable", 5), +- "time-exceeded": _icmp_types_fragments("icmp", "time-exceeded"), +- "timestamp-reply": _icmp_types_fragments("icmp", "timestamp-reply"), +- "timestamp-request": _icmp_types_fragments("icmp", "timestamp-request"), +- "tos-host-redirect": _icmp_types_fragments("icmp", "redirect", 3), +- "tos-host-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 12), +- "tos-network-redirect": _icmp_types_fragments("icmp", "redirect", 2), +- "tos-network-unreachable": _icmp_types_fragments("icmp", "destination-unreachable", 11), +- "ttl-zero-during-reassembly": _icmp_types_fragments("icmp", "time-exceeded", 1), +- "ttl-zero-during-transit": _icmp_types_fragments("icmp", "time-exceeded", 0), +- }, +- +- "ipv6": { +- "address-unreachable": _icmp_types_fragments("icmpv6", "destination-unreachable", 3), +- "bad-header": _icmp_types_fragments("icmpv6", "parameter-problem", 0), +- "beyond-scope": _icmp_types_fragments("icmpv6", "destination-unreachable", 2), +- "communication-prohibited": _icmp_types_fragments("icmpv6", "destination-unreachable", 1), +- "destination-unreachable": _icmp_types_fragments("icmpv6", "destination-unreachable"), +- "echo-reply": _icmp_types_fragments("icmpv6", "echo-reply"), +- "echo-request": _icmp_types_fragments("icmpv6", "echo-request"), +- "failed-policy": _icmp_types_fragments("icmpv6", "destination-unreachable", 5), +- "mld-listener-done": _icmp_types_fragments("icmpv6", "mld-listener-done"), +- "mld-listener-query": _icmp_types_fragments("icmpv6", "mld-listener-query"), +- "mld-listener-report": _icmp_types_fragments("icmpv6", "mld-listener-report"), +- "mld2-listener-report": _icmp_types_fragments("icmpv6", "mld2-listener-report"), +- "neighbour-advertisement": _icmp_types_fragments("icmpv6", "nd-neighbor-advert"), +- "neighbour-solicitation": _icmp_types_fragments("icmpv6", "nd-neighbor-solicit"), +- "no-route": _icmp_types_fragments("icmpv6", "destination-unreachable", 0), +- "packet-too-big": _icmp_types_fragments("icmpv6", "packet-too-big"), +- "parameter-problem": _icmp_types_fragments("icmpv6", "parameter-problem"), +- "port-unreachable": _icmp_types_fragments("icmpv6", "destination-unreachable", 4), +- "redirect": _icmp_types_fragments("icmpv6", "nd-redirect"), +- "reject-route": _icmp_types_fragments("icmpv6", "destination-unreachable", 6), +- "router-advertisement": _icmp_types_fragments("icmpv6", "nd-router-advert"), +- "router-solicitation": _icmp_types_fragments("icmpv6", "nd-router-solicit"), +- "time-exceeded": _icmp_types_fragments("icmpv6", "time-exceeded"), +- "ttl-zero-during-reassembly": _icmp_types_fragments("icmpv6", "time-exceeded", 1), +- "ttl-zero-during-transit": _icmp_types_fragments("icmpv6", "time-exceeded", 0), +- "unknown-header-type": _icmp_types_fragments("icmpv6", "parameter-problem", 1), +- "unknown-option": _icmp_types_fragments("icmpv6", "parameter-problem", 2), +- } +-} +- + class nftables(object): + name = "nftables" + policies_supported = True +@@ -566,12 +495,12 @@ class nftables(object): + return rules + + def supported_icmp_types(self, ipv=None): +- # nftables supports any icmp_type via arbitrary type/code matching. +- # We just need a translation for it in ICMP_TYPES_FRAGMENTS. + supported = set() + +- for _ipv in [ipv] if ipv else ICMP_TYPES_FRAGMENTS.keys(): +- supported.update(ICMP_TYPES_FRAGMENTS[_ipv].keys()) ++ if ipv is None or ipv == "ipv4": ++ supported.update(ICMP_TYPES.keys()) ++ if ipv is None or ipv == "ipv6": ++ supported.update(ICMPV6_TYPES.keys()) + + return list(supported) + +@@ -1577,8 +1506,12 @@ class nftables(object): + return [{add_del: {"rule": rule}}] + + def _icmp_types_to_nft_fragments(self, ipv, icmp_type): +- if icmp_type in ICMP_TYPES_FRAGMENTS[ipv]: +- return ICMP_TYPES_FRAGMENTS[ipv][icmp_type] ++ if ipv == "ipv4" and icmp_type in ICMP_TYPES: ++ _type, _code, _omit_code = ICMP_TYPES[icmp_type] ++ return _icmp_types_fragments("icmp", _type, None if _omit_code else _code) ++ elif ipv == "ipv6" and icmp_type in ICMPV6_TYPES: ++ _type, _code, _omit_code = ICMPV6_TYPES[icmp_type] ++ return _icmp_types_fragments("icmpv6", _type, None if _omit_code else _code) + else: + raise FirewallError(INVALID_ICMPTYPE, + "ICMP type '%s' not supported by %s for %s" % (icmp_type, self.name, ipv)) +-- +2.47.3 + diff --git a/SOURCES/0046-v2.4.0-chore-nftables-move-_icmp_types_fragments-ins.patch b/SOURCES/0046-v2.4.0-chore-nftables-move-_icmp_types_fragments-ins.patch new file mode 100644 index 0000000..28a5a7a --- /dev/null +++ b/SOURCES/0046-v2.4.0-chore-nftables-move-_icmp_types_fragments-ins.patch @@ -0,0 +1,68 @@ +From 47d8ef737fc2f7b5990e39a86efb9358dc761076 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Wed, 29 Oct 2025 11:51:18 -0400 +Subject: [PATCH 46/48] v2.4.0: chore(nftables): move _icmp_types_fragments() + inside the class + +It was the only function outside of the class and is not useful to any +other code. Put it inside the class with everything else. + +(cherry picked from commit 69ad16a4435a0b49b4196aa1a99ee963b72c4b69) +--- + src/firewall/core/nftables.py | 28 ++++++++++++++++------------ + 1 file changed, 16 insertions(+), 12 deletions(-) + +diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py +index 2a48ff6ed7f5..0d3e31299d15 100644 +--- a/src/firewall/core/nftables.py ++++ b/src/firewall/core/nftables.py +@@ -85,16 +85,6 @@ IPTABLES_TO_NFT_HOOK = { + }, + } + +-def _icmp_types_fragments(protocol, type, code=None): +- fragments = [{"match": {"left": {"payload": {"protocol": protocol, "field": "type"}}, +- "op": "==", +- "right": type}}] +- if code is not None: +- fragments.append({"match": {"left": {"payload": {"protocol": protocol, "field": "code"}}, +- "op": "==", +- "right": code}}) +- return fragments +- + class nftables(object): + name = "nftables" + policies_supported = True +@@ -1505,13 +1495,27 @@ class nftables(object): + rule.update(self._rich_rule_priority_fragment(rich_rule)) + return [{add_del: {"rule": rule}}] + ++ def _icmp_types_fragments(self, protocol, type, code=None): ++ fragments = [{"match": {"left": {"payload": {"protocol": protocol, "field": "type"}}, ++ "op": "==", ++ "right": type}}] ++ if code is not None: ++ fragments.append({"match": {"left": {"payload": {"protocol": protocol, "field": "code"}}, ++ "op": "==", ++ "right": code}}) ++ return fragments ++ + def _icmp_types_to_nft_fragments(self, ipv, icmp_type): + if ipv == "ipv4" and icmp_type in ICMP_TYPES: + _type, _code, _omit_code = ICMP_TYPES[icmp_type] +- return _icmp_types_fragments("icmp", _type, None if _omit_code else _code) ++ return self._icmp_types_fragments( ++ "icmp", _type, None if _omit_code else _code ++ ) + elif ipv == "ipv6" and icmp_type in ICMPV6_TYPES: + _type, _code, _omit_code = ICMPV6_TYPES[icmp_type] +- return _icmp_types_fragments("icmpv6", _type, None if _omit_code else _code) ++ return self._icmp_types_fragments( ++ "icmpv6", _type, None if _omit_code else _code ++ ) + else: + raise FirewallError(INVALID_ICMPTYPE, + "ICMP type '%s' not supported by %s for %s" % (icmp_type, self.name, ipv)) +-- +2.47.3 + diff --git a/SOURCES/0047-v2.4.0-chore-ipXtables-simplify-icmp-match-fragments.patch b/SOURCES/0047-v2.4.0-chore-ipXtables-simplify-icmp-match-fragments.patch new file mode 100644 index 0000000..818ab9f --- /dev/null +++ b/SOURCES/0047-v2.4.0-chore-ipXtables-simplify-icmp-match-fragments.patch @@ -0,0 +1,115 @@ +From ea62b989b3ccb4bdf99cd42cfc17f08723c63407 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Wed, 29 Oct 2025 16:33:17 -0400 +Subject: [PATCH 47/48] v2.4.0: chore(ipXtables): simplify icmp match fragments + +We can use ICMP_TYPES/ICMPV6_TYPES to get the codes and make rule +generation generic while also supporting more types that don't have name +support in iptables, e.g. mld. + +(cherry picked from commit 1562687ae6ca3cbcf6ece25126af9116adfb897b) +--- + src/firewall/core/ipXtables.py | 61 +++++++++++++++------------------- + 1 file changed, 27 insertions(+), 34 deletions(-) + +diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py +index 339840c37ba4..efe292b3f638 100644 +--- a/src/firewall/core/ipXtables.py ++++ b/src/firewall/core/ipXtables.py +@@ -27,10 +27,12 @@ from firewall.core.logger import log + from firewall.functions import tempFile, readfile, splitArgs, check_mac, portStr, \ + check_single_address, check_address, normalizeIP6 + from firewall import config +-from firewall.errors import FirewallError, INVALID_PASSTHROUGH, INVALID_RULE, UNKNOWN_ERROR, INVALID_ADDR ++from firewall.errors import FirewallError, INVALID_PASSTHROUGH, INVALID_RULE, UNKNOWN_ERROR, INVALID_ADDR, \ ++ INVALID_ICMPTYPE + from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, Rich_NFLog, \ + Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock, Rich_Tcp_Mss_Clamp + from firewall.core.base import DEFAULT_ZONE_TARGET ++from firewall.core.icmp import ICMP_TYPES, ICMPV6_TYPES + import string + + POLICY_CHAIN_PREFIX = "" +@@ -598,37 +600,14 @@ class ip4tables(object): + return rules + + def supported_icmp_types(self, ipv=None): +- """Return ICMP types that are supported by the iptables/ip6tables command and kernel""" +- ret = [ ] +- output = "" +- try: +- output = self.__run(["-p", +- "icmp" if self.ipv == "ipv4" else "ipv6-icmp", +- "--help"]) +- except ValueError as ex: +- if self.ipv == "ipv4": +- log.debug1("iptables error: %s" % ex) +- else: +- log.debug1("ip6tables error: %s" % ex) +- lines = output.splitlines() +- +- in_types = False +- for line in lines: +- #print(line) +- if in_types: +- line = line.strip().lower() +- splits = line.split() +- for split in splits: +- if split.startswith("(") and split.endswith(")"): +- x = split[1:-1] +- else: +- x = split +- if x not in ret: +- ret.append(x) +- if self.ipv == "ipv4" and line.startswith("Valid ICMP Types:") or \ +- self.ipv == "ipv6" and line.startswith("Valid ICMPv6 Types:"): +- in_types = True +- return ret ++ supported = set() ++ ++ if ipv is None or self.ipv == "ipv4": ++ supported.update(ICMP_TYPES.keys()) ++ if ipv is None or self.ipv == "ipv6": ++ supported.update(ICMPV6_TYPES.keys()) ++ ++ return list(supported) + + def build_default_tables(self): + # nothing to do, they always exist +@@ -1350,6 +1329,20 @@ class ip4tables(object): + + return rules + ++ def _icmp_types_fragment(self, icmp_type): ++ if self.ipv == "ipv4" and icmp_type in ICMP_TYPES: ++ _type, _code, _omit_code = ICMP_TYPES[icmp_type] ++ _type_str = str(_type) if _omit_code else str(_type) + "/" + str(_code) ++ return ["-m", "icmp", "--icmp-type", _type_str] ++ elif self.ipv == "ipv6" and icmp_type in ICMPV6_TYPES: ++ _type, _code, _omit_code = ICMPV6_TYPES[icmp_type] ++ _type_str = str(_type) if _omit_code else str(_type) + "/" + str(_code) ++ return ["-m", "icmp6", "--icmpv6-type", _type_str] ++ else: ++ raise FirewallError( ++ INVALID_ICMPTYPE, f"ICMP type {icmp_type} not supported by {self.name}" ++ ) ++ + def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None): + table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) +@@ -1357,10 +1350,10 @@ class ip4tables(object): + + if self.ipv == "ipv4": + proto = [ "-p", "icmp" ] +- match = [ "-m", "icmp", "--icmp-type", ict.name ] ++ match = self._icmp_types_fragment(ict.name) + else: + proto = [ "-p", "ipv6-icmp" ] +- match = [ "-m", "icmp6", "--icmpv6-type", ict.name ] ++ match = self._icmp_types_fragment(ict.name) + + rules = [] + if self._fw.policy.query_icmp_block_inversion(policy): +-- +2.47.3 + diff --git a/SOURCES/0048-v2.4.0-fix-policy-allow-host-ipv6-allow-MLD-packets.patch b/SOURCES/0048-v2.4.0-fix-policy-allow-host-ipv6-allow-MLD-packets.patch new file mode 100644 index 0000000..a195df4 --- /dev/null +++ b/SOURCES/0048-v2.4.0-fix-policy-allow-host-ipv6-allow-MLD-packets.patch @@ -0,0 +1,165 @@ +From bf8461cacb5a87bea1206dbc4a33f05d1007de46 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Mon, 27 Oct 2025 16:37:35 -0400 +Subject: [PATCH 48/48] v2.4.0: fix(policy): allow-host-ipv6: allow MLD packets + +RFC 4890 Section 4.4.1 makes it very clear that MLD packets must be +allowed by default. + +Fixes: RHEL-54411 +Fixes: RHEL-123703 +(cherry picked from commit 3c42d02b770391686e6f7b202556ea4eee0722f5) +--- + config/policies/allow-host-ipv6.xml | 16 +++++++++++++ + src/tests/dbus/policy_permanent_functional.at | 4 ++-- + src/tests/dbus/policy_runtime_functional.at | 2 +- + src/tests/features/policy.at | 24 +++++++++++++++++++ + src/tests/regression/rhbz2222044.at | 2 +- + 5 files changed, 44 insertions(+), 4 deletions(-) + +diff --git a/config/policies/allow-host-ipv6.xml b/config/policies/allow-host-ipv6.xml +index 0de7629c3809..33b5d13206a6 100644 +--- a/config/policies/allow-host-ipv6.xml ++++ b/config/policies/allow-host-ipv6.xml +@@ -20,4 +20,20 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +diff --git a/src/tests/dbus/policy_permanent_functional.at b/src/tests/dbus/policy_permanent_functional.at +index a5e1af90cc1c..44b8ebeea18e 100644 +--- a/src/tests/dbus/policy_permanent_functional.at ++++ b/src/tests/dbus/policy_permanent_functional.at +@@ -155,7 +155,7 @@ DBUS_CHECK([config/policy/${DBUS_BUILTIN_POLICY_OBJ}], [config.policy.getSetting + 'ingress_zones': m4_escape([<['ANY']>]) + 'masquerade': + 'priority': <-15000> +- 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept']>]) ++ 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept', 'rule family="ipv6" icmp-type name="mld-listener-done" accept', 'rule family="ipv6" icmp-type name="mld-listener-query" accept', 'rule family="ipv6" icmp-type name="mld-listener-report" accept', 'rule family="ipv6" icmp-type name="mld2-listener-report" accept']>]) + 'short': <'Allow host IPv6'> + 'target': <'DROP'> + 'version': <'1.2'> +@@ -168,7 +168,7 @@ DBUS_CHECK([config/policy/${DBUS_BUILTIN_POLICY_OBJ}], [config.policy.getSetting + 'ingress_zones': m4_escape([<['ANY']>]) + 'masquerade': + 'priority': <-15000> +- 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept']>]) ++ 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept', 'rule family="ipv6" icmp-type name="mld-listener-done" accept', 'rule family="ipv6" icmp-type name="mld-listener-query" accept', 'rule family="ipv6" icmp-type name="mld-listener-report" accept', 'rule family="ipv6" icmp-type name="mld2-listener-report" accept']>]) + 'short': <'Allow host IPv6'> + 'target': <'CONTINUE'> + ]) +diff --git a/src/tests/dbus/policy_runtime_functional.at b/src/tests/dbus/policy_runtime_functional.at +index ab9a43dda8f3..08cb3b1051ff 100644 +--- a/src/tests/dbus/policy_runtime_functional.at ++++ b/src/tests/dbus/policy_runtime_functional.at +@@ -11,7 +11,7 @@ DBUS_CHECK([], [policy.getPolicySettings], ["allow-host-ipv6"], 0, [dnl + 'ingress_zones': m4_escape([<['ANY']>]) + 'masquerade': + 'priority': <-15000> +- 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept']>]) ++ 'rich_rules': m4_escape([<['rule family="ipv6" icmp-type name="neighbour-advertisement" accept', 'rule family="ipv6" icmp-type name="neighbour-solicitation" accept', 'rule family="ipv6" icmp-type name="router-advertisement" accept', 'rule family="ipv6" icmp-type name="redirect" accept', 'rule family="ipv6" icmp-type name="mld-listener-done" accept', 'rule family="ipv6" icmp-type name="mld-listener-query" accept', 'rule family="ipv6" icmp-type name="mld-listener-report" accept', 'rule family="ipv6" icmp-type name="mld2-listener-report" accept']>]) + 'short': <'Allow host IPv6'> + 'target': <'CONTINUE'> + ]) +diff --git a/src/tests/features/policy.at b/src/tests/features/policy.at +index 7136c0cf5ab9..e237c333651a 100644 +--- a/src/tests/features/policy.at ++++ b/src/tests/features/policy.at +@@ -127,6 +127,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + FWD_CHECK([--permanent --info-policy allow-host-ipv6 | TRIM_WHITESPACE], 0, [m4_strip([dnl + allow-host-ipv6 (active) +@@ -146,6 +150,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + + FWD_CHECK([--list-all-policies | TRIM_WHITESPACE], 0, [m4_strip([dnl +@@ -166,6 +174,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + FWD_CHECK([--permanent --list-all-policies | TRIM_WHITESPACE], 0, [m4_strip([dnl + allow-host-ipv6 (active) +@@ -185,6 +197,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + + FWD_CHECK([--policy allow-host-ipv6 --list-all | TRIM_WHITESPACE], 0, [m4_strip([dnl +@@ -205,6 +221,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + FWD_CHECK([--permanent --policy allow-host-ipv6 --list-all | TRIM_WHITESPACE], 0, [m4_strip([dnl + allow-host-ipv6 (active) +@@ -224,6 +244,10 @@ allow-host-ipv6 (active) + rule family="ipv6" icmp-type name="neighbour-solicitation" accept + rule family="ipv6" icmp-type name="router-advertisement" accept + rule family="ipv6" icmp-type name="redirect" accept ++ rule family="ipv6" icmp-type name="mld-listener-done" accept ++ rule family="ipv6" icmp-type name="mld-listener-query" accept ++ rule family="ipv6" icmp-type name="mld-listener-report" accept ++ rule family="ipv6" icmp-type name="mld2-listener-report" accept + ])]) + + FWD_END_TEST +diff --git a/src/tests/regression/rhbz2222044.at b/src/tests/regression/rhbz2222044.at +index 2d0333865076..cfa8b32d6c86 100644 +--- a/src/tests/regression/rhbz2222044.at ++++ b/src/tests/regression/rhbz2222044.at +@@ -11,7 +11,7 @@ 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 +-326 ++330 + ]) + ], [ dnl iptables + NS_CHECK([iptables-save | wc -l], 0, [dnl +-- +2.47.3 + diff --git a/SPECS/firewalld.spec b/SPECS/firewalld.spec index 649cc18..0a0775d 100644 --- a/SPECS/firewalld.spec +++ b/SPECS/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: 15%{?dist} +Release: 18%{?dist} URL: http://www.firewalld.org License: GPLv2+ Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.bz2 @@ -39,6 +39,20 @@ Patch31: 0031-v2.4.0-fix-fw-start-remove-ipset-probe.patch Patch32: 0032-v2.4.0-fix-systemd-allow-start-code-251-RUNNING_BUT_FAILED.patch Patch33: 0033-v2.4.0-fix-policy-rich-verify-ipset-exists.patch Patch34: 0034-v2.4.0-test-rich-rule-reference-invalid-ipset.patch +Patch35: 0035-v2.2.0-feat-add-iperf-2-3-services.patch +Patch36: 0036-v2.4.0-test-functions-add-macro-WAIT_UNTIL.patch +Patch37: 0037-v2.4.0-fix-server-load-firewall-rules-before-claimin.patch +Patch38: 0038-v2.4.0-Revert-fix-systemd-allow-start-code-251-RUNNI.patch +Patch39: 0039-v2.4.0-Revert-fix-systemd-verify-firewalld-is-respon.patch +Patch40: 0040-v2.4.0-fix-nftables-ipset-add-entries-from-GLib-loop.patch +Patch41: 0041-v2.4.0-test-ipset-scale-verify-all-the-entries-were-.patch +Patch42: 0042-v2.4.0-fix-systemd-Requires-dbus.patch +Patch43: 0043-v2.4.0-chore-icmp-add-all-icmptypes-to-ICMP_TYPES-di.patch +Patch44: 0044-v2.4.0-chore-icmp-convert-type-code-map-to-tuple.patch +Patch45: 0045-v2.4.0-chore-nftables-simplify-icmp-match-fragments.patch +Patch46: 0046-v2.4.0-chore-nftables-move-_icmp_types_fragments-ins.patch +Patch47: 0047-v2.4.0-chore-ipXtables-simplify-icmp-match-fragments.patch +Patch48: 0048-v2.4.0-fix-policy-allow-host-ipv6-allow-MLD-packets.patch BuildArch: noarch BuildRequires: autoconf BuildRequires: automake @@ -262,6 +276,17 @@ rm -rf %{buildroot}%{_datadir}/firewalld/testsuite %{_mandir}/man1/firewall-config*.1* %changelog +* Tue Dec 02 2025 Eric Garver - 1.3.4-18 +- fix(policy): allow-host-ipv6: allow MLD packets + +* Tue Dec 02 2025 Eric Garver - 1.3.4-17 +- fix(server): load firewall rules before claiming dbus +- fix(nftables): ipset: add entries from GLib loop when idle +- fix(systemd): Requires dbus + +* Tue Dec 02 2025 Eric Garver - 1.3.4-16 +- feat: add iperf{2,3} services + * Tue Jun 17 2025 Eric Garver - 1.3.4-15 - fix(policy): rich: verify ipset exists