Linux Netfilter development
 help / color / mirror / Atom feed
* [nft PATCH 0/2] parser_json: fix JSON delete of ct stateful objects
@ 2026-08-11 13:43 Gergely Palotas
  2026-08-11 13:43 ` [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path Gergely Palotas
  2026-08-11 13:43 ` [nft PATCH 2/2] tests: shell: add JSON delete test for ct stateful objects Gergely Palotas
  0 siblings, 2 replies; 4+ messages in thread
From: Gergely Palotas @ 2026-08-11 13:43 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Gergely Palotas

JSON delete commands for ct timeout, ct expectation and tunnel stateful
objects have been broken since their introduction. The dispatch tables
passed NFT_OBJECT_* kernel constants where enum cmd_obj values were
expected, causing the delete path to send the wrong netlink message type
to the kernel (e.g. "delete chain" instead of "delete ct timeout"),
which returned EINVAL.

Patch 1 fixes the mismatch by using CMD_OBJ_* constants consistently
throughout the affected dispatch tables and the function itself.
Patch 2 adds a regression test covering JSON add and delete for all
three affected object types.

Gergely Palotas (2):
  parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path
  tests: shell: add JSON delete test for ct stateful objects

 src/parser_json.c                             |  34 ++---
 .../json/0009json_delete_ct_objects_0         | 140 ++++++++++++++++++
 2 files changed, 155 insertions(+), 19 deletions(-)
 create mode 100755 tests/shell/testcases/json/0009json_delete_ct_objects_0

-- 
2.47.3


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path
  2026-08-11 13:43 [nft PATCH 0/2] parser_json: fix JSON delete of ct stateful objects Gergely Palotas
