9a240b84de
* Fri Jun 24 2022 Phil Sutter <psutter@redhat.com> [1.0.4-2.el9] - intervals: Do not sort cached set elements over and over again (Phil Sutter) [1917398] - intervals: do not empty cache for maps (Phil Sutter) [1917398] - intervals: do not report exact overlaps for new elements (Phil Sutter) [1917398] - rule: collapse set element commands (Phil Sutter) [1917398] - tests: shell: runtime set element automerge (Phil Sutter) [1917398] Resolves: rhbz#1917398
237 lines
6.6 KiB
Diff
237 lines
6.6 KiB
Diff
From 0fb0e506d01f99548dbb9cabfef713bea7e447b5 Mon Sep 17 00:00:00 2001
|
|
From: Phil Sutter <psutter@redhat.com>
|
|
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 <pablo@netfilter.org>
|
|
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 <pablo@netfilter.org>
|
|
|
|
Signed-off-by: Phil Sutter <psutter@redhat.com>
|
|
---
|
|
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
|
|
|