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

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