Blame SOURCES/0002-rule-collapse-set-element-commands.patch

e34dd0
From 0fb0e506d01f99548dbb9cabfef713bea7e447b5 Mon Sep 17 00:00:00 2001
e34dd0
From: Phil Sutter <psutter@redhat.com>
e34dd0
Date: Fri, 24 Jun 2022 16:02:59 +0200
e34dd0
Subject: [PATCH] rule: collapse set element commands
e34dd0
e34dd0
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1917398
e34dd0
Upstream Status: nftables commit 498a5f0c219d8
e34dd0
e34dd0
commit 498a5f0c219d8a118af4f172f248647d9b077101
e34dd0
Author: Pablo Neira Ayuso <pablo@netfilter.org>
e34dd0
Date:   Mon Jun 13 17:22:44 2022 +0200
e34dd0
e34dd0
    rule: collapse set element commands
e34dd0
e34dd0
    Robots might generate a long list of singleton element commands such as:
e34dd0
e34dd0
      add element t s { 1.0.1.0/24 }
e34dd0
      ...
e34dd0
      add element t s { 1.0.2.0/23 }
e34dd0
e34dd0
    collapse them into one single command before the evaluation step, ie.
e34dd0
e34dd0
      add element t s { 1.0.1.0/24, ..., 1.0.2.0/23 }
e34dd0
e34dd0
    this speeds up overlap detection and set element automerge operations in
e34dd0
    this worst case scenario.
