Compare commits

...

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

21 changed files with 2 additions and 2374 deletions

View File

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

View File

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

View File

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

View File

@ -1,131 +0,0 @@
From b18ab581731a302ddba0428b685360d315293e73 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Wed, 29 Nov 2023 17:02:07 +0100
Subject: [PATCH 21/26] v2.1.0: feat(icmp): add ICMPv6 Multicast Listener
Discovery (MLD) types
Note that ip6tables does not support these ICMPv6 types. Currently,
the name of the ICMP types in firewalld must correspond to the names
in iptables. As ip6tables doesn't support it, it does not. If ip6tables
adds support for "mld-listener-query", but calls it differently, we have
a problem. Nothing that can be done about that.
`man nft` also lists an alias "mld-listener-reduction" (for
"mld-listener-done", type 132). That alias is not supported. Use the
name as from RFC 4890.
(cherry picked from commit dd88bbf812e0a50766b69c2bf12470ecf9d2466a)
---
config/Makefile.am | 4 ++++
config/icmptypes/mld-listener-done.xml | 7 +++++++
config/icmptypes/mld-listener-query.xml | 7 +++++++
config/icmptypes/mld-listener-report.xml | 7 +++++++
config/icmptypes/mld2-listener-report.xml | 7 +++++++
po/POTFILES.in | 4 ++++
src/firewall/core/nftables.py | 4 ++++
7 files changed, 40 insertions(+)
create mode 100644 config/icmptypes/mld-listener-done.xml
create mode 100644 config/icmptypes/mld-listener-query.xml
create mode 100644 config/icmptypes/mld-listener-report.xml
create mode 100644 config/icmptypes/mld2-listener-report.xml
diff --git a/config/Makefile.am b/config/Makefile.am
index f844a5a00e2f..a11c6abae583 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -83,6 +83,10 @@ CONFIG_FILES = \
icmptypes/host-unknown.xml \
icmptypes/host-unreachable.xml \
icmptypes/ip-header-bad.xml \
+ icmptypes/mld-listener-done.xml \
+ icmptypes/mld-listener-query.xml \
+ icmptypes/mld-listener-report.xml \
+ icmptypes/mld2-listener-report.xml \
icmptypes/neighbour-advertisement.xml \
icmptypes/neighbour-solicitation.xml \
icmptypes/network-prohibited.xml \
diff --git a/config/icmptypes/mld-listener-done.xml b/config/icmptypes/mld-listener-done.xml
new file mode 100644
index 000000000000..09b8bbba5b90
--- /dev/null
+++ b/config/icmptypes/mld-listener-done.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Done</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Done (type 132) (RFC 4890 section 4.4.1). Also known as mld-listener-reduction to nft.</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld-listener-query.xml b/config/icmptypes/mld-listener-query.xml
new file mode 100644
index 000000000000..418685578d1d
--- /dev/null
+++ b/config/icmptypes/mld-listener-query.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Query</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Query (type 130) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld-listener-report.xml b/config/icmptypes/mld-listener-report.xml
new file mode 100644
index 000000000000..98fb4161b298
--- /dev/null
+++ b/config/icmptypes/mld-listener-report.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Report</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Report (type 131) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld2-listener-report.xml b/config/icmptypes/mld2-listener-report.xml
new file mode 100644
index 000000000000..faee68c95b20
--- /dev/null
+++ b/config/icmptypes/mld2-listener-report.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLDv2 Multicast Listener Report</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDLv2) of type Multicast Listener Report (type 143) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 249cff8d0d2f..3bb71fd3d332 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,6 +15,10 @@ config/icmptypes/host-redirect.xml
config/icmptypes/host-unknown.xml
config/icmptypes/host-unreachable.xml
config/icmptypes/ip-header-bad.xml
+config/icmptypes/mld-listener-done.xml
+config/icmptypes/mld-listener-query.xml
+config/icmptypes/mld-listener-report.xml
+config/icmptypes/mld2-listener-report.xml
config/icmptypes/neighbour-advertisement.xml
config/icmptypes/neighbour-solicitation.xml
config/icmptypes/network-prohibited.xml
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index d238451ebd5d..67fb6457e86c 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -140,6 +140,10 @@ ICMP_TYPES_FRAGMENTS = {
"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),
--
2.43.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,38 +0,0 @@
From 3a56ea30acb41358742a94f088f12bd4f1ba1f80 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 31 Jan 2023 09:24:56 -0500
Subject: [PATCH 27/30] v2.0.0: test(atlocal): pass EBTABLES to testsuite
(cherry picked from commit a5adb26a5eebdaa6e978c580d4fb73f7aa06802f)
---
src/tests/atlocal.in | 1 +
src/tests/functions.at | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/tests/atlocal.in b/src/tests/atlocal.in
index 8c5493ac38df..595a96f0f5c9 100644
--- a/src/tests/atlocal.in
+++ b/src/tests/atlocal.in
@@ -1,5 +1,6 @@
export PYTHON="@PYTHON@"
+export EBTABLES="@EBTABLES@"
export IPTABLES="@IPTABLES@"
export IPTABLES_RESTORE="@IPTABLES_RESTORE@"
export IP6TABLES="@IP6TABLES@"
diff --git a/src/tests/functions.at b/src/tests/functions.at
index a2989c6345da..35e3271ce68d 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -368,7 +368,7 @@ m4_define([EBTABLES_LIST_RULES_NORMALIZE], [dnl
m4_define([EBTABLES_LIST_RULES], [
dnl ebtables commit 5f508b76a0ce change list output for inversion.
m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [], [
- NS_CHECK([PIPESTATUS0([ebtables --concurrent -t $1 -L $2], [EBTABLES_LIST_RULES_NORMALIZE])],
+ NS_CHECK([PIPESTATUS0([$EBTABLES --concurrent -t $1 -L $2], [EBTABLES_LIST_RULES_NORMALIZE])],
[$3], [m4_strip([$4])], [m4_strip([$5])], [$6], [$7])
])
])
--
2.43.0

View File

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

View File

@ -1,117 +0,0 @@
From 17c70eba7ddfd8a8687b16102cf5ee988e33993f Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 30 Jan 2023 16:42:50 -0500
Subject: [PATCH 29/30] v2.0.0: feat(direct): avoid iptables flush if using
nftables backend
If FirewallBackend=nftables and there are no direct rules; then we can
avoid flushing iptables at startup and shutdown. This means other
applications can control iptables while firewalld only touches nftables.
Fixes: #863
(cherry picked from commit b7faa74db15e2d1ebd9fdfcdc7579874d3a2fa87)
---
src/firewall/core/fw.py | 31 +++++++++++++++++++++++++++----
src/firewall/core/fw_direct.py | 9 +++++++++
2 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 5cef18b5f889..a2ad39bd9f5f 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -425,7 +425,8 @@ class Firewall(object):
transaction = FirewallTransaction(self)
# flush rules
- self.flush(use_transaction=transaction)
+ if not reload:
+ self.flush(use_transaction=transaction)
# If modules need to be unloaded in complete reload or if there are
# ipsets to get applied, limit the transaction to flush.
@@ -836,7 +837,26 @@ class Firewall(object):
if use_transaction is None:
transaction.execute(True)
- # flush and policy
+ def may_skip_flush_direct_backends(self):
+ if self.nftables_enabled and not self.direct.has_runtime_configuration():
+ return True
+
+ return False
+
+ def flush_direct_backends(self, use_transaction=None):
+ if use_transaction is None:
+ transaction = FirewallTransaction(self)
+ else:
+ transaction = use_transaction
+
+ for backend in self.all_backends():
+ if backend in self.enabled_backends():
+ continue
+ rules = backend.build_flush_rules()
+ transaction.add_rules(backend, rules)
+
+ if use_transaction is None:
+ transaction.execute(True)
def flush(self, use_transaction=None):
if use_transaction is None:
@@ -846,7 +866,10 @@ class Firewall(object):
log.debug1("Flushing rule set")
- for backend in self.all_backends():
+ if not self.may_skip_flush_direct_backends():
+ self.flush_direct_backends(use_transaction=transaction)
+
+ for backend in self.enabled_backends():
rules = backend.build_flush_rules()
transaction.add_rules(backend, rules)
@@ -1002,7 +1025,7 @@ class Firewall(object):
if not _panic:
self.set_policy("DROP")
- # stop
+ self.flush()
self.cleanup()
start_exception = None
diff --git a/src/firewall/core/fw_direct.py b/src/firewall/core/fw_direct.py
index a35ebce1f276..5d4cc6a6918e 100644
--- a/src/firewall/core/fw_direct.py
+++ b/src/firewall/core/fw_direct.py
@@ -219,6 +219,9 @@ class FirewallDirect(object):
else:
transaction = use_transaction
+ if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends)
+
#TODO: policy="ACCEPT"
self._chain(True, ipv, table, chain, transaction)
@@ -265,6 +268,9 @@ class FirewallDirect(object):
else:
transaction = use_transaction
+ if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends)
+
self._rule(True, ipv, table, chain, priority, args, transaction)
if use_transaction is None:
@@ -347,6 +353,9 @@ class FirewallDirect(object):
else:
transaction = use_transaction
+ if self._fw.may_skip_flush_direct_backends():
+ transaction.add_pre(self._fw.flush_direct_backends)
+
self._passthrough(True, ipv, list(args), transaction)
if use_transaction is None:
--
2.43.0

View File

@ -1,175 +0,0 @@
From 2e34d7361f8a7528f5e5d86f794bc87c94f8214e Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 30 Jan 2023 14:43:18 -0500
Subject: [PATCH 30/30] v2.0.0: test(direct): avoid iptables flush if using
nftables backend
Coverage: #863
(cherry picked from commit dcd0dd3674ea8ef757a1b41f6b53717a45e821aa)
---
src/tests/features/features.at | 1 +
.../features/iptables_no_flush_on_shutdown.at | 143 ++++++++++++++++++
2 files changed, 144 insertions(+)
create mode 100644 src/tests/features/iptables_no_flush_on_shutdown.at
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index 381bf6dba0e4..cfe8e88b46a9 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -14,3 +14,4 @@ m4_include([features/icmp_blocks.at])
m4_include([features/rpfilter.at])
m4_include([features/zone_combine.at])
m4_include([features/rich_destination_ipset.at])
+m4_include([features/iptables_no_flush_on_shutdown.at])
diff --git a/src/tests/features/iptables_no_flush_on_shutdown.at b/src/tests/features/iptables_no_flush_on_shutdown.at
new file mode 100644
index 000000000000..a3bb1395791d
--- /dev/null
+++ b/src/tests/features/iptables_no_flush_on_shutdown.at
@@ -0,0 +1,143 @@
+m4_if(nftables, FIREWALL_BACKEND, [
+
+dnl If FirewallBackend=nftables, and there are no --direct rules, then we can
+dnl avoid flushing iptables on shutdown. We can also avoid a flush on startup
+dnl if there are no permanent direct rules. But we will have to flush on the
+dnl first direct rule added.
+FWD_START_TEST([avoid iptables flush if using nftables])
+AT_KEYWORDS(direct gh863)
+
+dnl no flush on reload if no direct rules
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IF_HOST_SUPPORTS_IP6TABLES([
+NS_CHECK([$IP6TABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IP6TABLES -t filter -I firewalld_testsuite -j ACCEPT])
+])
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT
+])
+FWD_RELOAD()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT
+])
+
+dnl no flush on restart (or stop) if no direct rules
+FWD_RESTART()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT
+])
+
+dnl the first runtime direct rule should trigger an iptables flush
+FWD_CHECK([--direct --add-rule ipv4 filter INPUT 1 -j ACCEPT], 0, [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IF_HOST_SUPPORTS_IP6TABLES([
+NS_CHECK([$IP6TABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IP6TABLES -t filter -I firewalld_testsuite -j ACCEPT])
+])
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 0, [dnl
+ ACCEPT all ::/0 ::/0
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 0, [dnl
+ -j ACCEPT
+])
+FWD_RELOAD()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [INPUT], 0, [dnl
+])
+
+dnl permanent direct rules should trigger a flush at start
+FWD_CHECK([--permanent --direct --add-rule ipv4 filter INPUT 1 -j ACCEPT], 0, [ignore])
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IF_HOST_SUPPORTS_IP6TABLES([
+NS_CHECK([$IP6TABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IP6TABLES -t filter -I firewalld_testsuite -j ACCEPT])
+])
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+FWD_RELOAD()
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+ ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
+])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [INPUT], 0, [dnl
+])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [INPUT], 0, [dnl
+])
+
+FWD_CHECK([--permanent --direct --remove-rule ipv4 filter INPUT 1 -j ACCEPT], 0, [ignore])
+FWD_RELOAD()
+
+dnl adding a chain should trigger a flush
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IF_HOST_SUPPORTS_IP6TABLES([
+NS_CHECK([$IP6TABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IP6TABLES -t filter -I firewalld_testsuite -j ACCEPT])
+])
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+FWD_CHECK([--direct --add-chain ipv4 filter firewalld_foobar], 0, [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+FWD_RELOAD()
+
+dnl adding a chain should trigger a flush
+NS_CHECK([$IPTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IPTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+IF_HOST_SUPPORTS_IP6TABLES([
+NS_CHECK([$IP6TABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$IP6TABLES -t filter -I firewalld_testsuite -j ACCEPT])
+])
+NS_CHECK([$EBTABLES -t filter -N firewalld_testsuite])
+NS_CHECK([$EBTABLES -t filter -I firewalld_testsuite -j ACCEPT])
+FWD_CHECK([--direct --add-passthrough ipv4 -t filter -I INPUT -j ACCEPT], 0, [ignore])
+IPTABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+IP6TABLES_LIST_RULES_ALWAYS([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+EBTABLES_LIST_RULES([filter], [firewalld_testsuite], 1, [ignore], [ignore])
+
+FWD_END_TEST()
+
+])
--
2.43.0

View File

@ -1,29 +0,0 @@
From 4c95c843cd21f618677fe4d047b187facb00d027 Mon Sep 17 00:00:00 2001
From: Pierre Riteau <pierre@stackhpc.com>
Date: Mon, 22 Apr 2024 11:50:25 +0200
Subject: [PATCH] v2.2.0: fix(service): update highest port number for ceph
The highest port number used by Ceph was updated in
https://github.com/ceph/ceph/pull/42210.
Fixes https://github.com/firewalld/firewalld/issues/1329
(cherry picked from commit f514a3ea4b59a0be11467d1b68a992329b6dc8dd)
---
config/services/ceph.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/services/ceph.xml b/config/services/ceph.xml
index efed53691afd..eb6a3f2d1e3d 100644
--- a/config/services/ceph.xml
+++ b/config/services/ceph.xml
@@ -2,5 +2,5 @@
<service>
<short>ceph</short>
<description>Ceph is a distributed object store and file system. Enable this option to support Ceph's Object Storage Daemons (OSD), Metadata Server Daemons (MDS), or Manager Daemons (MGR).</description>
- <port protocol="tcp" port="6800-7300"/>
+ <port protocol="tcp" port="6800-7568"/>
</service>
--
2.43.0

View File

@ -1,96 +0,0 @@
From 84c7032bd1a744fc1c363d9836151b497064e402 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 28 Oct 2025 15:59:26 -0400
Subject: [PATCH 32/37] 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 ed030026a111..87f87ab1c49e 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.52.0

View File

@ -1,265 +0,0 @@
From 3c9a81ac67bf2a25190233e3fd20c07e7d33ba97 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 29 Oct 2025 11:32:35 -0400
Subject: [PATCH 33/37] 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 | 158 +++++++++++++++++-----------------
src/firewall/core/io/ipset.py | 26 ++++--
2 files changed, 100 insertions(+), 84 deletions(-)
diff --git a/src/firewall/core/icmp.py b/src/firewall/core/icmp.py
index 87f87ab1c49e..db0398eee6fd 100644
--- a/src/firewall/core/icmp.py
+++ b/src/firewall/core/icmp.py
@@ -20,89 +20,91 @@
#
__all__ = [ "ICMP_TYPES", "ICMPV6_TYPES",
- "check_icmp_type", "check_icmpv6_type" ]
+ "check_icmp_type_code", "check_icmpv6_type_code" ]
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 1222c588a345..edb19cc784b3 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 PY2, 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
@@ -216,24 +216,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.52.0

View File

@ -1,139 +0,0 @@
From 91b01b3d98853a38761a8c2ccd1227aec94a5972 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 29 Oct 2025 11:49:12 -0400
Subject: [PATCH 34/37] 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 834176c09cbc..ef059150917b 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -32,6 +32,7 @@ from firewall.errors import FirewallError, UNKNOWN_ERROR, INVALID_RULE, \
INVALID_PORT
from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \
Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock
+from firewall.core.icmp import ICMP_TYPES, ICMPV6_TYPES
from nftables.nftables import Nftables
TABLE_NAME = "firewalld"
@@ -90,78 +91,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
@@ -503,12 +432,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)
@@ -1574,8 +1503,12 @@ class nftables(object):
return rules
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.52.0

View File

@ -1,64 +0,0 @@
From f5f299900f2247acc81f24554c9bfe6c8ae4d1d3 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 29 Oct 2025 11:51:18 -0400
Subject: [PATCH 35/37] 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 | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index ef059150917b..ba0aa7845838 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -81,16 +81,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
@@ -1502,13 +1492,23 @@ class nftables(object):
return rules
+ 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.52.0

View File

@ -1,114 +0,0 @@
From ec822d83e3877decb1ab4db80a8303a0bbb1bdd1 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 29 Oct 2025 16:33:17 -0400
Subject: [PATCH 36/37] 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 0f9a1518380e..f20253b5f01b 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -27,9 +27,11 @@ 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_Masquerade, Rich_ForwardPort, Rich_IcmpBlock
+from firewall.core.icmp import ICMP_TYPES, ICMPV6_TYPES
import string
POLICY_CHAIN_PREFIX = ""
@@ -590,37 +592,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
@@ -1328,6 +1307,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)
@@ -1335,10 +1328,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.52.0

View File

@ -1,165 +0,0 @@
From 242e9d83840115260452b2ac48638f8f40d8ed3d Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 27 Oct 2025 16:37:35 -0400
Subject: [PATCH 37/37] 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 @@
<icmp-type name="redirect" />
<accept />
</rule>
+ <rule family="ipv6">
+ <icmp-type name="mld-listener-done" />
+ <accept />
+ </rule>
+ <rule family="ipv6">
+ <icmp-type name="mld-listener-query" />
+ <accept />
+ </rule>
+ <rule family="ipv6">
+ <icmp-type name="mld-listener-report" />
+ <accept />
+ </rule>
+ <rule family="ipv6">
+ <icmp-type name="mld2-listener-report" />
+ <accept />
+ </rule>
</policy>
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': <false>
'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': <false>
'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': <false>
'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 9811330aee07..1db150d3c430 100644
--- a/src/tests/features/policy.at
+++ b/src/tests/features/policy.at
@@ -121,6 +121,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)
@@ -140,6 +144,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
@@ -160,6 +168,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)
@@ -179,6 +191,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
@@ -199,6 +215,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)
@@ -218,6 +238,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 9f3b1615b2f9..2e579c6315b0 100644
--- a/src/tests/regression/rhbz2222044.at
+++ b/src/tests/regression/rhbz2222044.at
@@ -6,7 +6,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
-237
+241
])
NS_CHECK([nft list table ip firewalld | wc -l], 0, [dnl
105
--
2.52.0

View File

@ -1,7 +1,7 @@
Summary: A firewall daemon with D-Bus interface providing a dynamic firewall
Name: firewalld
Version: 0.9.11
Release: 11%{?dist}
Release: 4%{?dist}
URL: http://www.firewalld.org
License: GPLv2+
Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.gz
@ -21,27 +21,7 @@ Patch13: 0013-v1.2.0-fix-ipset-fix-configuring-IP-range-for-ipsets.patch
Patch14: 0014-v1.2.0-chore-nftables-add-delete-table-helper.patch
Patch15: 0015-v1.2.0-fix-nftables-always-flush-main-table-on-start.patch
Patch16: 0016-v1.2.0-test-CleanUpOnExit-verify-restart-does-not-du.patch
Patch17: 0017-v1.2.0-chore-nftables-policy-use-delete-table-helper.patch
Patch18: 0018-v1.0.0-feat-rich-support-using-ipset-in-destination.patch
Patch19: 0019-v1.0.0-test-rich-destination-ipset.patch
Patch20: 0020-v1.0.0-test-rich-destination-ipset-verify-policy-sup.patch
Patch21: 0021-v2.1.0-feat-icmp-add-ICMPv6-Multicast-Listener-Disco.patch
Patch22: 0022-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch
Patch23: 0023-v2.2.0-fix-rich-fix-range-check-for-large-rule-limit.patch
Patch24: 0024-v2.2.0-improvement-policy-extract-helper-function-fo.patch
Patch25: 0025-v2.2.0-improvement-rich-add-Rich_Limit.value_parse-a.patch
Patch26: 0026-v2.2.0-improvement-rich-support-burst-attribute-to-l.patch
Patch27: 0027-v2.0.0-test-atlocal-pass-EBTABLES-to-testsuite.patch
Patch28: 0028-v2.0.0-chore-direct-add-has_runtime_configuration.patch
Patch29: 0029-v2.0.0-feat-direct-avoid-iptables-flush-if-using-nft.patch
Patch30: 0030-v2.0.0-test-direct-avoid-iptables-flush-if-using-nft.patch
Patch31: 0031-v2.2.0-fix-service-update-highest-port-number-for-ce.patch
Patch32: 0032-v2.4.0-chore-icmp-add-all-icmptypes-to-ICMP_TYPES-di.patch
Patch33: 0033-v2.4.0-chore-icmp-convert-type-code-map-to-tuple.patch
Patch34: 0034-v2.4.0-chore-nftables-simplify-icmp-match-fragments.patch
Patch35: 0035-v2.4.0-chore-nftables-move-_icmp_types_fragments-ins.patch
Patch36: 0036-v2.4.0-chore-ipXtables-simplify-icmp-match-fragments.patch
Patch37: 0037-v2.4.0-fix-policy-allow-host-ipv6-allow-MLD-packets.patch
Patch18: 0017-v1.2.0-chore-nftables-policy-use-delete-table-helper.patch
BuildArch: noarch
BuildRequires: autoconf
@ -243,27 +223,6 @@ desktop-file-install --delete-original \
%{_mandir}/man1/firewall-config*.1*
%changelog
* Sat Mar 21 2026 Eric Garver <egarver@redhat.com> - 0.9.11-11
- fix(policy): allow-host-ipv6: allow MLD packets
* Tue Feb 04 2025 Eric Garver <egarver@redhat.com> - 0.9.11-10
- fix(service): update highest port number for ceph
* Fri Aug 02 2024 Eric Garver <egarver@redhat.com> - 0.9.11-9
- feat(direct): avoid iptables flush if using nftables backend
* Thu Jun 13 2024 Eric Garver <egarver@redhat.com> - 0.9.11-8
- feat(rich): support "burst" attribute to limit in rich rules
* Thu Jun 13 2024 Eric Garver <egarver@redhat.com> - 0.9.11-7
- fix(rich): validate service name of rich rule
* Thu Jun 13 2024 Eric Garver <egarver@redhat.com> - 0.9.11-6
- feat(icmp): add ICMPv6 Multicast Listener Discovery (MLD) types
* Thu Jun 13 2024 Eric Garver <egarver@redhat.com> - 0.9.11-5
- feat(rich): support using ipset in destination
* Fri Nov 03 2023 Eric Garver <egarver@redhat.com> - 0.9.11-4
- fix(nftables): always flush main table on start