]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
rule: collapse set element commands
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 13 Jun 2022 15:22:44 +0000 (17:22 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Sat, 18 Jun 2022 22:55:16 +0000 (00:55 +0200)
Robots might generate a long list of singleton element commands such as:

  add element t s { 1.0.1.0/24 }
  ...
  add element t s { 1.0.2.0/23 }

collapse them into one single command before the evaluation step, ie.

  add element t s { 1.0.1.0/24, ..., 1.0.2.0/23 }

this speeds up overlap detection and set element automerge operations in
this worst case scenario.

Since 3da9643fb9ff9 ("intervals: add support to automerge with kernel
elements"), the new interval tracking relies on mergesort. The pattern
above triggers the set sorting for each element.

This patch adds a list to cmd objects that store collapsed commands.
Moreover, expressions also contain a reference to the original command,
to uncollapse the commands after the evaluation step.

These commands are uncollapsed after the evaluation step to ensure error
reporting works as expected (command and netlink message are mapped
1:1).

For the record:

- nftables versions <= 1.0.2 did not perform any kind of overlap
  check for the described scenario above (because set cache only contained
  elements in the kernel in this case). This is a problem for kernels < 5.7
  which rely on userspace to detect overlaps.

- the overlap detection could be skipped for kernels >= 5.7.

- The extended netlink error reporting available for set elements
  since 5.19-rc might allow to remove the uncollapse step, in this case,
  error reporting does not rely on the netlink sequence to refer to the
  command triggering the problem.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/expression.h
include/rule.h
src/libnftables.c
src/rule.c

index 2c3818e89b791926d65fb53f38605f87c1db5e54..53194c9248e4b3866f9c2b348f35ffa60f1418b9 100644 (file)
@@ -243,6 +243,7 @@ struct expr {
        enum expr_types         etype:8;
        enum ops                op:8;
        unsigned int            len;
+       struct cmd              *cmd;
 
        union {
                struct {
index e232b97afed781597e6fc2a716526b70ca4bd371..908122559cdf754dcd9de42938abdfbe2c0053c2 100644 (file)
@@ -700,6 +700,7 @@ struct cmd {
        enum cmd_obj            obj;
        struct handle           handle;
        uint32_t                seqnum;
+       struct list_head        collapse_list;
        union {
                void            *data;
                struct expr     *expr;
@@ -728,6 +729,8 @@ extern struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
                             const struct handle *h, const struct location *loc,
                             void *data);
 extern void nft_cmd_expand(struct cmd *cmd);
+extern bool nft_cmd_collapse(struct list_head *cmds);
+extern void nft_cmd_uncollapse(struct list_head *cmds);
 extern struct cmd *cmd_alloc_obj_ct(enum cmd_ops op, int type,
                                    const struct handle *h,
                                    const struct location *loc, struct obj *obj);
index 6a22ea093952413c36948bb8fc13141f89235a40..aac682b706ffabc857b717135ceb85f236a25901 100644 (file)
@@ -501,7 +501,9 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
 {
        struct nft_cache_filter *filter;
        struct cmd *cmd, *next;
+       bool collapsed = false;
        unsigned int flags;
+       int err = 0;
 
        filter = nft_cache_filter_init();
        flags = nft_cache_evaluate(nft, cmds, filter);
@@ -512,17 +514,26 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
 
        nft_cache_filter_fini(filter);
 
+       if (nft_cmd_collapse(cmds))
+               collapsed = true;
+
        list_for_each_entry_safe(cmd, next, cmds, list) {
                struct eval_ctx ectx = {
                        .nft    = nft,
                        .msgs   = msgs,
                };
+
                if (cmd_evaluate(&ectx, cmd) < 0 &&
-                   ++nft->state->nerrs == nft->parser_max_errors)
-                       return -1;
+                   ++nft->state->nerrs == nft->parser_max_errors) {
+                       err = -1;
+                       break;
+               }
        }
 
-       if (nft->state->nerrs)
+       if (collapsed)
+               nft_cmd_uncollapse(cmds);
+
+       if (err < 0 || nft->state->nerrs)
                return -1;
 
        list_for_each_entry(cmd, cmds, list) {
index 7f61bdc1cec9984f08de81ba7989d6bdfb09fb21..0526a1482438b45be606258cd2cf008cbcecc506 100644 (file)
@@ -1279,6 +1279,8 @@ struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
        cmd->handle   = *h;
        cmd->location = *loc;
        cmd->data     = data;
+       init_list_head(&cmd->collapse_list);
+
        return cmd;
 }
 
@@ -1379,6 +1381,79 @@ void nft_cmd_expand(struct cmd *cmd)
        }
 }
 
+bool nft_cmd_collapse(struct list_head *cmds)
+{
+       struct cmd *cmd, *next, *elems = NULL;
+       struct expr *expr, *enext;
+       bool collapse = false;
+
+       list_for_each_entry_safe(cmd, next, cmds, list) {
+               if (cmd->op != CMD_ADD &&
+                   cmd->op != CMD_CREATE) {
+                       elems = NULL;
+                       continue;
+               }
+
+               if (cmd->obj != CMD_OBJ_ELEMENTS) {
+                       elems = NULL;
+                       continue;
+               }
+
+               if (!elems) {
+                       elems = cmd;
+                       continue;
+               }
+
+               if (cmd->op != elems->op) {
+                       elems = cmd;
+                       continue;
+               }
+
+               if (strcmp(elems->handle.table.name, cmd->handle.table.name) ||
+                   strcmp(elems->handle.set.name, cmd->handle.set.name)) {
+                       elems = cmd;
+                       continue;
+               }
+
+               collapse = true;
+               list_for_each_entry_safe(expr, enext, &cmd->expr->expressions, list) {
+                       expr->cmd = cmd;
+                       list_move_tail(&expr->list, &elems->expr->expressions);
+               }
+               elems->expr->size += cmd->expr->size;
+               list_move_tail(&cmd->list, &elems->collapse_list);
+       }
+
+       return collapse;
+}
+
+void nft_cmd_uncollapse(struct list_head *cmds)
+{
+       struct cmd *cmd, *cmd_next, *collapse_cmd, *collapse_cmd_next;
+       struct expr *expr, *next;
+
+       list_for_each_entry_safe(cmd, cmd_next, cmds, list) {
+               if (list_empty(&cmd->collapse_list))
+                       continue;
+
+               assert(cmd->obj == CMD_OBJ_ELEMENTS);
+
+               list_for_each_entry_safe(expr, next, &cmd->expr->expressions, list) {
+                       if (!expr->cmd)
+                               continue;
+
+                       list_move_tail(&expr->list, &expr->cmd->expr->expressions);
+                       cmd->expr->size--;
+                       expr->cmd = NULL;
+               }
+
+               list_for_each_entry_safe(collapse_cmd, collapse_cmd_next, &cmd->collapse_list, list) {
+                       collapse_cmd->elem.set = set_get(cmd->elem.set);
+                       list_add(&collapse_cmd->list, &cmd->list);
+               }
+       }
+}
+
 struct markup *markup_alloc(uint32_t format)
 {
        struct markup *markup;