@ 2026-08-11 13:43 ` Gergely Palotas
  2026-08-20 11:48   ` Pablo Neira Ayuso
  2026-08-11 13:43 ` [nft PATCH 2/2] tests: shell: add JSON delete test for ct stateful objects Gergely Palotas
  1 sibling, 1 reply; 4+ messages in thread
From: Gergely Palotas @ 2026-08-11 13:43 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Gergely Palotas

The dispatch tables in json_parse_cmd_add() and json_parse_cmd_list()
pass NFT_OBJECT_CT_TIMEOUT, NFT_OBJECT_CT_EXPECT and NFT_OBJECT_TUNNEL
(kernel constants from <linux/netfilter/nf_tables.h>) as the cmd_obj
argument to json_parse_cmd_add_object(), which declares its parameter
as enum cmd_obj.

On the delete/list/destroy early-return path, cmd_obj is forwarded
directly to cmd_alloc(), which expects enum cmd_obj values. For ct
timeout, NFT_OBJECT_CT_TIMEOUT=7 aliases to CMD_OBJ_CHAIN=7, so a
JSON delete of a ct timeout is sent to the kernel as a delete chain
netlink message, returning EINVAL. The same mismatch affects ct
expectation and tunnel objects.

The add path was unaffected because the switch/case blocks inside the
function also used NFT_OBJECT_* constants and contained explicit
cmd_obj = CMD_OBJ_* assignments before falling through to cmd_alloc().
Those assignments are never reached on the delete path due to the
early return.

Fix this by using CMD_OBJ_* constants consistently in the dispatch
tables, matching the declared type of the parameter. Update the
switch cases and the CT_HELPER identity checks in the function
prologue to use CMD_OBJ_* as well, and drop the now-unnecessary
cmd_obj reassignments inside each case.

Signed-off-by: Gergely Palotas <palotasgergely@gmail.com>
---
 src/parser_json.c | 34 +++++++++++++++-------------------
 1 file changed, 15 insertions(+), 19 deletions(-)

diff --git a/src/parser_json.c b/src/parser_json.c
index e0144b9b..6f40f8d8 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -3792,11 +3792,11 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 			    "table", &h.table.name))
 		return NULL;
 	if ((op != CMD_DELETE ||
-	     cmd_obj == NFT_OBJECT_CT_HELPER) &&
+	     cmd_obj == CMD_OBJ_CT_HELPER) &&
 	    json_unpack_err(ctx, root, "{s:s}", "name", &h.obj.name)) {
 		return NULL;
 	} else if ((op == CMD_DELETE || op == CMD_DESTROY) &&
-		   cmd_obj != NFT_OBJECT_CT_HELPER &&
+		   cmd_obj != CMD_OBJ_CT_HELPER &&
 		   json_unpack(root, "{s:s}", "name", &h.obj.name) &&
 		   json_unpack(root, "{s:I}", "handle", &h.handle.id)) {
 		json_error(ctx, "Either name or handle required to delete an object.");
@@ -3812,7 +3812,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 		h.obj.name = xstrdup(h.obj.name);
 
 	if (op == CMD_DELETE || op == CMD_LIST || op == CMD_DESTROY) {
-		if (cmd_obj == NFT_OBJECT_CT_HELPER)
+		if (cmd_obj == CMD_OBJ_CT_HELPER)
 			return cmd_alloc_obj_ct(op, NFT_OBJECT_CT_HELPER,
 						&h, int_loc, obj_alloc(int_loc));
 		return cmd_alloc(op, cmd_obj, &h, int_loc, NULL);
@@ -3849,8 +3849,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 			}
 		}
 		break;
-	case NFT_OBJECT_CT_HELPER:
-		cmd_obj = CMD_OBJ_CT_HELPER;
+	case CMD_OBJ_CT_HELPER:
 		obj->type = NFT_OBJECT_CT_HELPER;
 		if (!json_unpack(root, "{s:s}", "type", &tmp)) {
 			int ret;
@@ -3881,8 +3880,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 		}
 		obj->ct_helper.l3proto = l3proto;
 		break;
-	case NFT_OBJECT_CT_TIMEOUT:
-		cmd_obj = CMD_OBJ_CT_TIMEOUT;
+	case CMD_OBJ_CT_TIMEOUT:
 		init_list_head(&obj->ct_timeout.timeout_list);
 		obj->type = NFT_OBJECT_CT_TIMEOUT;
 		if (!json_unpack(root, "{s:s}", "protocol", &tmp)) {
@@ -3905,8 +3903,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 		if (json_parse_ct_timeout_policy(ctx, root, obj))
 			goto err_free_obj;
 		break;
-	case NFT_OBJECT_CT_EXPECT:
-		cmd_obj = CMD_OBJ_CT_EXPECT;
+	case CMD_OBJ_CT_EXPECT:
 		obj->type = NFT_OBJECT_CT_EXPECT;
 		if (!json_unpack(root, "{s:s}", "l3proto", &tmp) &&
 		    parse_family(tmp, &l3proto)) {
@@ -3972,8 +3969,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 
 		obj->synproxy.flags |= flags;
 		break;
-	case NFT_OBJECT_TUNNEL:
-		cmd_obj = CMD_OBJ_TUNNEL;
+	case CMD_OBJ_TUNNEL:
 		obj->type = NFT_OBJECT_TUNNEL;
 		if (json_parse_tunnel(ctx, root, obj))
 			goto err_free_obj;
@@ -4011,10 +4007,10 @@ static struct cmd *json_parse_cmd_add(struct json_ctx *ctx,
 		{ "flowtable", CMD_OBJ_FLOWTABLE, json_parse_cmd_add_flowtable },
 		{ "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object },
 		{ "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object },
-		{ "ct helper", NFT_OBJECT_CT_HELPER, json_parse_cmd_add_object },
-		{ "ct timeout", NFT_OBJECT_CT_TIMEOUT, json_parse_cmd_add_object },
-		{ "ct expectation", NFT_OBJECT_CT_EXPECT, json_parse_cmd_add_object },
-		{ "tunnel", NFT_OBJECT_TUNNEL, json_parse_cmd_add_object },
+		{ "ct helper", CMD_OBJ_CT_HELPER, json_parse_cmd_add_object },
+		{ "ct timeout", CMD_OBJ_CT_TIMEOUT, json_parse_cmd_add_object },
+		{ "ct expectation", CMD_OBJ_CT_EXPECT, json_parse_cmd_add_object },
+		{ "tunnel", CMD_OBJ_TUNNEL, json_parse_cmd_add_object },
 		{ "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object },
 		{ "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object },
 		{ "synproxy", CMD_OBJ_SYNPROXY, json_parse_cmd_add_object }
@@ -4186,11 +4182,11 @@ static struct cmd *json_parse_cmd_list(struct json_ctx *ctx,
 		{ "counters", CMD_OBJ_COUNTERS, json_parse_cmd_list_multiple },
 		{ "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object },
 		{ "quotas", CMD_OBJ_QUOTAS, json_parse_cmd_list_multiple },
-		{ "ct helper", NFT_OBJECT_CT_HELPER, json_parse_cmd_add_object },
+		{ "ct helper", CMD_OBJ_CT_HELPER, json_parse_cmd_add_object },
 		{ "ct helpers", CMD_OBJ_CT_HELPERS, json_parse_cmd_list_multiple },
-		{ "ct timeout", NFT_OBJECT_CT_TIMEOUT, json_parse_cmd_add_object },
-		{ "ct expectation", NFT_OBJECT_CT_EXPECT, json_parse_cmd_add_object },
-		{ "tunnel", NFT_OBJECT_TUNNEL, json_parse_cmd_add_object },
+		{ "ct timeout", CMD_OBJ_CT_TIMEOUT, json_parse_cmd_add_object },
+		{ "ct expectation", CMD_OBJ_CT_EXPECT, json_parse_cmd_add_object },
+		{ "tunnel", CMD_OBJ_TUNNEL, json_parse_cmd_add_object },
 		{ "tunnels", CMD_OBJ_TUNNELS, json_parse_cmd_list_multiple },
 		{ "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object },
 		{ "limits", CMD_OBJ_LIMIT, json_parse_cmd_list_multiple },
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [nft PATCH 2/2] tests: shell: add JSON delete test for ct stateful objects
  2026-08-11 13:43 [nft PATCH 0/2] parser_json: fix JSON delete of ct stateful objects Gergely Palotas
  2026-08-11 13:43 ` [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path Gergely Palotas
@ 2026-08-11 13:43 ` Gergely Palotas
  1 sibling, 0 replies; 4+ messages in thread
From: Gergely Palotas @ 2026-08-11 13:43 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Gergely Palotas

Verify that JSON delete commands work correctly for ct timeout, ct
expectation and tunnel stateful objects. These were broken before the
previous fix due to a CMD_OBJ/NFT_OBJECT mismatch in the delete path
of json_parse_cmd_add_object().

Signed-off-by: Gergely Palotas <palotasgergely@gmail.com>
---
 .../json/0009json_delete_ct_objects_0         | 140 ++++++++++++++++++
 1 file changed, 140 insertions(+)
 create mode 100755 tests/shell/testcases/json/0009json_delete_ct_objects_0

diff --git a/tests/shell/testcases/json/0009json_delete_ct_objects_0 b/tests/shell/testcases/json/0009json_delete_ct_objects_0
new file mode 100755
index 00000000..78a68be4
--- /dev/null
+++ b/tests/shell/testcases/json/0009json_delete_ct_objects_0
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_json)
+#
+# Regression test for JSON delete of ct timeout, ct expectation and tunnel
+# objects.  Prior to the fix, json_parse_cmd_add_object() passed the raw
+# NFT_OBJECT_* kernel constant to cmd_alloc() on the delete/list/destroy
+# early-return path instead of the correct CMD_OBJ_* userspace enum value.
+# For ct timeout NFT_OBJECT_CT_TIMEOUT=7 aliased to CMD_OBJ_CHAIN=7, causing
+# the kernel to return EINVAL.
+
+set -e
+
+$NFT flush ruleset
+
+$NFT -j -f - << 'EOF'
+{"nftables": [{"add": {"table": {"family": "inet", "name": "t"}}}]}
+EOF
+
+# ===== ct timeout =====
+
+if [ "$NFT_TEST_HAVE_cttimeout" != n ]; then
+
+	echo "Test 1: JSON add ct timeout"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"add": {"ct timeout": {"family": "inet", "table": "t", "name": "ctto", "protocol": "udp", "policy": {"unreplied": 30, "replied": 60}}}}]}
+EOF
+
+	if ! $NFT list ct timeout inet t ctto > /dev/null 2>&1; then
+		echo "Test 1 failed: ct timeout not created"
+		exit 1
+	fi
+
+	echo "Test 2: JSON delete ct timeout by name"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"ct timeout": {"family": "inet", "table": "t", "name": "ctto"}}}]}
+EOF
+
+	if $NFT list ct timeout inet t ctto > /dev/null 2>&1; then
+		echo "Test 2 failed: ct timeout not deleted"
+		exit 1
+	fi
+
+	echo "Test 3: JSON add + delete ct timeout by handle"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"add": {"ct timeout": {"family": "inet", "table": "t", "name": "ctto2", "protocol": "tcp", "policy": {"established": 120}}}}]}
+EOF
+
+	HANDLE=$($NFT -a list ct timeout inet t ctto2 | sed -n 's/.*# handle \([0-9]\+\).*/\1/p')
+	if [ -z "$HANDLE" ]; then
+		echo "Test 3 failed: could not get ct timeout handle"
+		exit 1
+	fi
+
+	$NFT -j -f - << EOF
+{"nftables": [{"delete": {"ct timeout": {"family": "inet", "table": "t", "handle": $HANDLE}}}]}
+EOF
+
+	if $NFT list ct timeout inet t ctto2 > /dev/null 2>&1; then
+		echo "Test 3 failed: ct timeout not deleted by handle"
+		exit 1
+	fi
+
+fi
+
+# ===== ct expectation =====
+
+if [ "$NFT_TEST_HAVE_ctexpect" != n ]; then
+
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"add": {"table": {"family": "ip", "name": "t"}}}]}
+EOF
+
+	echo "Test 4: JSON add ct expectation"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"add": {"ct expectation": {"family": "ip", "table": "t", "name": "ctex", "protocol": "tcp", "dport": 2121, "timeout": 300000, "size": 12}}}]}
+EOF
+
+	if ! $NFT list ct expectation ip t ctex > /dev/null 2>&1; then
+		echo "Test 4 failed: ct expectation not created"
+		exit 1
+	fi
+
+	echo "Test 5: JSON delete ct expectation by name"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"ct expectation": {"family": "ip", "table": "t", "name": "ctex"}}}]}
+EOF
+
+	if $NFT list ct expectation ip t ctex > /dev/null 2>&1; then
+		echo "Test 5 failed: ct expectation not deleted"
+		exit 1
+	fi
+
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"table": {"family": "ip", "name": "t"}}}]}
+EOF
+
+fi
+
+# ===== tunnel =====
+#
+# Tunnel objects must be created and deleted in the same batch as the table
+# when using JSON (kernel lookup limitation with separate add commands).
+
+if [ "$NFT_TEST_HAVE_tunnel" != n ]; then
+
+	echo "Test 6: JSON add+delete tunnel object in batch"
+	$NFT -j -f - << 'EOF'
+{"nftables": [
+  {"add": {"table": {"family": "netdev", "name": "t"}}},
+  {"add": {"tunnel": {"family": "netdev", "table": "t", "name": "tun0", "id": 20, "src-ipv4": "192.168.2.20", "dst-ipv4": "192.168.2.21", "sport": 0, "dport": 4789, "tos": 0, "ttl": 255}}}
+]}
+EOF
+
+	if ! $NFT list tunnel netdev t tun0 > /dev/null 2>&1; then
+		echo "Test 6 failed: tunnel not created"
+		exit 1
+	fi
+
+	echo "Test 7: JSON delete tunnel by name"
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"tunnel": {"family": "netdev", "table": "t", "name": "tun0"}}}]}
+EOF
+
+	if $NFT list tunnel netdev t tun0 > /dev/null 2>&1; then
+		echo "Test 7 failed: tunnel not deleted"
+		exit 1
+	fi
+
+	$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"table": {"family": "netdev", "name": "t"}}}]}
+EOF
+
+fi
+
+$NFT -j -f - << 'EOF'
+{"nftables": [{"delete": {"table": {"family": "inet", "name": "t"}}}]}
+EOF
+
+echo "All tests passed!"
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path
  2026-08-11 13:43 ` [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path Gergely Palotas
@ 2026-08-20 11:48   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 4+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-20 11:48 UTC (permalink / raw)
  To: Gergely Palotas; +Cc: netfilter-devel

On Tue, Aug 11, 2026 at 03:43:25PM +0200, Gergely Palotas wrote:
> The dispatch tables in json_parse_cmd_add() and json_parse_cmd_list()
> pass NFT_OBJECT_CT_TIMEOUT, NFT_OBJECT_CT_EXPECT and NFT_OBJECT_TUNNEL
> (kernel constants from <linux/netfilter/nf_tables.h>) as the cmd_obj
> argument to json_parse_cmd_add_object(), which declares its parameter
> as enum cmd_obj.
> 
> On the delete/list/destroy early-return path, cmd_obj is forwarded
> directly to cmd_alloc(), which expects enum cmd_obj values. For ct
> timeout, NFT_OBJECT_CT_TIMEOUT=7 aliases to CMD_OBJ_CHAIN=7, so a
> JSON delete of a ct timeout is sent to the kernel as a delete chain
> netlink message, returning EINVAL. The same mismatch affects ct
> expectation and tunnel objects.
> 
> The add path was unaffected because the switch/case blocks inside the
> function also used NFT_OBJECT_* constants and contained explicit
> cmd_obj = CMD_OBJ_* assignments before falling through to cmd_alloc().
> Those assignments are never reached on the delete path due to the
> early return.
> 
> Fix this by using CMD_OBJ_* constants consistently in the dispatch
> tables, matching the declared type of the parameter. Update the
> switch cases and the CT_HELPER identity checks in the function
> prologue to use CMD_OBJ_* as well, and drop the now-unnecessary
> cmd_obj reassignments inside each case.

cmd_alloc_obj_ct() expects NFT_OBJECT_CT_*

        switch (type) {
        case NFT_OBJECT_CT_HELPER:
                cmd_obj = CMD_OBJ_CT_HELPER;
                break;
        case NFT_OBJECT_CT_TIMEOUT:
                cmd_obj = CMD_OBJ_CT_TIMEOUT;
                break;
        case NFT_OBJECT_CT_EXPECT:
                cmd_obj = CMD_OBJ_CT_EXPECT;
                break;
        default:
                BUG("missing type mapping");
        }

Maybe this needs to be updated so this looks consistent?

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-20 11:48 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 13:43 [nft PATCH 0/2] parser_json: fix JSON delete of ct stateful objects Gergely Palotas
2026-08-11 13:43 ` [nft PATCH 1/2] parser_json: fix CMD_OBJ/NFT_OBJECT mismatch in delete path Gergely Palotas
2026-08-20 11:48   ` Pablo Neira Ayuso
2026-08-11 13:43 ` [nft PATCH 2/2] tests: shell: add JSON delete test for ct stateful objects Gergely Palotas

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox