Re: [OpenWrt-Devel] [RFC] firewall3: zones: Use ipsets instead of interfaces in zone rules

2019-10-31 Thread Kristian Evensen
Hi all,

On Wed, Aug 28, 2019 at 7:37 PM Kristian Evensen
 wrote:
>
> firewall3 currently creates one rule for each interface that is a member of a
> zone. On for example devices with multiple interfaces, the current firewall3
> behavior quickly leads to a lot of rules. In order to reduce the number of
> rules, this patch replaces the per-interface rules with ipset matches (if 
> ipset
> is available). Since 2011, ipset has supported the set type "hash:net,iface".
> By adding (and matching on) on pairs consiting of the v4/v6 any-address and an
> interface name, we get the same behavior as the current interface-rules.
>
> After applying this patch (and assuming ipset is available), the following
> actions are performed when a zone is created:
>
> * We creates (allocate) an ipset of type "hash:net,iface" for each zone. The
> name follows the following format: zone__<4/6>_set.
> * If creating a set fails, then we ignore the zone. This is something we can
> change, but my reason for this behavior is to have consistent firewall rules.
> I.e., zone-rules either match on ipset or interface names, and not a mix.
> * Each set is populated with pairs consisting of the IPv4/IPv6 any-address and
> an interface name, for example "0.0.0.0/0, eth0.2".
> * Instead of one rule per device, a single rule is created matching on the
> ipset.
> * The check used to select the OUTPUT/PREROUTING-chain when adding rules to 
> the
> raw-table has been moved to print_interface_rules_{default,set}. The 
> motivation
> behind this move was to avoid changing print_interface_rule() too much. As far
> as I can see (and have tested), the logic for selecting chain/creating the
> rules is the same as before.
>
> Because the change introduced by this patch is quite intrusive and I am sure
> there will be comments/disagreements/suggestions, I have sent this patch as an
> RFC. One thing that I am aware of and will fix before the final submission, is
> to add support for printing ipsets. Right now "fw3 print" prints per-interface
> rules.

I have had the chance to run this patch in production for a while, and
thought I should share my experiences. All in all, switching to ipsets
seems to work well and, with one exception, I have not found any
configurations or configuration steps that break. Also, in some of my
setups, the number of iptables rules are greatly reduced. While I
haven't measured any performance improvements, fewer rules makes the
rule set easier to work with. The need to reload the firewall on ifup
is also removed (it is sufficient to update the set), which removes an
annoying gap or interruption in traffic on some of the devices I am
running.

What (currently) breaks is wildcard interface names, ipset currently
has no wildcard-support. I have submitted a patch upstream adding
support for wildcard naming, and have received positive feedback but
no final decision.

BR,
Kristian

___
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel


[OpenWrt-Devel] [RFC] firewall3: zones: Use ipsets instead of interfaces in zone rules

2019-08-28 Thread Kristian Evensen
firewall3 currently creates one rule for each interface that is a member of a
zone. On for example devices with multiple interfaces, the current firewall3
behavior quickly leads to a lot of rules. In order to reduce the number of
rules, this patch replaces the per-interface rules with ipset matches (if ipset
is available). Since 2011, ipset has supported the set type "hash:net,iface".
By adding (and matching on) on pairs consiting of the v4/v6 any-address and an
interface name, we get the same behavior as the current interface-rules.

After applying this patch (and assuming ipset is available), the following
actions are performed when a zone is created:

* We creates (allocate) an ipset of type "hash:net,iface" for each zone. The
name follows the following format: zone__<4/6>_set.
* If creating a set fails, then we ignore the zone. This is something we can
change, but my reason for this behavior is to have consistent firewall rules.
I.e., zone-rules either match on ipset or interface names, and not a mix.
* Each set is populated with pairs consisting of the IPv4/IPv6 any-address and
an interface name, for example "0.0.0.0/0, eth0.2".
* Instead of one rule per device, a single rule is created matching on the
ipset.
* The check used to select the OUTPUT/PREROUTING-chain when adding rules to the
raw-table has been moved to print_interface_rules_{default,set}. The motivation
behind this move was to avoid changing print_interface_rule() too much. As far
as I can see (and have tested), the logic for selecting chain/creating the
rules is the same as before.

