Blame SOURCES/v1.0.0-0049-fix-ipset-reduce-cost-of-entry-overlap-detection.patch

60263b
From 34967402eda57d051b239c1551ecc0259881e7d4 Mon Sep 17 00:00:00 2001
60263b
From: Eric Garver <eric@garver.life>
60263b
Date: Tue, 30 Nov 2021 14:54:20 -0500
60263b
Subject: [PATCH 49/51] fix(ipset): reduce cost of entry overlap detection
60263b
60263b
This increases peak memory usage to reduce the duration it takes to
60263b
apply the set entries. Building the list of IPv4Network objects up front
60263b
means we don't have to build them multiple times inside the for loop.
60263b
60263b
Fixes: #881
60263b
(cherry picked from commit 7f5b736378c0133f46470c42e0c1fb3b95087de5)
60263b
---
60263b
 src/firewall/client.py              | 10 ++++------
60263b
 src/firewall/core/fw_ipset.py       |  9 +++------
60263b
 src/firewall/core/ipset.py          | 27 ++++++++++++++++++++++-----
60263b
 src/firewall/server/config_ipset.py | 10 ++++------
60263b
 4 files changed, 33 insertions(+), 23 deletions(-)
60263b
60263b
diff --git a/src/firewall/client.py b/src/firewall/client.py
60263b
index 3715ffd29316..fdc88ac7946b 100644
60263b
--- a/src/firewall/client.py
60263b
+++ b/src/firewall/client.py
60263b
@@ -34,7 +34,8 @@ from firewall.core.base import DEFAULT_ZONE_TARGET, DEFAULT_POLICY_TARGET, DEFAU
60263b
 from firewall.dbus_utils import dbus_to_python
60263b
 from firewall.functions import b2u
60263b
 from firewall.core.rich import Rich_Rule
60263b
-from firewall.core.ipset import normalize_ipset_entry, check_entry_overlaps_existing
60263b
+from firewall.core.ipset import normalize_ipset_entry, check_entry_overlaps_existing, \
60263b
+                                check_for_overlapping_entries
60263b
 from firewall import errors
60263b
 from firewall.errors import FirewallError
60263b
 
60263b
@@ -1617,11 +1618,8 @@ class FirewallClientIPSetSettings(object):
60263b
         if "timeout" in self.settings[4] and \
60263b
            self.settings[4]["timeout"] != "0":
60263b
             raise FirewallError(errors.IPSET_WITH_TIMEOUT)
60263b
-        _entries = set()
60263b
-        for _entry in dbus_to_python(entries, list):
60263b
-            check_entry_overlaps_existing(_entry, _entries)
60263b
-            _entries.add(normalize_ipset_entry(_entry))
60263b
-        self.settings[5] = list(_entries)
60263b
+        check_for_overlapping_entries(entries)
60263b
+        self.settings[5] = entries
60263b
     @handle_exceptions
60263b
     def addEntry(self, entry):
60263b
         if "timeout" in self.settings[4] and \
60263b
diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py
60263b
index a285fd4a4aab..d7878c01921e 100644
60263b
--- a/src/firewall/core/fw_ipset.py
60263b
+++ b/src/firewall/core/fw_ipset.py
60263b
@@ -25,7 +25,8 @@ __all__ = [ "FirewallIPSet" ]
60263b
 
60263b
 from firewall.core.logger import log
60263b
 from firewall.core.ipset import remove_default_create_options as rm_def_cr_opts, \
60263b
-                                normalize_ipset_entry, check_entry_overlaps_existing
60263b
+                                normalize_ipset_entry, check_entry_overlaps_existing, \
60263b
+                                check_for_overlapping_entries
60263b
 from firewall.core.io.ipset import IPSet
60263b
 from firewall import errors
60263b
 from firewall.errors import FirewallError
60263b
@@ -244,11 +245,7 @@ class FirewallIPSet(object):
60263b
     def set_entries(self, name, entries):
60263b
         obj = self.get_ipset(name, applied=True)
60263b
 