e34dd0
e34dd0
    Since 3da9643fb9ff9 ("intervals: add support to automerge with kernel
e34dd0
    elements"), the new interval tracking relies on mergesort. The pattern
e34dd0
    above triggers the set sorting for each element.
e34dd0
e34dd0
    This patch adds a list to cmd objects that store collapsed commands.
e34dd0
    Moreover, expressions also contain a reference to the original command,
e34dd0
    to uncollapse the commands after the evaluation step.
e34dd0
e34dd0
    These commands are uncollapsed after the evaluation step to ensure error
e34dd0
    reporting works as expected (command and netlink message are mapped
e34dd0
    1:1).
e34dd0
e34dd0
    For the record:
e34dd0
e34dd0
    - nftables versions <= 1.0.2 did not perform any kind of overlap
e34dd0
      check for the described scenario above (because set cache only contained
e34dd0
      elements in the kernel in this case). This is a problem for kernels < 5.7
e34dd0
      which rely on userspace to detect overlaps.
e34dd0
e34dd0
    - the overlap detection could be skipped for kernels >= 5.7.
e34dd0
e34dd0
    - The extended netlink error reporting available for set elements
e34dd0
      since 5.19-rc might allow to remove the uncollapse step, in this case,
e34dd0
      error reporting does not rely on the netlink sequence to refer to the
e34dd0
      command triggering the problem.
e34dd0
e34dd0
    Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
e34dd0
e34dd0
Signed-off-by: Phil Sutter <psutter@redhat.com>
e34dd0
---
e34dd0
 include/expression.h |  1 +
e34dd0
 include/rule.h       |  3 ++
e34dd0
 src/libnftables.c    | 17 ++++++++--
e34dd0
 src/rule.c           | 75 ++++++++++++++++++++++++++++++++++++++++++++
e34dd0
 4 files changed, 93 insertions(+), 3 deletions(-)
e34dd0
e34dd0
diff --git a/include/expression.h b/include/expression.h
e34dd0
index 2c3818e..53194c9 100644
e34dd0
--- a/include/expression.h
e34dd0
+++ b/include/expression.h
e34dd0
@@ -243,6 +243,7 @@ struct expr {
e34dd0
 	enum expr_types		etype:8;
e34dd0
 	enum ops		op:8;
e34dd0
 	unsigned int		len;
e34dd0
+	struct cmd		*cmd;
e34dd0
 
e34dd0
 	union {
e34dd0
 		struct {
e34dd0
diff --git a/include/rule.h b/include/rule.h
e34dd0
index e232b97..9081225 100644
e34dd0
--- a/include/rule.h
e34dd0
+++ b/include/rule.h
e34dd0
@@ -700,6 +700,7 @@ struct cmd {
e34dd0
 	enum cmd_obj		obj;
e34dd0
 	struct handle		handle;
e34dd0
 	uint32_t		seqnum;
e34dd0
+	struct list_head	collapse_list;
e34dd0
 	union {
e34dd0
 		void		*data;
e34dd0
 		struct expr	*expr;
e34dd0
@@ -728,6 +729,8 @@ extern struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
e34dd0
 			     const struct handle *h, const struct location *loc,
e34dd0
 			     void *data);
e34dd0
 extern void nft_cmd_expand(struct cmd *cmd);
e34dd0
+extern bool nft_cmd_collapse(struct list_head *cmds);
e34dd0
+extern void nft_cmd_uncollapse(struct list_head *cmds);
e34dd0
 extern struct cmd *cmd_alloc_obj_ct(enum cmd_ops op, int type,
e34dd0
 				    const struct handle *h,
e34dd0
 				    const struct location *loc, struct obj *obj);
e34dd0
diff --git a/src/libnftables.c b/src/libnftables.c
e34dd0
index 6a22ea0..aac682b 100644
e34dd0
--- a/src/libnftables.c
e34dd0
+++ b/src/libnftables.c
e34dd0
@@ -501,7 +501,9 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
e34dd0
 {
e34dd0
 	struct nft_cache_filter *filter;
e34dd0
 	struct cmd *cmd, *next;
e34dd0
+	bool collapsed = false;
e34dd0
 	unsigned int flags;
e34dd0
+	int err = 0;
e34dd0
 
e34dd0
 	filter = nft_cache_filter_init();
e34dd0
 	flags = nft_cache_evaluate(nft, cmds, filter);
e34dd0
@@ -512,17 +514,26 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
e34dd0
 
e34dd0
 	nft_cache_filter_fini(filter);
e34dd0
 
e34dd0
+	if (nft_cmd_collapse(cmds))
e34dd0
+		collapsed = true;
e34dd0
+
e34dd0
 	list_for_each_entry_safe(cmd, next, cmds, list) {
e34dd0
 		struct eval_ctx ectx = {
e34dd0
 			.nft	= nft,
e34dd0
 			.msgs	= msgs,
e34dd0
 		};
e34dd0
+
e34dd0
 		if (cmd_evaluate(&ectx, cmd) < 0 &&
e34dd0
-		    ++nft->state->nerrs == nft->parser_max_errors)
e34dd0
-			return -1;
e34dd0
+		    ++nft->state->nerrs == nft->parser_max_errors) {
e34dd0
+			err = -1;
e34dd0
+			break;
e34dd0
+		}
e34dd0
 	}
e34dd0
 
e34dd0
-	if (nft->state->nerrs)
e34dd0
+	if (collapsed)
e34dd0
+		nft_cmd_uncollapse(cmds);
e34dd0
+
e34dd0
+	if (err < 0 || nft->state->nerrs)
e34dd0
 		return -1;
e34dd0
 
e34dd0
 	list_for_each_entry(cmd, cmds, list) {
e34dd0
diff --git a/src/rule.c b/src/rule.c
e34dd0
index 7f61bdc..0526a14 100644
e34dd0
--- a/src/rule.c
e34dd0
+++ b/src/rule.c
e34dd0
@@ -1279,6 +1279,8 @@ struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
e34dd0
 	cmd->handle   = *h;
e34dd0
 	cmd->location = *loc;
e34dd0
 	cmd->data     = data;
e34dd0
+	init_list_head(&cmd->collapse_list);
e34dd0
+
e34dd0
 	return cmd;
e34dd0
 }
e34dd0
 
e34dd0
@@ -1379,6 +1381,79 @@ void nft_cmd_expand(struct cmd *cmd)
e34dd0
 	}
e34dd0
 }
e34dd0
 
e34dd0
+bool nft_cmd_collapse(struct list_head *cmds)
e34dd0
+{
e34dd0
+	struct cmd *cmd, *next, *elems = NULL;
e34dd0
+	struct expr *expr, *enext;
e34dd0
+	bool collapse = false;
e34dd0
+
e34dd0
+	list_for_each_entry_safe(cmd, next, cmds, list) {
e34dd0
+		if (cmd->op != CMD_ADD &&
e34dd0
+		    cmd->op != CMD_CREATE) {
e34dd0
+			elems = NULL;
e34dd0
+			continue;
e34dd0
+		}
e34dd0
+
e34dd0
+		if (cmd->obj != CMD_OBJ_ELEMENTS) {
e34dd0
+			elems = NULL;
e34dd0
+			continue;
e34dd0
+		}
e34dd0
+
e34dd0
+		if (!elems) {
e34dd0
+			elems = cmd;
e34dd0
+			continue;
e34dd0
+		}
e34dd0
+
e34dd0
+		if (cmd->op != elems->op) {
e34dd0
+			elems = cmd;
e34dd0
+			continue;
e34dd0
+		}
e34dd0
+
e34dd0
+		if (strcmp(elems->handle.table.name, cmd->handle.table.name) ||
e34dd0
+		    strcmp(elems->handle.set.name, cmd->handle.set.name)) {
e34dd0
+			elems = cmd;
e34dd0
+			continue;
e34dd0
+		}
e34dd0
+
e34dd0
+		collapse = true;
e34dd0
+		list_for_each_entry_safe(expr, enext, &cmd->expr->expressions, list) {
e34dd0
+			expr->cmd = cmd;
e34dd0
+			list_move_tail(&expr->list, &elems->expr->expressions);
e34dd0
+		}
e34dd0
+		elems->expr->size += cmd->expr->size;
e34dd0
+		list_move_tail(&cmd->list, &elems->collapse_list);
e34dd0
+	}
e34dd0
+
e34dd0
+	return collapse;
e34dd0
+}
e34dd0
+
e34dd0
+void nft_cmd_uncollapse(struct list_head *cmds)
e34dd0
+{
e34dd0
+	struct cmd *cmd, *cmd_next, *collapse_cmd, *collapse_cmd_next;
e34dd0
+	struct expr *expr, *next;
e34dd0
+
e34dd0
+	list_for_each_entry_safe(cmd, cmd_next, cmds, list) {
e34dd0
+		if (list_empty(&cmd->collapse_list))
e34dd0
+			continue;
e34dd0
+
e34dd0
+		assert(cmd->obj == CMD_OBJ_ELEMENTS);
e34dd0
+
e34dd0
+		list_for_each_entry_safe(expr, next, &cmd->expr->expressions, list) {
e34dd0
+			if (!expr->cmd)
e34dd0
+				continue;
e34dd0
+
e34dd0
+			list_move_tail(&expr->list, &expr->cmd->expr->expressions);
e34dd0
+			cmd->expr->size--;
e34dd0
+			expr->cmd = NULL;
e34dd0
+		}
e34dd0
+
e34dd0
+		list_for_each_entry_safe(collapse_cmd, collapse_cmd_next, &cmd->collapse_list, list) {
e34dd0
+			collapse_cmd->elem.set = set_get(cmd->elem.set);
e34dd0
+			list_add(&collapse_cmd->list, &cmd->list);
e34dd0
+		}
e34dd0
+	}
e34dd0
+}
e34dd0
+
e34dd0
 struct markup *markup_alloc(uint32_t format)
e34dd0
 {
e34dd0
 	struct markup *markup;
e34dd0
-- 
e34dd0
2.36.1
e34dd0