Because the change introduced by this patch is quite intrusive and I am sure
there will be comments/disagreements/suggestions, I have sent this patch as an
RFC. One thing that I am aware of and will fix before the final submission, is
to add support for printing ipsets. Right now "fw3 print" prints per-interface
rules.

Signed-off-by: Kristian Evensen 
---
 ipsets.c  |  38 ++-
 ipsets.h  |   7 ++
 main.c|   8 +-
 options.c |   3 +-
 options.h |   4 +
 zones.c   | 291 +++---
 zones.h   |  15 ++-
 7 files changed, 347 insertions(+), 19 deletions(-)

diff --git a/ipsets.c b/ipsets.c
index 280845b..e052278 100644
--- a/ipsets.c
+++ b/ipsets.c
@@ -81,6 +81,8 @@ static struct ipset_type ipset_types[] = {
  OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH,   NET,  PORT,   UNSPEC, 0,
  OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
+   T(HASH,   NET,  IFACE,   UNSPEC, 0,
+ OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH,   IP,   PORT,   IP, 0,
  OPT_FAMILY | OPT_HASHSIZE | OPT_MAXELEM),
T(HASH,   IP,   PORT,   NET,0,
@@ -247,7 +249,7 @@ check_ipset(struct fw3_state *state, struct fw3_ipset 
*ipset, struct uci_element
return false;
 }
 
-static struct fw3_ipset *
+struct fw3_ipset *
 fw3_alloc_ipset(struct fw3_state *state)
 {
struct fw3_ipset *ipset;
@@ -611,3 +613,37 @@ fw3_ipsets_update_run_state(enum fw3_family family, struct 
fw3_state *run_state,
ipset_run->reload_set = ipset_cfg->reload_set;
}
 }
+
+void
+fw3_ipset_add_devices(struct list_head *devices, enum fw3_family family,
+ const char *set_name)
+{
+   struct fw3_device *dev;
+   bool exec = false;
+   const char *addr;
+
+   fw3_foreach(dev, devices) {
+   if (!dev)
+   continue;
+
+   if (!exec) {
+   exec = fw3_command_pipe(false, "ipset", "-exist", "-");
+
+   if (!exec)
+   return;
+   }
+
+   if (family == FW3_FAMILY_V4) {
+   addr = "0.0.0.0/0";
+   } else {
+   addr = "::/0";
+   }
+
+   fw3_pr("add %s %s,%s\n", set_name, addr, dev->name);
+   }
+
+   if (exec) {
+   fw3_pr("quit\n");
+   fw3_command_close();
+   }
+}
diff --git a/ipsets.h b/ipsets.h
index 76078d4..19528e9 100644
--- a/ipsets.h
+++ b/ipsets.h
@@ -41,6 +41,13 @@ void
 fw3_ipsets_update_run_state(enum fw3_family family, struct fw3_state 
*run_state,
struct fw3_state *cfg_state);
 
+struct fw3_ipset *
+fw3_alloc_ipset(struct fw3_state *state);
+
+void
+fw3_ipset_add_devices(struct list_head *devices, enum fw3_family family,
+ const char *set_name);
+
 static inline void fw3_free_ipset(struct fw3_ipset *ipset)
 {
list_del(>list);
diff --git a/main.c b/main.c
index 7ad00b4..7b9c9e3 100644
--- a/main.c
+++ b/main.c
@@ -266,6 +266,9 @@ start(void)
continue;
}
 
+   if (!print_family)
+   fw3_fill_zone_ipsets(family, cfg_state);
+
for (table = FW3_TABLE_FILTER; table <= FW3_TABLE_RAW; table++)
{
if