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

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