import UBI firewalld-0.9.11-8.el8_10
This commit is contained in:
parent
23992e9a26
commit
14997c6f43
@ -0,0 +1,242 @@
|
|||||||
|
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
|
||||||
|
|
60
SOURCES/0019-v1.0.0-test-rich-destination-ipset.patch
Normal file
60
SOURCES/0019-v1.0.0-test-rich-destination-ipset.patch
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,131 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,82 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,189 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,238 @@
|
|||||||
|
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
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
Summary: A firewall daemon with D-Bus interface providing a dynamic firewall
|
Summary: A firewall daemon with D-Bus interface providing a dynamic firewall
|
||||||
Name: firewalld
|
Name: firewalld
|
||||||
Version: 0.9.11
|
Version: 0.9.11
|
||||||
Release: 4%{?dist}
|
Release: 8%{?dist}
|
||||||
URL: http://www.firewalld.org
|
URL: http://www.firewalld.org
|
||||||
License: GPLv2+
|
License: GPLv2+
|
||||||
Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.gz
|
Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.gz
|
||||||
@ -21,7 +21,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
|
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
|
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
|
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
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
BuildRequires: autoconf
|
BuildRequires: autoconf
|
||||||
@ -223,6 +232,18 @@ desktop-file-install --delete-original \
|
|||||||
%{_mandir}/man1/firewall-config*.1*
|
%{_mandir}/man1/firewall-config*.1*
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* 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
|
* Fri Nov 03 2023 Eric Garver <egarver@redhat.com> - 0.9.11-4
|
||||||
- fix(nftables): always flush main table on start
|
- fix(nftables): always flush main table on start
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user