391 lines
18 KiB
Diff
391 lines
18 KiB
Diff
From a5291bcee84b56b30aac38544d85fb601fe6a25a Mon Sep 17 00:00:00 2001
|
|
From: Eric Garver <eric@garver.life>
|
|
Date: Tue, 17 Mar 2020 13:51:43 -0400
|
|
Subject: [PATCH] improvement: port: allow coalescing and breaking of ranges
|
|
|
|
---
|
|
src/firewall/core/fw_zone.py | 106 +++++++++++++++++++++++++++----------
|
|
src/firewall/functions.py | 85 +++++++++++++++++++++++++++++
|
|
src/firewall/server/config_zone.py | 55 ++++++++++++++-----
|
|
3 files changed, 204 insertions(+), 42 deletions(-)
|
|
|
|
diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py
|
|
index 2bc94e3..d32d7a8 100644
|
|
--- a/src/firewall/core/fw_zone.py
|
|
+++ b/src/firewall/core/fw_zone.py
|
|
@@ -25,7 +25,7 @@ from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, \
|
|
from firewall.core.logger import log
|
|
from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \
|
|
checkProtocol, enable_ip_forwarding, check_single_address, check_mac, \
|
|
- portInPortRange, get_nf_conntrack_short_name
|
|
+ portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange
|
|
from firewall.core.rich import Rich_Rule, Rich_Accept, \
|
|
Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \
|
|
Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \
|
|
@@ -857,11 +857,13 @@ class FirewallZone(object):
|
|
self._fw.check_panic()
|
|
_obj = self._zones[_zone]
|
|
|
|
- port_id = self.__port_id(port, protocol)
|
|
- if port_id in _obj.settings["ports"]:
|
|
- raise FirewallError(errors.ALREADY_ENABLED,
|
|
- "'%s:%s' already in '%s'" % (port, protocol,
|
|
- _zone))
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ raise FirewallError(errors.ALREADY_ENABLED,
|
|
+ "'%s:%s' already in '%s'" % (port, protocol, _zone))
|
|
+
|
|
+ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
|
|
if use_transaction is None:
|
|
transaction = self.new_transaction()
|
|
@@ -869,10 +871,18 @@ class FirewallZone(object):
|
|
transaction = use_transaction
|
|
|
|
if _obj.applied:
|
|
- self._port(True, _zone, port, protocol, transaction)
|
|
-
|
|
- self.__register_port(_obj, port_id, timeout, sender)
|
|
- transaction.add_fail(self.__unregister_port, _obj, port_id)
|
|
+ for range in added_ranges:
|
|
+ self._port(True, _zone, portStr(range, "-"), protocol, transaction)
|
|
+ for range in removed_ranges:
|
|
+ self._port(False, _zone, portStr(range, "-"), protocol, transaction)
|
|
+
|
|
+ for range in added_ranges:
|
|
+ port_id = self.__port_id(range, protocol)
|
|
+ self.__register_port(_obj, port_id, timeout, sender)
|
|
+ transaction.add_fail(self.__unregister_port, _obj, port_id)
|
|
+ for range in removed_ranges:
|
|
+ port_id = self.__port_id(range, protocol)
|
|
+ transaction.add_post(self.__unregister_port, _obj, port_id)
|
|
|
|
if use_transaction is None:
|
|
transaction.execute(True)
|
|
@@ -889,20 +899,34 @@ class FirewallZone(object):
|
|
self._fw.check_panic()
|
|
_obj = self._zones[_zone]
|
|
|
|
- port_id = self.__port_id(port, protocol)
|
|
- if port_id not in _obj.settings["ports"]:
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ break
|
|
+ else:
|
|
raise FirewallError(errors.NOT_ENABLED,
|
|
"'%s:%s' not in '%s'" % (port, protocol, _zone))
|
|
|
|
+ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+
|
|
if use_transaction is None:
|
|
transaction = self.new_transaction()
|
|
else:
|
|
transaction = use_transaction
|
|
|
|
if _obj.applied:
|
|
- self._port(False, _zone, port, protocol, transaction)
|
|
-
|
|
- transaction.add_post(self.__unregister_port, _obj, port_id)
|
|
+ for range in added_ranges:
|
|
+ self._port(True, _zone, portStr(range, "-"), protocol, transaction)
|
|
+ for range in removed_ranges:
|
|
+ self._port(False, _zone, portStr(range, "-"), protocol, transaction)
|
|
+
|
|
+ for range in added_ranges:
|
|
+ port_id = self.__port_id(range, protocol)
|
|
+ self.__register_port(_obj, port_id, 0, None)
|
|
+ transaction.add_fail(self.__unregister_port, _obj, port_id)
|
|
+ for range in removed_ranges:
|
|
+ port_id = self.__port_id(range, protocol)
|
|
+ transaction.add_post(self.__unregister_port, _obj, port_id)
|
|
|
|
if use_transaction is None:
|
|
transaction.execute(True)
|
|
@@ -1015,11 +1039,13 @@ class FirewallZone(object):
|
|
self._fw.check_panic()
|
|
_obj = self._zones[_zone]
|
|
|
|
- port_id = self.__source_port_id(port, protocol)
|
|
- if port_id in _obj.settings["source_ports"]:
|
|
- raise FirewallError(errors.ALREADY_ENABLED,
|
|
- "'%s:%s' already in '%s'" % (port, protocol,
|
|
- _zone))
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ raise FirewallError(errors.ALREADY_ENABLED,
|
|
+ "'%s:%s' already in '%s'" % (port, protocol, _zone))
|
|
+
|
|
+ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
|
|
if use_transaction is None:
|
|
transaction = self.new_transaction()
|
|
@@ -1027,10 +1053,18 @@ class FirewallZone(object):
|
|
transaction = use_transaction
|
|
|
|
if _obj.applied:
|
|
- self._source_port(True, _zone, port, protocol, transaction)
|
|
-
|
|
- self.__register_source_port(_obj, port_id, timeout, sender)
|
|
- transaction.add_fail(self.__unregister_source_port, _obj, port_id)
|
|
+ for range in added_ranges:
|
|
+ self._source_port(True, _zone, portStr(range, "-"), protocol, transaction)
|
|
+ for range in removed_ranges:
|
|
+ self._source_port(False, _zone, portStr(range, "-"), protocol, transaction)
|
|
+
|
|
+ for range in added_ranges:
|
|
+ port_id = self.__source_port_id(range, protocol)
|
|
+ self.__register_source_port(_obj, port_id, timeout, sender)
|
|
+ transaction.add_fail(self.__unregister_source_port, _obj, port_id)
|
|
+ for range in removed_ranges:
|
|
+ port_id = self.__source_port_id(range, protocol)
|
|
+ transaction.add_post(self.__unregister_source_port, _obj, port_id)
|
|
|
|
if use_transaction is None:
|
|
transaction.execute(True)
|
|
@@ -1047,20 +1081,34 @@ class FirewallZone(object):
|
|
self._fw.check_panic()
|
|
_obj = self._zones[_zone]
|
|
|
|
- port_id = self.__source_port_id(port, protocol)
|
|
- if port_id not in _obj.settings["source_ports"]:
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ break
|
|
+ else:
|
|
raise FirewallError(errors.NOT_ENABLED,
|
|
"'%s:%s' not in '%s'" % (port, protocol, _zone))
|
|
|
|
+ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+
|
|
if use_transaction is None:
|
|
transaction = self.new_transaction()
|
|
else:
|
|
transaction = use_transaction
|
|
|
|
if _obj.applied:
|
|
- self._source_port(False, _zone, port, protocol, transaction)
|
|
-
|
|
- transaction.add_post(self.__unregister_source_port, _obj, port_id)
|
|
+ for range in added_ranges:
|
|
+ self._source_port(True, _zone, portStr(range, "-"), protocol, transaction)
|
|
+ for range in removed_ranges:
|
|
+ self._source_port(False, _zone, portStr(range, "-"), protocol, transaction)
|
|
+
|
|
+ for range in added_ranges:
|
|
+ port_id = self.__source_port_id(range, protocol)
|
|
+ self.__register_source_port(_obj, port_id, 0, None)
|
|
+ transaction.add_fail(self.__unregister_source_port, _obj, port_id)
|
|
+ for range in removed_ranges:
|
|
+ port_id = self.__source_port_id(range, protocol)
|
|
+ transaction.add_post(self.__unregister_source_port, _obj, port_id)
|
|
|
|
if use_transaction is None:
|
|
transaction.execute(True)
|
|
diff --git a/src/firewall/functions.py b/src/firewall/functions.py
|
|
index 6af2206..6bc52d9 100644
|
|
--- a/src/firewall/functions.py
|
|
+++ b/src/firewall/functions.py
|
|
@@ -72,6 +72,10 @@ def getPortRange(ports):
|
|
@return Array containing start and end port id for a valid range or -1 if port can not be found and -2 if port is too big for integer input or -1 for invalid ranges or None if the range is ambiguous.
|
|
"""
|
|
|
|
+ # (port, port) or [port, port] case
|
|
+ if isinstance(ports, tuple) or isinstance(ports, list):
|
|
+ return ports
|
|
+
|
|
# "<port-id>" case
|
|
if isinstance(ports, int) or ports.isdigit():
|
|
id1 = getPortID(ports)
|
|
@@ -155,6 +159,87 @@ def portInPortRange(port, range):
|
|
|
|
return False
|
|
|
|
+def coalescePortRange(new_range, ranges):
|
|
+ """ Coalesce a port range with existing list of port ranges
|
|
+
|
|
+ @param new_range tuple/list/string
|
|
+ @param ranges list of tuple/list/string
|
|
+ @return tuple of (list of ranges added after coalescing, list of removed original ranges)
|
|
+ """
|
|
+
|
|
+ coalesced_range = getPortRange(new_range)
|
|
+ # normalize singleton ranges, e.g. (x,) --> (x,x)
|
|
+ if len(coalesced_range) == 1:
|
|
+ coalesced_range = (coalesced_range[0], coalesced_range[0])
|
|
+ _ranges = map(getPortRange, ranges)
|
|
+ _ranges = sorted(map(lambda x: (x[0],x[0]) if len(x) == 1 else x, _ranges), key=lambda x: x[0])
|
|
+
|
|
+ removed_ranges = []
|
|
+ for range in _ranges:
|
|
+ if coalesced_range[0] <= range[0] and coalesced_range[1] >= range[1]:
|
|
+ # new range covers this
|
|
+ removed_ranges.append(range)
|
|
+ elif coalesced_range[0] <= range[0] and coalesced_range[1] < range[1] and \
|
|
+ coalesced_range[1] >= range[0]:
|
|
+ # expand beginning of range
|
|
+ removed_ranges.append(range)
|
|
+ coalesced_range = (coalesced_range[0], range[1])
|
|
+ elif coalesced_range[0] > range[0] and coalesced_range[1] >= range[1] and \
|
|
+ coalesced_range[0] <= range[1]:
|
|
+ # expand end of range
|
|
+ removed_ranges.append(range)
|
|
+ coalesced_range = (range[0], coalesced_range[1])
|
|
+
|
|
+ # normalize singleton ranges, e.g. (x,x) --> (x,)
|
|
+ removed_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, removed_ranges))
|
|
+ if coalesced_range[0] == coalesced_range[1]:
|
|
+ coalesced_range = (coalesced_range[0],)
|
|
+
|
|
+ return ([coalesced_range], removed_ranges)
|
|
+
|
|
+def breakPortRange(remove_range, ranges):
|
|
+ """ break a port range from existing list of port ranges
|
|
+
|
|
+ @param remove_range tuple/list/string
|
|
+ @param ranges list of tuple/list/string
|
|
+ @return tuple of (list of ranges added after breaking up, list of removed original ranges)
|
|
+ """
|
|
+
|
|
+ remove_range = getPortRange(remove_range)
|
|
+ # normalize singleton ranges, e.g. (x,) --> (x,x)
|
|
+ if len(remove_range) == 1:
|
|
+ remove_range = (remove_range[0], remove_range[0])
|
|
+ _ranges = map(getPortRange, ranges)
|
|
+ _ranges = sorted(map(lambda x: (x[0],x[0]) if len(x) == 1 else x, _ranges), key=lambda x: x[0])
|
|
+
|
|
+ removed_ranges = []
|
|
+ added_ranges = []
|
|
+ for range in _ranges:
|
|
+ if remove_range[0] <= range[0] and remove_range[1] >= range[1]:
|
|
+ # remove entire range
|
|
+ removed_ranges.append(range)
|
|
+ elif remove_range[0] <= range[0] and remove_range[1] < range[1] and \
|
|
+ remove_range[1] >= range[0]:
|
|
+ # remove from beginning of range
|
|
+ removed_ranges.append(range)
|
|
+ added_ranges.append((remove_range[1] + 1, range[1]))
|
|
+ elif remove_range[0] > range[0] and remove_range[1] >= range[1] and \
|
|
+ remove_range[0] <= range[1]:
|
|
+ # remove from end of range
|
|
+ removed_ranges.append(range)
|
|
+ added_ranges.append((range[0], remove_range[0] - 1))
|
|
+ elif remove_range[0] > range[0] and remove_range[1] < range[1]:
|
|
+ # remove inside range
|
|
+ removed_ranges.append(range)
|
|
+ added_ranges.append((range[0], remove_range[0] - 1))
|
|
+ added_ranges.append((remove_range[1] + 1, range[1]))
|
|
+
|
|
+ # normalize singleton ranges, e.g. (x,x) --> (x,)
|
|
+ removed_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, removed_ranges))
|
|
+ added_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, added_ranges))
|
|
+
|
|
+ return (added_ranges, removed_ranges)
|
|
+
|
|
def getServiceName(port, proto):
|
|
""" Check and Get service name from port and proto string combination using socket.getservbyport
|
|
|
|
diff --git a/src/firewall/server/config_zone.py b/src/firewall/server/config_zone.py
|
|
index 1ae20ce..1c05318 100644
|
|
--- a/src/firewall/server/config_zone.py
|
|
+++ b/src/firewall/server/config_zone.py
|
|
@@ -41,7 +41,8 @@ from firewall.server.decorators import handle_exceptions, \
|
|
dbus_handle_exceptions, dbus_service_method
|
|
from firewall import errors
|
|
from firewall.errors import FirewallError
|
|
-from firewall.functions import portInPortRange
|
|
+from firewall.functions import portStr, portInPortRange, coalescePortRange, \
|
|
+ breakPortRange
|
|
|
|
############################################################################
|
|
#
|
|
@@ -455,10 +456,16 @@ class FirewallDConfigZone(slip.dbus.service.Object):
|
|
protocol)
|
|
self.parent.accessCheck(sender)
|
|
settings = list(self.getSettings())
|
|
- if (port,protocol) in settings[6]:
|
|
- raise FirewallError(errors.ALREADY_ENABLED,
|
|
- "%s:%s" % (port, protocol))
|
|
- settings[6].append((port,protocol))
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[6]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ raise FirewallError(errors.ALREADY_ENABLED,
|
|
+ "%s:%s" % (port, protocol))
|
|
+ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+ for range in removed_ranges:
|
|
+ settings[6].remove((portStr(range, "-"), protocol))
|
|
+ for range in added_ranges:
|
|
+ settings[6].append((portStr(range, "-"), protocol))
|
|
self.update(settings)
|
|
|
|
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE,
|
|
@@ -471,9 +478,17 @@ class FirewallDConfigZone(slip.dbus.service.Object):
|
|
protocol)
|
|
self.parent.accessCheck(sender)
|
|
settings = list(self.getSettings())
|
|
- if (port,protocol) not in settings[6]:
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[6]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ break
|
|
+ else:
|
|
raise FirewallError(errors.NOT_ENABLED, "%s:%s" % (port, protocol))
|
|
- settings[6].remove((port,protocol))
|
|
+ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+ for range in removed_ranges:
|
|
+ settings[6].remove((portStr(range, "-"), protocol))
|
|
+ for range in added_ranges:
|
|
+ settings[6].append((portStr(range, "-"), protocol))
|
|
self.update(settings)
|
|
|
|
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE,
|
|
@@ -583,10 +598,16 @@ class FirewallDConfigZone(slip.dbus.service.Object):
|
|
protocol)
|
|
self.parent.accessCheck(sender)
|
|
settings = list(self.getSettings())
|
|
- if (port,protocol) in settings[14]:
|
|
- raise FirewallError(errors.ALREADY_ENABLED,
|
|
- "%s:%s" % (port, protocol))
|
|
- settings[14].append((port,protocol))
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[14]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ raise FirewallError(errors.ALREADY_ENABLED,
|
|
+ "%s:%s" % (port, protocol))
|
|
+ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+ for range in removed_ranges:
|
|
+ settings[14].remove((portStr(range, "-"), protocol))
|
|
+ for range in added_ranges:
|
|
+ settings[14].append((portStr(range, "-"), protocol))
|
|
self.update(settings)
|
|
|
|
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE,
|
|
@@ -599,9 +620,17 @@ class FirewallDConfigZone(slip.dbus.service.Object):
|
|
protocol)
|
|
self.parent.accessCheck(sender)
|
|
settings = list(self.getSettings())
|
|
- if (port,protocol) not in settings[14]:
|
|
+ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[14]))
|
|
+ for port_id in existing_port_ids:
|
|
+ if portInPortRange(port, port_id[0]):
|
|
+ break
|
|
+ else:
|
|
raise FirewallError(errors.NOT_ENABLED, "%s:%s" % (port, protocol))
|
|
- settings[14].remove((port,protocol))
|
|
+ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids])
|
|
+ for range in removed_ranges:
|
|
+ settings[14].remove((portStr(range, "-"), protocol))
|
|
+ for range in added_ranges:
|
|
+ settings[14].append((portStr(range, "-"), protocol))
|
|
self.update(settings)
|
|
|
|
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE,
|
|
--
|
|
1.8.3.1
|
|
|