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

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