Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2220924/?format=api
{ "id": 2220924, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2220924/?format=api", "web_url": "http://patchwork.ozlabs.org/project/netfilter-devel/patch/20260408115922.48676-6-pablo@netfilter.org/", "project": { "id": 26, "url": "http://patchwork.ozlabs.org/api/1.1/projects/26/?format=api", "name": "Netfilter Development", "link_name": "netfilter-devel", "list_id": "netfilter-devel.vger.kernel.org", "list_email": "netfilter-devel@vger.kernel.org", "web_url": null, "scm_url": null, "webscm_url": null }, "msgid": "<20260408115922.48676-6-pablo@netfilter.org>", "date": "2026-04-08T11:59:22", "name": "[nft,5/5] libnftables: support for several list and reset commands", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "1f422f6bc83ff4a326d1a4d6d31990d9a3c47fa6", "submitter": { "id": 1315, "url": "http://patchwork.ozlabs.org/api/1.1/people/1315/?format=api", "name": "Pablo Neira Ayuso", "email": "pablo@netfilter.org" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/netfilter-devel/patch/20260408115922.48676-6-pablo@netfilter.org/mbox/", "series": [ { "id": 499127, "url": "http://patchwork.ozlabs.org/api/1.1/series/499127/?format=api", "web_url": "http://patchwork.ozlabs.org/project/netfilter-devel/list/?series=499127", "date": "2026-04-08T11:59:18", "name": "support for several list and reset commands", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/499127/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2220924/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2220924/checks/", "tags": {}, "headers": { "Return-Path": "\n <netfilter-devel+bounces-11730-incoming=patchwork.ozlabs.org@vger.kernel.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "netfilter-devel@vger.kernel.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=netfilter.org header.i=@netfilter.org\n header.a=rsa-sha256 header.s=2025 header.b=X3gmXUDZ;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c15:e001:75::12fc:5321; helo=sin.lore.kernel.org;\n envelope-from=netfilter-devel+bounces-11730-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=netfilter.org header.i=@netfilter.org\n header.b=\"X3gmXUDZ\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=217.70.190.124", "smtp.subspace.kernel.org;\n dmarc=none (p=none dis=none) header.from=netfilter.org", "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=netfilter.org" ], "Received": [ "from sin.lore.kernel.org (sin.lore.kernel.org\n [IPv6:2600:3c15:e001:75::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4frMF34jYSz1xv0\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 08 Apr 2026 22:04:39 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sin.lore.kernel.org (Postfix) with ESMTP id 349D13004DAA\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 8 Apr 2026 11:59:38 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 810993B8BA1;\n\tWed, 8 Apr 2026 11:59:37 +0000 (UTC)", "from mail.netfilter.org (mail.netfilter.org [217.70.190.124])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 63C563B6BF7\n\tfor <netfilter-devel@vger.kernel.org>; Wed, 8 Apr 2026 11:59:35 +0000 (UTC)", "from localhost.localdomain (mail-agni [217.70.190.124])\n\tby mail.netfilter.org (Postfix) with ESMTPSA id 42B776033A;\n\tWed, 8 Apr 2026 13:59:33 +0200 (CEST)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775649577; cv=none;\n b=Jn7UCxtl9cN+QR7rAEt5PxtlFhWi9sO4uYzn5fD6MWLTSY6ldo2X5R0yLl8i524KITbJkPQdVRyZSChuL8ueojsFtxTvu1U6legYFdxX7qBFvw4CMtTW76bMAMPv8vI+7r6Ko2BFaLSl6XgsL2SvBl5LO4Jmy2ai/8RSWbhldH0=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775649577; c=relaxed/simple;\n\tbh=nkgXogrGRg/OmkvQStAE4PRF9XlDZW1586B3EvJrOrQ=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=kM/IL+HtdimKv+PKDnfsNfE4puWtM9f71wKsPMOePobi7ctmVuGfkCTFhjyJ9uZOzC/cl8VugekhwlfnNBgpXXyF2ymemu0wDXuZowbZCMm3YlRWQfKa2ancf9BBRp28mrEWNILJhzVyRYiEz+CjIjCPFAXOUWuk/6/P/g2bn64=", "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=none (p=none dis=none) header.from=netfilter.org;\n spf=pass smtp.mailfrom=netfilter.org;\n dkim=pass (2048-bit key) header.d=netfilter.org header.i=@netfilter.org\n header.b=X3gmXUDZ; arc=none smtp.client-ip=217.70.190.124", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=netfilter.org;\n\ts=2025; t=1775649573;\n\tbh=OlAADbsc21NVZGeMocaLErZyMDmKKEVoLvaiRcfK0PU=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=X3gmXUDZ6bmfPiQ5Q2IH24yt3UVvQGux49au9d6RJhOQpPOtNzHwsacefpQClbZMX\n\t LX2EV2QmSiYVHxRNLn1u0fGrgn4ewiMN3WQJkeTQjRwPdl9kGpQlhumwV4jjp3YDom\n\t jnZfW9BiI7g3Z6vW1FxLZ7PBBMsjyO0Ge/+dmgVQkrihpziFutLwX65BSlRUbSrPtQ\n\t sTCCtm/A84qzPyl3oY9FmKX0YshBCXKvVF2I1Oios5lyFySkRAU43b+ydWMvrMhu5v\n\t MAsV4bMwEd5SP/UhhmE3y9KCox1MqqyWxGrL9kLo5VAG+Ta2HBQErJrUHzTM/wgD7W\n\t FFqQVQmDOpDwg==", "From": "Pablo Neira Ayuso <pablo@netfilter.org>", "To": "netfilter-devel@vger.kernel.org", "Cc": "phil@nwl.cc,\n\tfw@strlen.de", "Subject": "[PATCH nft 5/5] libnftables: support for several list and reset\n commands", "Date": "Wed, 8 Apr 2026 13:59:22 +0200", "Message-ID": "<20260408115922.48676-6-pablo@netfilter.org>", "X-Mailer": "git-send-email 2.47.3", "In-Reply-To": "<20260408115922.48676-1-pablo@netfilter.org>", "References": "<20260408115922.48676-1-pablo@netfilter.org>", "Precedence": "bulk", "X-Mailing-List": "netfilter-devel@vger.kernel.org", "List-Id": "<netfilter-devel.vger.kernel.org>", "List-Subscribe": "<mailto:netfilter-devel+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:netfilter-devel+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit" }, "content": "The cache logic woes with either more than one single list or reset\ncommand, e.g. list table x; list table y;\n\nOne possibility is to handle this from the cache logic itself through\nthe existing netlink dump filter infrastructure, but it looks fragile\nbecause this needs to combine the different dump filtering requirements.\n\nThe list and reset commands have different semantics, these commands are\nnot built into the batch that is delivered to the 2-phase commit\nprotocol in nf_tables, instead these commands follow the netlink dump\npath to fetch (and reset) data from the kernel.\n\nThis patch updates the parser to create one cmd_batch object that\nstores the usual add/delete cmd object in a batch. The exception are\nCMD_LIST and CMD_RESET commands, which have a single cmd_batch object\nwith a single command. Then, iterate over the cmd_batch to handle the\ncommands sequentially.\n\nThe structure is a list of lists, collecting commands\n\n .-----------.\n | cmd_batch |-> add cmd -> add cmd -> add cmd\n `-----------'\n |\n .-----------.\n | cmd_batch |-> list cmd\n `-----------'\n\nThis is handled sequentially, first a batch for the 2-commit phase\nprotocol is build and delivered, then the list command fetches the\ncontent in the kernel, so it shows the changes already applied by the\nprevious batch.\n\nIn most case, there will be a single cmd_batch object, unless list and\nreset commands are used.\n\nAfter this patch, list and reset commands result in an implicit end of\nbatch/transaction when mixed with other existing commands. To the user,\nthese commands are handled now sequentially.\n\nGiven list and reset commands trigger this implicit end of batch, this\nadds a restriction to disallow complicated mixes that could result in\nmore than one single transaction. Currently this allows for a batch\nfor the 2-phase commit protocol and commands to list/reset as long as\nthey are not intertwined, eg.\n\n .-----------.\n | cmd_batch |-> add cmd -> add cmd -> add cmd\n `-----------'\n |\n .-----------.\n | cmd_batch |-> list cmd\n `-----------'\n |\n .-----------.\n | cmd_batch |-> add cmd -> add cmd -> add cmd\n `-----------'\n\nThis is not allowed, this reports \"unsupported command mix\" as an error.\n\nThere is also a bug in bugzilla that refers to users hitting issues when\nusing the list command in a file, this patch should address this too.\n\nReported-by: Phil Sutter <phil@nwl.cc>\nSigned-off-by: Pablo Neira Ayuso <pablo@netfilter.org>\n---\n include/cmd.h | 10 +++++\n src/cmd.c | 100 ++++++++++++++++++++++++++++++++++++++++++++-\n src/libnftables.c | 39 +++++++++++++++---\n src/parser_bison.y | 12 +++++-\n src/parser_json.c | 9 ++--\n 5 files changed, 157 insertions(+), 13 deletions(-)", "diff": "diff --git a/include/cmd.h b/include/cmd.h\nindex cf7e43bf46ec..48f4a07675ed 100644\n--- a/include/cmd.h\n+++ b/include/cmd.h\n@@ -1,6 +1,8 @@\n #ifndef _NFT_CMD_H_\n #define _NFT_CMD_H_\n \n+#include <list.h>\n+\n void cmd_add_loc(struct cmd *cmd, const struct nlmsghdr *nlh, const struct location *loc);\n struct mnl_err;\n void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd,\n@@ -11,4 +13,12 @@ bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds,\n \n void nft_cmd_expand(struct cmd *cmd);\n \n+struct cmd_batch {\n+\tstruct list_head\tlist;\n+\tstruct list_head\tsublist;\n+};\n+\n+void __cmd_batch_add(struct cmd *cmd, struct list_head *cmds);\n+int cmd_batch_add(struct cmd *cmd, struct list_head *cmds);\n+\n #endif\ndiff --git a/src/cmd.c b/src/cmd.c\nindex 9d5544f03c32..365fcd8ec0d9 100644\n--- a/src/cmd.c\n+++ b/src/cmd.c\n@@ -384,6 +384,7 @@ static void nft_cmd_expand_chain(struct chain *chain, struct list_head *new_cmds\n bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds,\n \t\t\t struct handle *handle, struct expr *init)\n {\n+\tstruct cmd_batch *cmd_batch;\n \tstruct cmd *last_cmd;\n \n \tif (list_empty(cmds))\n@@ -392,7 +393,9 @@ bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds,\n \tif (init->etype == EXPR_VARIABLE)\n \t\treturn false;\n \n-\tlast_cmd = list_last_entry(cmds, struct cmd, list);\n+\tcmd_batch = list_last_entry(cmds, struct cmd_batch, list);\n+\n+\tlast_cmd = list_last_entry(&cmd_batch->sublist, struct cmd, list);\n \tif (last_cmd->op != op ||\n \t last_cmd->obj != CMD_OBJ_ELEMENTS ||\n \t last_cmd->expr->etype == EXPR_VARIABLE ||\n@@ -489,3 +492,98 @@ void nft_cmd_expand(struct cmd *cmd)\n \t\tbreak;\n \t}\n }\n+\n+void __cmd_batch_add(struct cmd *cmd, struct list_head *cmds)\n+{\n+\tstruct cmd_batch *cmd_batch;\n+\n+\tcmd_batch = xmalloc(sizeof(struct cmd_batch));\n+\tinit_list_head(&cmd_batch->sublist);\n+\tlist_add_tail(&cmd->list, &cmd_batch->sublist);\n+\tlist_add_tail(&cmd_batch->list, cmds);\n+}\n+\n+/* Reject a batch mixing too many of these commands. */\n+static int cmd_batch_mix(struct cmd *cmd, enum cmd_ops last_cmd_op)\n+{\n+\tif ((last_cmd_op == CMD_LIST || last_cmd_op == CMD_RESET) &&\n+\t (cmd->op != CMD_LIST && cmd->op != CMD_RESET))\n+\t\treturn true;\n+\n+\tif ((last_cmd_op != CMD_LIST && last_cmd_op != CMD_RESET) &&\n+\t (cmd->op == CMD_LIST || cmd->op == CMD_RESET))\n+\t\treturn true;\n+\n+\treturn false;\n+}\n+\n+/* Allow simple mix of list and reset commands, the following combinations\n+ * are rejected:\n+ *\n+ *\tadd + list + add\n+ *\n+ * which would trigger two independent add transactions. Same applies\n+ * to this combination.\n+ *\n+ * list + add + list\n+ *\n+ * This is allowed:\n+ *\n+ *\tadd + list\n+ *\tlist + add\n+ *\n+ * This can be one or more commands of the same class, as long as they are\n+ * not intertwined.\n+ */\n+static int cmd_batch_validate(struct list_head *cmds)\n+{\n+\tenum cmd_ops last_cmd_op = CMD_INVALID;\n+\tstruct cmd_batch *cmd_batch;\n+\tstruct cmd *cmd;\n+\tuint32_t mix = 0;\n+\n+\tlist_for_each_entry(cmd_batch, cmds, list) {\n+\t\tcmd = list_first_entry(&cmd_batch->sublist, struct cmd, list);\n+\t\tif (last_cmd_op == CMD_INVALID) {\n+\t\t\tlast_cmd_op = cmd->op;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (cmd_batch_mix(cmd, last_cmd_op))\n+\t\t\tmix++;\n+\n+\t\tif (mix == UINT32_MAX)\n+\t\t\tbreak;\n+\n+\t\tlast_cmd_op = cmd->op;\n+\t}\n+\n+\treturn mix < 2;\n+}\n+\n+int cmd_batch_add(struct cmd *cmd, struct list_head *cmds)\n+{\n+\tstruct cmd_batch *cmd_batch = NULL;\n+\tstruct cmd *last_cmd = NULL;\n+\n+\tif (!list_empty(cmds)) {\n+\t\tcmd_batch = list_last_entry(cmds, struct cmd_batch, list);\n+\t\tlast_cmd = list_last_entry(&cmd_batch->sublist, struct cmd, list);\n+\t}\n+\n+\tif ((cmd->op == CMD_LIST || cmd->op == CMD_RESET) ||\n+\t ((last_cmd && (last_cmd->op == CMD_LIST || last_cmd->op == CMD_RESET)) &&\n+\t (cmd->op != CMD_LIST && cmd->op != CMD_RESET))) {\n+\t\t__cmd_batch_add(cmd, cmds);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!cmd_batch)\n+\t\t__cmd_batch_add(cmd, cmds);\n+\telse\n+\t\tlist_add_tail(&cmd->list, &cmd_batch->sublist);\n+out:\n+\tif (!cmd_batch_validate(cmds))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\ndiff --git a/src/libnftables.c b/src/libnftables.c\nindex 987f5d73ade4..c645314d89b2 100644\n--- a/src/libnftables.c\n+++ b/src/libnftables.c\n@@ -665,7 +665,8 @@ err:\n EXPORT_SYMBOL(nft_run_cmd_from_buffer);\n int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf)\n {\n-\tint rc = -EINVAL;\n+\tstruct cmd_batch *cmd_batch, *next;\n+\tint rc = -EINVAL, rc_loop = 0;\n \tLIST_HEAD(msgs);\n \tLIST_HEAD(cmds);\n \tchar *nlbuf;\n@@ -679,7 +680,19 @@ int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf)\n \t\trc = nft_parse_bison_buffer(nft, nlbuf, &msgs, &cmds,\n \t\t\t\t\t &indesc_cmdline);\n \n-\trc = nft_eval_run_cmds(nft, &msgs, &cmds, rc);\n+\tif (rc < 0 && list_empty(&cmds))\n+\t\terec_print_list(&nft->output, &msgs, nft->debug_mask);\n+\n+\tlist_for_each_entry_safe(cmd_batch, next, &cmds, list) {\n+\t\trc = nft_eval_run_cmds(nft, &msgs, &cmd_batch->sublist, rc);\n+\t\tassert(list_empty(&cmd_batch->sublist));\n+\t\tlist_del(&cmd_batch->list);\n+\t\tfree(cmd_batch);\n+\t\tif (rc < 0)\n+\t\t\trc_loop = rc;\n+\t}\n+\tif (rc_loop)\n+\t\trc = rc_loop;\n \n \tfree(nlbuf);\n \tiface_cache_release();\n@@ -760,10 +773,11 @@ static struct error_record *filename_is_useable(struct nft_ctx *nft, const char\n \n static int __nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename)\n {\n+\tstruct cmd_batch *cmd_batch, *next;\n \tstruct error_record *erec;\n+\tint rc, rc_loop = 0;\n \tLIST_HEAD(msgs);\n \tLIST_HEAD(cmds);\n-\tint rc;\n \n \terec = filename_is_useable(nft, filename);\n \tif (erec) {\n@@ -782,10 +796,23 @@ static int __nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename\n \tif (rc == -EINVAL)\n \t\trc = nft_parse_bison_filename(nft, filename, &msgs, &cmds);\n \n-\tif (nft->optimize_flags)\n-\t\tnft_optimize(nft, &cmds);\n+\tif (rc < 0 && list_empty(&cmds))\n+\t\terec_print_list(&nft->output, &msgs, nft->debug_mask);\n+\n+\tlist_for_each_entry_safe(cmd_batch, next, &cmds, list) {\n+\t\tif (nft->optimize_flags)\n+\t\t\tnft_optimize(nft, &cmd_batch->sublist);\n+\n+\t\trc = nft_eval_run_cmds(nft, &msgs, &cmd_batch->sublist, rc);\n+\t\tassert(list_empty(&cmd_batch->sublist));\n+\t\tlist_del(&cmd_batch->list);\n+\t\tfree(cmd_batch);\n+\t\tif (rc < 0)\n+\t\t\trc_loop = rc;\n+\t}\n \n-\trc = nft_eval_run_cmds(nft, &msgs, &cmds, rc);\n+\tif (rc_loop)\n+\t\trc = rc_loop;\n \n \tiface_cache_release();\n \tif (nft->scanner) {\ndiff --git a/src/parser_bison.y b/src/parser_bison.y\nindex 5a334bf0c499..48151a419096 100644\n--- a/src/parser_bison.y\n+++ b/src/parser_bison.y\n@@ -1073,7 +1073,11 @@ input\t\t\t:\t/* empty */\n \t\t\t{\n \t\t\t\tif ($2 != NULL) {\n \t\t\t\t\t$2->location = @2;\n-\t\t\t\t\tlist_add_tail(&$2->list, state->cmds);\n+\t\t\t\t\tif (cmd_batch_add($2, state->cmds) < 0) {\n+\t\t\t\t\t\terec_queue(error(&@2, \"unsupported command mix\"),\n+\t\t\t\t\t\t\t state->msgs);\n+\t\t\t\t\t\tYYERROR;\n+\t\t\t\t\t}\n \t\t\t\t}\n \t\t\t}\n \t\t\t;\n@@ -1210,7 +1214,11 @@ line\t\t\t:\tcommon_block\t\t\t{ $$ = NULL; }\n \t\t\t\t */\n \t\t\t\tif ($1 != NULL) {\n \t\t\t\t\t$1->location = @1;\n-\t\t\t\t\tlist_add_tail(&$1->list, state->cmds);\n+\t\t\t\t\tif (cmd_batch_add($1, state->cmds) < 0) {\n+\t\t\t\t\t\terec_queue(error(&@2, \"unsupported command mix\"),\n+\t\t\t\t\t\t\t state->msgs);\n+\t\t\t\t\t\tYYERROR;\n+\t\t\t\t\t}\n \t\t\t\t}\n \t\t\t\t$$ = NULL;\n \t\t\t\tYYACCEPT;\ndiff --git a/src/parser_json.c b/src/parser_json.c\nindex 2f70b9877c6e..9656e154f052 100644\n--- a/src/parser_json.c\n+++ b/src/parser_json.c\n@@ -4492,7 +4492,6 @@ static int __json_parse(struct json_ctx *ctx)\n \n \tjson_array_foreach(tmp, index, value) {\n \t\t/* this is more or less from parser_bison.y:716 */\n-\t\tLIST_HEAD(list);\n \t\tstruct cmd *cmd;\n \t\tjson_t *tmp2;\n \n@@ -4522,9 +4521,11 @@ static int __json_parse(struct json_ctx *ctx)\n \t\t\treturn -1;\n \t\t}\n \n-\t\tlist_add_tail(&cmd->list, &list);\n-\n-\t\tlist_splice_tail(&list, ctx->cmds);\n+\t\tif (cmd_batch_add(cmd, ctx->cmds) < 0) {\n+\t\t\tjson_error(ctx, \"unsupported command mix\");\n+\t\t\tcmd_free(cmd);\n+\t\t\treturn -1;\n+\t\t}\n \n \t\tif (nft_output_echo(&ctx->nft->output))\n \t\t\tjson_cmd_assoc_add(value, cmd);\n", "prefixes": [ "nft", "5/5" ] }