60263b
-        _entries = set()
60263b
-        for _entry in entries:
60263b
-            check_entry_overlaps_existing(_entry, _entries)
60263b
-            _entries.add(normalize_ipset_entry(_entry))
60263b
-        entries = list(_entries)
60263b
+        check_for_overlapping_entries(entries)
60263b
 
60263b
         for entry in entries:
60263b
             IPSet.check_entry(entry, obj.options, obj.type)
60263b
diff --git a/src/firewall/core/ipset.py b/src/firewall/core/ipset.py
60263b
index d6defa395241..66ea4335536d 100644
60263b
--- a/src/firewall/core/ipset.py
60263b
+++ b/src/firewall/core/ipset.py
60263b
@@ -309,9 +309,26 @@ def check_entry_overlaps_existing(entry, entries):
60263b
     if len(entry.split(",")) > 1:
60263b
         return
60263b
 
60263b
+    try:
60263b
+        entry_network = ipaddress.ip_network(entry, strict=False)
60263b
+    except ValueError:
60263b
+        # could not parse the new IP address, maybe a MAC
60263b
+        return
60263b
+
60263b
     for itr in entries:
60263b
-        try:
60263b
-            if ipaddress.ip_network(itr, strict=False).overlaps(ipaddress.ip_network(entry, strict=False)):
60263b
-                raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps with existing entry '{}'".format(itr, entry))
60263b
-        except ValueError:
60263b
-            pass
60263b
+        if entry_network.overlaps(ipaddress.ip_network(itr, strict=False)):
60263b
+            raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps with existing entry '{}'".format(entry, itr))
60263b
+
60263b
+def check_for_overlapping_entries(entries):
60263b
+    """ Check if any entry overlaps any entry in the list of entries """
60263b
+    try:
60263b
+        entries = [ipaddress.ip_network(x, strict=False) for x in entries]
60263b
+    except ValueError:
60263b
+        # at least one entry can not be parsed
60263b
+        return
60263b
+
60263b
+    while entries:
60263b
+        entry = entries.pop()
60263b
+        for itr in entries:
60263b
+            if entry.overlaps(itr):
60263b
+                raise FirewallError(errors.INVALID_ENTRY, "Entry '{}' overlaps entry '{}'".format(entry, itr))
60263b
diff --git a/src/firewall/server/config_ipset.py b/src/firewall/server/config_ipset.py
60263b
index f33c2a02926f..499ffcb9227a 100644
60263b
--- a/src/firewall/server/config_ipset.py
60263b
+++ b/src/firewall/server/config_ipset.py
60263b
@@ -34,7 +34,8 @@ from firewall.dbus_utils import dbus_to_python, \
60263b
     dbus_introspection_add_properties
60263b
 from firewall.core.io.ipset import IPSet
60263b
 from firewall.core.ipset import IPSET_TYPES, normalize_ipset_entry, \
60263b
-                                check_entry_overlaps_existing
60263b
+                                check_entry_overlaps_existing, \
60263b
+                                check_for_overlapping_entries
60263b
 from firewall.core.logger import log
60263b
 from firewall.server.decorators import handle_exceptions, \
60263b
     dbus_handle_exceptions, dbus_service_method
60263b
@@ -407,11 +408,8 @@ class FirewallDConfigIPSet(slip.dbus.service.Object):
60263b
                          in_signature='as')
60263b
     @dbus_handle_exceptions
60263b
     def setEntries(self, entries, sender=None):
60263b
-        _entries = set()
60263b
-        for _entry in dbus_to_python(entries, list):
60263b
-            check_entry_overlaps_existing(_entry, _entries)
60263b
-            _entries.add(normalize_ipset_entry(_entry))
60263b
-        entries = list(_entries)
60263b
+        entries = dbus_to_python(entries, list)
60263b
+        check_for_overlapping_entries(entries)
60263b
         log.debug1("%s.setEntries('[%s]')", self._log_prefix,
60263b
                    ",".join(entries))
60263b
         self.parent.accessCheck(sender)
60263b
-- 
60263b
2.31.1
60263b