diff --git a/0018-v1.0.0-feat-rich-support-using-ipset-in-destination.patch b/0018-v1.0.0-feat-rich-support-using-ipset-in-destination.patch new file mode 100644 index 0000000000000000000000000000000000000000..3d5e0a1d58ee2c85a8bc3565e39f85ec27719afd --- /dev/null +++ b/0018-v1.0.0-feat-rich-support-using-ipset-in-destination.patch @@ -0,0 +1,242 @@ +From 0715e07a68d50d33797a724d24157a96afee3de6 Mon Sep 17 00:00:00 2001 +From: Derek Dai +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" + Destination + + +-destination [not] address="address[/mask]" ++destination [not] address="address[/mask]"|ipset="ipset" + + 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. + +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 + diff --git a/0019-v1.0.0-test-rich-destination-ipset.patch b/0019-v1.0.0-test-rich-destination-ipset.patch new file mode 100644 index 0000000000000000000000000000000000000000..cc7e28c67d778043a6ff3f40765407bb1ae55693 --- /dev/null +++ b/0019-v1.0.0-test-rich-destination-ipset.patch @@ -0,0 +1,60 @@ +From cf8a55d1fe769a9e4632fbccf5ae4738ab661421 Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0020-v1.0.0-test-rich-destination-ipset-verify-policy-sup.patch b/0020-v1.0.0-test-rich-destination-ipset-verify-policy-sup.patch new file mode 100644 index 0000000000000000000000000000000000000000..547239327ccea38a7b6adfdf810479214400a179 --- /dev/null +++ b/0020-v1.0.0-test-rich-destination-ipset-verify-policy-sup.patch @@ -0,0 +1,63 @@ +From 63100ca625942e6be2c68422e7a48bc68f8d01c5 Mon Sep 17 00:00:00 2001 +From: Eric Garver +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 + diff --git a/0021-v2.1.0-feat-icmp-add-ICMPv6-Multicast-Listener-Disco.patch b/0021-v2.1.0-feat-icmp-add-ICMPv6-Multicast-Listener-Disco.patch new file mode 100644 index 0000000000000000000000000000000000000000..4c829b15abce98566b83dfd3fa938443ae7c99d6 --- /dev/null +++ b/0021-v2.1.0-feat-icmp-add-ICMPv6-Multicast-Listener-Disco.patch @@ -0,0 +1,131 @@ +From b18ab581731a302ddba0428b685360d315293e73 Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 @@ ++ ++ ++ MLD Listener Done ++ 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. ++ ++ ++ +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 @@ ++ ++ ++ MLD Listener Query ++ ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Query (type 130) (RFC 4890 section 4.4.1). ++ ++ ++ +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 @@ ++ ++ ++ MLD Listener Report ++ ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Report (type 131) (RFC 4890 section 4.4.1). ++ ++ ++ +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 @@ ++ ++ ++ MLDv2 Multicast Listener Report ++ ICMPv6 Link-Local Multicast Listener Discovery (MDLv2) of type Multicast Listener Report (type 143) (RFC 4890 section 4.4.1). ++ ++ ++ +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 + diff --git a/0022-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch b/0022-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch new file mode 100644 index 0000000000000000000000000000000000000000..4b17038af90f17e9479f93abea933149ee5b056a --- /dev/null +++ b/0022-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch @@ -0,0 +1,82 @@ +From 5266735bf4827178ddd9a12edc37b1b0a93e0d3a Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 + diff --git a/0023-v2.2.0-fix-rich-fix-range-check-for-large-rule-limit.patch b/0023-v2.2.0-fix-rich-fix-range-check-for-large-rule-limit.patch new file mode 100644 index 0000000000000000000000000000000000000000..523638dad0e91af33a5f8cc938df19c6664690da --- /dev/null +++ b/0023-v2.2.0-fix-rich-fix-range-check-for-large-rule-limit.patch @@ -0,0 +1,27 @@ +From 39e8946ba75fc3ce36c3ff72e3af1fb2ae0d95ec Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 + diff --git a/0024-v2.2.0-improvement-policy-extract-helper-function-fo.patch b/0024-v2.2.0-improvement-policy-extract-helper-function-fo.patch new file mode 100644 index 0000000000000000000000000000000000000000..aed7dc78c19638fda37e3de81b2092f1e63542da --- /dev/null +++ b/0024-v2.2.0-improvement-policy-extract-helper-function-fo.patch @@ -0,0 +1,63 @@ +From 028529e33ed45507bcb1f3eb2722de3344eea091 Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 + diff --git a/0025-v2.2.0-improvement-rich-add-Rich_Limit.value_parse-a.patch b/0025-v2.2.0-improvement-rich-add-Rich_Limit.value_parse-a.patch new file mode 100644 index 0000000000000000000000000000000000000000..94ffdc60bed25240ea1d79836d4d9253a4988c63 --- /dev/null +++ b/0025-v2.2.0-improvement-rich-add-Rich_Limit.value_parse-a.patch @@ -0,0 +1,189 @@ +From 2844fedea7b0c65d864f9960b513150c4468adb2 Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 + diff --git a/0026-v2.2.0-improvement-rich-support-burst-attribute-to-l.patch b/0026-v2.2.0-improvement-rich-support-burst-attribute-to-l.patch new file mode 100644 index 0000000000000000000000000000000000000000..cfe7fa4657d9baf57f71e413867c5ef4e4ae9378 --- /dev/null +++ b/0026-v2.2.0-improvement-rich-support-burst-attribute-to-l.patch @@ -0,0 +1,238 @@ +From 45ebffc5521db62064f365f4a9100b4ab40dce90 Mon Sep 17 00:00:00 2001 +From: Thomas Haller +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 + diff --git a/dist b/dist index 9c0e36ec42a2d9bfefacb21ac6354c9ddd910533..1fe92cf0fdf9c2625d878a2ace258f64c1e8ca44 100644 --- a/dist +++ b/dist @@ -1 +1 @@ -an8 +an8_10 diff --git a/firewalld.spec b/firewalld.spec index b24a86575bb513127b6f08a5d5143edefbe0c133..567aa1d38bfbcd1ea41ac40d4e3385aae204d12c 100644 --- a/firewalld.spec +++ b/firewalld.spec @@ -2,7 +2,7 @@ Summary: A firewall daemon with D-Bus interface providing a dynamic firewall Name: firewalld Version: 0.9.11 -Release: 4%{anolis_release}%{?dist} +Release: 8%{anolis_release}%{?dist} URL: http://www.firewalld.org License: GPLv2+ Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.gz @@ -22,7 +22,16 @@ 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 -Patch18: 0017-v1.2.0-chore-nftables-policy-use-delete-table-helper.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 # Add by Anolis Patch1000: 0001-firewalld-anolis-rebranding.patch @@ -228,9 +237,21 @@ desktop-file-install --delete-original \ %{_mandir}/man1/firewall-config*.1* %changelog -* Mon Jul 22 2024 Liwei Ge - 0.9.11-4.0.1 +* Fri Aug 16 2024 Liwei Ge - 0.9.11-8.0.1 - Remove Satellite service +* Thu Jun 13 2024 Eric Garver - 0.9.11-8 +- feat(rich): support "burst" attribute to limit in rich rules + +* Thu Jun 13 2024 Eric Garver - 0.9.11-7 +- fix(rich): validate service name of rich rule + +* Thu Jun 13 2024 Eric Garver - 0.9.11-6 +- feat(icmp): add ICMPv6 Multicast Listener Discovery (MLD) types + +* Thu Jun 13 2024 Eric Garver - 0.9.11-5 +- feat(rich): support using ipset in destination + * Fri Nov 03 2023 Eric Garver - 0.9.11-4 - fix(nftables): always flush main table on start