From 0fb0e506d01f99548dbb9cabfef713bea7e447b5 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Fri, 24 Jun 2022 16:02:59 +0200 Subject: [PATCH] rule: collapse set element commands Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1917398 Upstream Status: nftables commit 498a5f0c219d8 commit 498a5f0c219d8a118af4f172f248647d9b077101 Author: Pablo Neira Ayuso Date: Mon Jun 13 17:22:44 2022 +0200 rule: collapse set element commands 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 Signed-off-by: Phil Sutter --- include/expression.h | 1 + include/rule.h | 3 ++ src/libnftables.c | 17 ++++++++-- src/rule.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/include/expression.h b/include/expression.h index 2c3818e..53194c9 100644 --- a/include/expression.h +++ b/include/expression.h @@ -243,6 +243,7 @@ struct expr { enum expr_types etype:8; enum ops op:8; unsigned int len; + struct cmd *cmd; union { struct { diff --git a/include/rule.h b/include/rule.h index e232b97..9081225 100644 --- a/include/rule.h +++ b/include/rule.h @@ -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); diff --git a/src/libnftables.c b/src/libnftables.c index 6a22ea0..aac682b 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -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) { diff --git a/src/rule.c b/src/rule.c index 7f61bdc..0526a14 100644 --- a/src/rule.c +++ b/src/rule.c @@ -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; -- 2.36.1